本文介绍如何通过 Spring AI 上报 Trace 数据到扣子罗盘。
Spring AI 是一个强大的框架,通过与 OpenTelemetry 的集成,能够自动捕获应用程序中的关键操作和性能指标,并将这些数据作为 Trace 信息上报到扣子罗盘。Spring AI 的自动化功能可以大大简化集成过程。
在通过 Spring AI 上报 Trace 前,需要先添加 Maven 依赖,包括 Spring AI 的模型调用库、OpenTelemetry 检测库、Micrometer 到 OpenTelemetry 的连接器以及 OpenTelemetry 导出器。
本示例使用的是 Spring AI 应用程序调用 OpenAI 模型的 pom.xml 文件,如需使用 Gradle 工具,配置类似。
说明
spring-ai-openai-spring-boot-starter 替换为对应模型的依赖。<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>ai-openai-helloworld</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>ai-openai-helloworld</name>
<description>Simple AI Application using OpenAPI Service</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-M8</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-instrumentation-bom</artifactId>
<version>2.13.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.opentelemetry.instrumentation</groupId>
<artifactId>opentelemetry-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-otel</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.47.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
下述代码是一个调用方舟模型的 application.yaml 配置示例,用于设置 Spring AI 针对 Span 导出的相关开关。
spring:
application:
name: spring-ai-demo # 服务名称,自定义。
ai:
chat:
observations:
include-prompt: true # 是否上报模型输入,默认关闭。
include-completion: true # 是否上报模型输出,默认关闭。
openai: # 使用 OpenAI 协议的模型接口,在此配置。
api-key: "*********" # 填写你的大模型 API_KEY。
chat:
base-url: "https://ark.cn-beijing.volces.com"
completions-path: "/api/v3/chat/completions"
management:
tracing:
sampling:
probability: 1.0 # 采样率,1.0是100%采样,全量Span上报。
以上配置完成了 Spring AI 服务的 Opentelemetry 自动检测配置,服务内部的模型执行过程将被记录到 Span。
在上报 Trace 数据前,你需要正确配置 OpenTelemetry 的环境变量 OTEL_EXPORTER_OTLP_HEADERS 和 OTEL_EXPORTER_OTLP_TRACES_ENDPOINT,用于指定上报数据时所需的扣子罗盘空间 ID,访问令牌和上报地址,以确保数据能够正确发送到指定的扣子罗盘空间中。环境变量配置格式及说明如下:
OTEL_EXPORTER_OTLP_HEADERS: cozeloop-workspace-id={CozeLoop空间ID},Authorization=Bearer {个人访问令牌或服务访问令牌}
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: https://api.coze.cn/v1/loop/opentelemetry/v1/traces
|
环境变量 |
说明 |
|---|---|
|
OTEL_EXPORTER_OTLP_HEADERS |
设置上报数据时所需的认证信息和工作空间标识。包括:
|
|
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT |
用于指定 OpenTelemetry 数据的上报地址,固定为 |
本示例展示了一个基于 Spring AI 框架搭建的应用程序,如何集成豆包大模型实现智能交互,并上报 Trace 数据到扣子罗盘。通过定义 DateTimeTools 类提供获取当前日期时间和天气信息的工具方法,供大模型在需要时调用。使用 ChatClient 构建器配置豆包模型,支持非流式和流式两种响应模式。
package spring.ai.example.spring_ai_demo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.DefaultToolCallingChatOptions;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.i18n.LocaleContextHolder;
import reactor.core.publisher.Flux;
import java.time.LocalDateTime;
@SpringBootApplication
public class Application_tool {
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application_tool.class);
app.run(args);
}
static class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "Get the current weather in the given location")
String getCurrentWeather() {
return "今天上海多云转晴,温度25摄氏度";
}
}
@Bean
CommandLineRunner cli_tool(ChatClient.Builder builder) {
return args -> {
invokeAI(builder);
};
}
private void invokeAI(ChatClient.Builder builder) {
var chat = builder.build();
var prompt = new Prompt("你是一个专业的沟通师,可以根据用户的问题给出准确的回答",
DefaultToolCallingChatOptions.builder().model("doubao-1-5-pro-256k-250115").build());
// 非流式
System.out.println(chat.prompt(prompt).user("今天的日期是什么?")
.tools(new DateTimeTools())
.call()
.content());
// 流式
// Flux<String> stream = chat.prompt(prompt).user("今天的日期是什么?")
// .tools(new DateTimeTools())
// .stream()
// .content();
// stream.subscribe(
// System.out::print, // 处理每个响应部分
// error -> System.err.println("Error: " + error.getMessage()), // 处理错误
// System.out::println // 处理完成事件
// );
}
}
上报 Trace 数据后,你可以在扣子罗盘的 Trace 页面,找到并单击目标 Span,查看上报的 Trace 数据。
本示例展示了如何在 Spring AI 中借助 OpenTelemetry 实现 Span 与用户 ID、会话 ID 的关联,将用户和会话信息添加到 Trace 数据中。示例首先展示了 OpenTelemetry 的初始化配置、工具类定义、追踪上下文管理及 AI 交互逻辑。然后在此基础上,创建 Tracer 和根 Span,通过设置 user.id 与 session.id 绑定用户与会话标识。
package spring.ai.example.spring_ai_demo;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.tool.DefaultToolCallingChatOptions;
import org.springframework.ai.tool.annotation.Tool;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.i18n.LocaleContextHolder;
import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter;
import io.opentelemetry.sdk.trace.export.BatchSpanProcessor;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.api.trace.Tracer;
import io.opentelemetry.context.Scope;
import io.opentelemetry.sdk.OpenTelemetrySdk;
import io.opentelemetry.sdk.trace.SdkTracerProvider;
import reactor.core.publisher.Flux;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
@SpringBootApplication
public class Application {
public static void initOpenTelemetry() {
// 创建一个 OTLP HTTP 导出器
Supplier<Map<String, String>> headersSupplier = () -> {
Map<String, String> headers = new HashMap<>();
headers.put("cozeloop-workspace-id", "750894316061025****");
headers.put("Authorization", "Bearer pat_AyJcG40P6TeIQLJENe6QpVuGs3SiJ3t****");
return headers;
};
OtlpHttpSpanExporter otlpExporter = OtlpHttpSpanExporter.builder()
.setEndpoint("https://api.coze.cn/v1/loop/opentelemetry/v1/traces") // 设置你的 OTLP 接收器的 URL
.setHeaders(headersSupplier)
.build();
// 创建 TracerProvider 并添加批量处理器
SdkTracerProvider tracerProvider = SdkTracerProvider.builder()
.addSpanProcessor(BatchSpanProcessor.builder(otlpExporter).build())
.build();
// 创建 OpenTelemetrySdk 实例
OpenTelemetrySdk openTelemetrySdk = OpenTelemetrySdk.builder()
.setTracerProvider(tracerProvider)
.buildAndRegisterGlobal();
}
public static void main(String[] args) {
SpringApplication app = new SpringApplication(Application.class);
app.run(args);
}
static class DateTimeTools {
@Tool(description = "Get the current date and time in the user's timezone")
String getCurrentDateTime() {
return LocalDateTime.now().atZone(LocaleContextHolder.getTimeZone().toZoneId()).toString();
}
@Tool(description = "Get the current weather in the given location")
String getCurrentWeather() {
return "今天上海多云转晴,温度25摄氏度";
}
}
@Bean
CommandLineRunner cli(ChatClient.Builder builder) {
return args -> {
// 初始化 OpenTelemetry, 供自定义节点使用
initOpenTelemetry();
// 创建一个 Tracer
Tracer tracer = GlobalOpenTelemetry.getTracer("exampleTracer");
// 创建根节点,存储userID和sessionID
Span span = tracer.spanBuilder("root-span")
.setAttribute("user.id", "user-123456")
.setAttribute("session.id", "session-123456")
.startSpan();
try (Scope ignored = span.makeCurrent()) {
invokeAI(builder);
} finally {
span.end();
}
};
}
private void invokeAI(ChatClient.Builder builder) {
var chat = builder.build();
var prompt = new Prompt("你是一个专业的沟通师,可以根据用户的问题给出准确的回答",
DefaultToolCallingChatOptions.builder().model("doubao-1-5-pro-256k-250115").build());
// 非流式
System.out.println(chat.prompt(prompt).user("今天是周几?")
.tools(new DateTimeTools())
.call()
.content());
// 流式
// Flux<String> stream = chat.prompt(prompt).user("What day is today?")
// .tools(new DateTimeTools())
// .stream()
// .content();
// stream.subscribe(
// System.out::print, // 处理每个响应部分
// error -> System.err.println("Error: " + error.getMessage()), // 处理错误
// System.out::println // 处理完成事件
// );
}
}
上报 Trace 数据后,你可以在扣子罗盘的 Trace 页面,找到并单击目标 Span,查看上报的 Trace 数据。
1.0.0-M版本(如1.0.0-M8)
application.yaml 文件中的配置字段分别为 spring.ai.chat.observations.include-prompt 和 spring.ai.chat.observations.include-completion,值为 true,表示打开状态。1.0.0-RC1、1.0.0-SNAPSHOT、1.1.0-SNAPSHOT
application.yaml 文件中的配置字段分别为 spring.ai.chat.observations.log-prompt 和spring.ai.chat.observations.log-completion,值为 true,表示打开状态。application.yaml 文件中的配置字段为spring.ai.tools.observations.include-content,值为 true,表示打开状态,具体数据将展示在 Metadata 里展示。