考虑一个问题,AI 应用上线后,想要打印调用大模型的出入参日志(提示词、整体回答),以便后续能够跟踪调试,要如何实现呢?小伙伴们可能会说,自定义一个打印日志的注解 ,通过 AOP 环绕,不就行了吗?
确实,但如果是流式对话,想要一次性打印整个流式回答,而不是片段性的打印,要怎么办呢?貌似之前的方案不太好搞定,有没有什么优雅的方式呢?那就是本文的主题 ------ Advisor 组件。
可以先看下前几篇文章:
Spring AI:对接DeepSeek实战
Spring AI:对接官方 DeepSeek-R1 模型 ------ 实现推理效果
Spring AI:ChatClient实现对话效果
什么是 Advisor?
Advisor 是 Spring AI 中的核心组件,用于在 AI 模型交互过程中动态拦截和增强请求与响应流,类似于面向切面编程(AOP)的拦截器。其核心目标是通过模块化设计提升 AI 应用的灵活性、安全性和扩展性。
通过它,可以实现功能如下:
- 请求与响应拦截:Advisor 能够拦截 AI 模型的输入和输出,允许开发者在请求发送前和响应返回后插入自定义逻辑,例如数据增强、敏感词过滤等;
- 功能模块化:
重复任务封装:例如日志记录、上下文记忆管理等通用功能可封装为可复用组件。
数据转换:优化发送给模型的数据结构(如添加上下文信息),并标准化响应格式。
业务规则注入:例如限制模型输出范围或根据业务需求引导生成内容。 - 跨模型兼容性:通过抽象接口设计,Advisor 可适配不同 AI 模型(如 DeepSeek、OpenAI 等),提升代码的可移植性。
Advisor 处理流程
下图是 Spring AI 官方提供的 Advisor 处理流程图:

大致过程如下:
1、请求初始化: Spring AI 框架根据用户的输入(Prompt)创建一个 AdvisedRequest(请求),并初始化一个空的 AdvisorContext(Advisor 上下文) 对象。
2、Advisor 链路处理:
- 整个链路中,可能包含多个 Advisor, 每个 Advisor 依次处理 AdvisedRequest,可以修改请求内容(例如添加上下文或过滤敏感词)。
- 拦截机制:若某个 Advisor 决定阻止请求(如检测到违规内容),则不再调用后续 Advisor 组件,并直接生成响应(需自行填充 AdvisedResponse)。
3、调用聊天模型
- 最后一个 Advisor(由框架提供)将处理后的请求发送给 聊天模型(Chat Model)(如 DeepSeek)。
4、大模型响应
- 大模型的响应会通过 Advisor 链逆向回传,并被封装为 AdvisedResponse(响应),其中包含共享的 AdvisorContext 实例。
5、响应后处理
- 每个 Advisor 可对响应进行二次处理或修改(例如格式化输出、添加日志等等)。
6、最终响应返回
- 框架从最终的 AdvisedResponse 中提取 ChatCompletion(聊天完成的结果),并将其返回给客户端。
配置 Advisor
接下来,我们就来通过 Spring AI 内置提供的日志记录组件 SimpleLoggerAdvisor ,实现一下打印出入参日志功能。编辑 ChatClientConfig 配置类:
java
@Configuration
public class ChatClientConfig {
/**
* 初始化 ChatClient 客户端
* @param chatModel
* @return
*/
@Bean
public ChatClient chatClient(DeepSeekChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("请你扮演一个智能客服")
.defaultAdvisors(new SimpleLoggerAdvisor()) // 添加 Spring AI 内置的日志记录功能
.build();
}
}
开启 debug 模式
为了能够让 SimpleLoggerAdvisor 组件打印日志,还需要编辑 application.yml 配置文件,将 SimpleLoggerAdvisor 所在的包路径,设置为 debug 模式:
java
logging:
level:
org.springframework.ai.chat.client.advisor: debug
TIP: SimpleLoggerAdvisor 组件的具体包路径,可以点击查看源码来获取,如下:

重启项目,调用接口看下控制台是否有相关日志。
自定义 Advisor
另外,我们也可以自定义 Advisor 组件,接下来,我们尝试自己创建一个记录出入参日志的 Advisor 组件。
添加 Lombok
为了能够方便的输出日志,在 pom.xml 文件中,添加 Lombok 的依赖,如下:
java
<properties>
// 省略
<lombok.version>1.18.30</lombok.version>
</properties>
<dependencies>
// 省略...
<!-- 避免编写那些冗余的 Java 样板式代码,如 get、set 方法等 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>
创建 Advisor
创建一个 /advisor 包,在里面创建 MyLoggerAdvisor 自定义组件:
java
@Slf4j
public class MyLoggerAdvisor implements CallAdvisor {
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
log.info("## 请求入参: {}", chatClientRequest);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
log.info("## 请求出参: {}", chatClientResponse);
return chatClientResponse;
}
@Override
public int getOrder() {
return 1; // order 值越小,越先执行
}
@Override
public String getName() {
// 获取类名称
return this.getClass().getSimpleName();
}
}
解释一下,上述代码中核心的地方:
- 添加 Lombok 提供的 @Slf4j 注解,可以方便的打印日志;
- 注意,自定义的 Advisor 类实现了 CallAdvisor 接口。在 Spring AI 中,CallAdvisor 和 StreamAdvisor是两种不同类型的 Advisor,分别用于处理 同步调用 和 流式调用 场景。它们的设计目的是为了适应不同的 AI 模型交互模式,提供灵活的请求拦截与响应处理能力。咱们这个日志记录 Advisor 仅支持一下同步调用场景,故只实现了 CallAdvisor 接口;
在 adviseCall() 方法中,打印调用大模型的出入参日志; - getOrder() : 支持配置多个 Advisor, 通过 Order 值来决定执行顺序,值越小,越先执行。进入到 SimpleLoggerAdvisor 源码中,会发现其 Order 值默认为 0, 我们这里设置为 1, 在 SimpleLoggerAdvisor 后面执行;
配置自定义 Advisor
自定义 Advisor 创建完成后,编辑 ChatClientConfig 类,配置上咱们自定义的 MyLoggerAdvisor 日志记录组件:
java
@Configuration
public class ChatClientConfig {
/**
* 初始化 ChatClient 客户端
* @param chatModel
* @return
*/
@Bean
public ChatClient chatClient(DeepSeekChatModel chatModel) {
return ChatClient.builder(chatModel)
.defaultSystem("请你扮演一个智能客服")
.defaultAdvisors(new SimpleLoggerAdvisor(), // 添加 Spring 内置的日志记录功能
new MyLoggerAdvisor()) // 添加自定义的日志打印 Advisor
.build();
}
}
重启项目。调用接口测试。