Spring AI Observability可观测性
作者:微信文章Spring AI Observability可观测性
Spring AI非常好用,但是也经常有人问
• 这个工具调用传的什么参数,为什么返回值不对• 究竟从知识库检索到了哪些内容,模型怎么回答的不符合预期
等等这些问题
当一个框架越成熟,也就意味着,我们越无法了解其内部的运行情况。幸好的是Spring AI提供了可观测性(Observability)支持,为开发者了解框架内部运行情况提供了可能。
本篇我们就来通过可观测性,实现工具调用、知识库检索的日志打印。
什么是可观测性
可观测性在Spring Framework 6 和 Spring Boot 3 中正式推出,可观测性是指:
检查系统的输出,以更好地理解系统的内部工作原理
通过指标、日志、分布式追踪之间的相互关联性,赋予开发者推理系统状态的能力,以便调试应用程序中的异常和延迟
本篇仅通过日志实现可观测性,指标、分布式追踪详细内容,大家请从 Spring 框架文档获取
准备
搭建SpringBoot项目、添加SpringAI依赖、配置Open AI参数,请大家直接看Spring AI入门这篇。
因为众所周知的原因,我们不能直接访问国外网站。如果有需要Open AI 代理地址,请关注公众号,点击菜单apikey 免费获取!
添加依赖,以通过SpringBoot自动配置启用可观测性
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>原理
添加spring-boot-starter-actuator 依赖后,项目启动会创建 ObservationRegistry 实例,然后自动将Spring管理的所有 ObservationHandler 实例添加到 ObservationRegistry 的配置类中。当我们使用Observation实例监控我们代码时,符合Observation.Context 的handle实现类会被执行。
Observation 执行监控示例(示例中自行创建registry并添加handle实例 ):
// 创建 ObservationRegistry
ObservationRegistry registry = ObservationRegistry.create();
// 注册 ObservationHandler
registry.observationConfig().observationHandler(new MyHandler());
// 创建Observation 并执行监测
Observation.createNotStarted("user.name", registry)
.contextualName("getting-user-name")
.lowCardinalityKeyValue("userType", "userType1") // let's assume that you can have 3 user types
.highCardinalityKeyValue("userId", "1234") // let's assume that this is an arbitrary number
.observe(() -> log.info("Hello")); // this is a shortcut for starting an observation, opening a scope, running user's code, closing the scope and stopping the observation工具调用
当模型选择调用工具时,系统并不会有任何输出,开发人员无法得知模型的具体动作,不利于调试和优化。
在Spring AI ToolCalling 中,我们通过@Tool注解创建了获取当前时间、设定闹钟两个工具,下面我们就来打印模型调用工具的具体内容:
首先创建ToolCallingObservationHandler:
@Component
public class ToolCallingObservationHandler implements ObservationHandler<ToolCallingObservationContext> {
private static final Logger logger = LoggerFactory.getLogger(ToolCallingObservationHandler.class);
@Autowired
private ObjectMapper objectMapper;
@Override
public void onStop(ToolCallingObservationContext context) {
try {
logger.info("\n tool calling completion: \n 工具定义:{} \n 请求参数:{} \n 响应内容:{}", objectMapper.writeValueAsString(context.getToolDefinition()), context.getToolCallArguments(), context.getToolCallResult());
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof ToolCallingObservationContext;
}
}
重写方法还有onStart()、onEvent()、onError()等,大家自行选择在哪个生命周期进行自定义处理即可。
创建工具调用controller
@RestController
@RequestMapping("/tool")
public class ToolCallingController {
private final ChatClient chatClient;
ToolCallingController(ChatClient.Builder chatClientBuilder) {
this.chatClient = chatClientBuilder.build();
}
@RequestMapping("/time")
public String time(String userInput) {
String content=this.chatClient.prompt()
.tools(newDateTimeTools())
.user(userInput)
.call()
.content();
return content;
}
}
浏览器请求:http://localhost:8080/tool/time?userInput=现在几点了,我们看一下打印日志
2025-08-29T22:13:25.471+08:00INFO 64415 --- c.s.l.c.ToolCallingObservationHandler :
tool calling completion:
工具定义:{"name":"getCurrentDateTime","description":"Get the current date and time in the user's timezone","inputSchema":"{\n\"$schema\" : \"https://json-schema.org/draft/2020-12/schema\",\n\"type\" : \"object\",\n\"properties\" : { },\n\"required\" : [ ],\n\"additionalProperties\" : false\n}"}
请求参数:{}
响应内容:"2025-08-29T22:13:25.467503+08:00"
这样,我们就可以很清楚的看到工具调用的相关数据。
RAG检索
当使用Spring AI 提供的RetrievalAugmentationAdvisor以实现开箱即用RAG检索时,仅需要一行代码,即可完成RAG的接入,但同时也意味着无法直观的监视RAG数据。
下面我们通过Observability来实现RAG的可观测性。
向量库及数据我们直接使用之前RAG篇的内容,具体请看:Spring AI RAG检索增强生成。
创建RAGObservationHandle实现ObservationHandler
@Component
public class RAGObservationHandle implements ObservationHandler<VectorStoreObservationContext> {
private static final Logger logger = LoggerFactory.getLogger(RAGObservationHandle.class);
public void onStop(VectorStoreObservationContext context) {
logger.info("------------------- rag查询到的数据: start ------------------------");
context.getQueryResponse().forEach(result -> {
// 这里截取一下,避免打印太多数据
logger.info(" rag查询到的数据: {}", result.getText().substring(0, 30));
});
logger.info("------------------- rag查询到的数据: end ------------------------");
}
@Override
public boolean supportsContext(Observation.Context context) {
return context instanceof VectorStoreObservationContext;
}
}
创建RAG 调用接口
@RestController
@RequestMapping(value = "/rag")
public class RagController {
private final ChatClient chatClient;
private final VectorStore vectorStore;
public RagController(ChatClient.Builder chatClientBuilder, VectorStore vectorStore) {
this.chatClient = chatClientBuilder.build();
this.vectorStore = vectorStore;
}
@RequestMapping("/qa")
public String qa(String userInput) {
return chatClient.prompt()
.advisors(newQuestionAnswerAdvisor(vectorStore))
.user(userInput)
.call()
.content();
}
}浏览器访问:http://localhost:8080/rag/qa?userInput=都考哪些科目,查看控制台打印
为了方便查看,这里仅截取了30个字符。Spring AI支持
SpringAI 内置了部分handler,当项目启动时,SpringBoot自动配置监测到相关配置,会创建内置handle实例,交给Sprint管理。
支持的handle配置有
# 打印模型prompt日志
spring.ai.chat.observations.log-prompt=true
# 打印模型完成内容
spring.ai.chat.observations.log-completion=true
# 打印模型错误日志
spring.ai.chat.observations.include-error-logging=true
# 打印图片生成prompt
spring.ai.image.observations.log-prompt=true
# 打印向量查询结果
spring.ai.vectorstore.observations.log-query-response=true下面以添加了spring.ai.chat.observations.log-completion=true为例,当请求完成后,控制台打印如下:
Sprig AI提供的默认handle大多仅返回了简单的内容,并没返回相关的元数据,大家可以根据需求自行实现。
最后
利用 Spring AI 的可观测性功能,通过观察日志,可以了解到工具调用、RAG其内部数据流转情况,方便了我们对应用的调试、记录。另外,Spring AI在很多地方都预留了观察入口,当有需要时,可以查看是否有依赖相应模块的Observation包,自行接入即可。
文章源码、Open AI代理地址、免费api-key请关注公众号免费获取。
页:
[1]