强军对决
106.2MB · 2026-03-22
在构建基于 Spring AI 的智能应用时,我们经常需要在 AI 请求和响应的处理流程中插入一些额外的逻辑,比如记录日志、优化提示词、对结果进行二次处理等。Spring AI 提供了一套优雅的 Advisor(拦截器) 机制,让你可以像切面编程一样,在 AI 调用的前后加入自定义逻辑。
我将以新手友好的方式,带你理解 Advisor 的核心概念,并手把手教你实现两个实用的自定义 Advisor:一个日志记录器和一个能提升推理能力的 Re-Reading Advisor。
简单来说,Advisor 就像是一个 “拦截器” 。当你的程序调用 AI 模型时,请求会依次经过一系列 Advisor,每个 Advisor 都可以:
这种设计让你能够将横切关注点(如日志、监控、提示词优化)与核心业务逻辑分离,代码更加清晰、可维护。
Spring AI 提供了两种 Advisor 接口,分别对应两种调用场景:
| 接口 | 适用场景 | 核心方法 |
|---|---|---|
CallAroundAdvisor | 非流式请求(一次性返回完整结果) | aroundCall |
StreamAroundAdvisor | 流式请求(数据分块返回) | aroundStream |
最佳实践:如果你的 Advisor 同时支持流式和非流式调用,建议同时实现两个接口。
实现一个 Advisor 需要关注以下几点:
CallAroundAdvisor 和/或 StreamAroundAdvisoraroundCall 或 aroundStream 中编写你的逻辑getOrder() 指定优先级(值越小越先执行)getName() 返回一个标识符Spring AI 内置了 SimpleLoggerAdvisor,但它以 Debug 级别输出日志。在默认的 Spring Boot 项目中,Info 级别下看不到日志输出。因此,我们需要一个更精简、可自定义级别的日志记录器。
package com.swl.baoaiagent.advisor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.model.MessageAggregator;
import reactor.core.publisher.Flux;
@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
// 请求前:打印用户提示词
private void before(AdvisedRequest advisedRequest) {
log.info("Ai Request: {}", advisedRequest.userText());
}
// 响应后:打印 AI 回复
private void observeAfter(AdvisedResponse advisedResponse) {
log.info("Ai Response: {}", advisedResponse.response().getResult().getOutput().getText());
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
before(advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
observeAfter(advisedResponse);
return advisedResponse;
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
before(advisedRequest);
Flux<AdvisedResponse> adviseResponses = chain.nextAroundStream(advisedRequest);
// 流式场景需要聚合消息后才能打印完整内容
return new MessageAggregator().aggregateAdvisedResponse(adviseResponses, this::observeAfter);
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public int getOrder() {
return 0; // 优先级最高,最先执行
}
}
MessageAggregator 将分块数据聚合成完整消息后,再打印一次日志。getOrder() 返回 0,表示这个日志拦截器会优先执研究发现,让 AI 把问题再读一遍 可以显著提升推理能力。这种技术被称为 Re-Reading(Re2),虽然效果显著,但 成本会翻倍(因为prompt长度翻倍),所以面向 C 端用户时要谨慎使用。
原始请求:
Re2 改写后的请求:
package com.swl.baoaiagent.advisor;
import org.springframework.ai.chat.client.advisor.api.*;
import reactor.core.publisher.Flux;
import java.util.HashMap;
import java.util.Map;
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
// 在请求前改写 Prompt
private AdvisedRequest before(AdvisedRequest advisedRequest) {
Map<String, Object> advisedUserParams = new HashMap<>(advisedRequest.userParams());
advisedUserParams.put("re2_input_query", advisedRequest.userText());
return AdvisedRequest.from(advisedRequest)
.userText("""
{re2_input_query}
Read the question again: {re2_input_query}
""")
.userParams(advisedUserParams)
.build();
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
return chain.nextAroundCall(advisedRequest);
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
advisedRequest = before(advisedRequest);
return chain.nextAroundStream(advisedRequest);
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public int getOrder() {
return 0;
}
}
before 方法,将原始问题和“再读一遍”的指令拼接成新的提示词。userParams 保留了原始输入,方便后续追踪。当多个 Advisor 同时存在时,getOrder() 决定了它们的执行顺序:
请求 → Order=0 (日志) → Order=1 (Re2) → Order=2 (其他) → AI 模型
有时我们需要在多个 Advisor 之间传递数据,可以通过 adviseContext 实现:
// 在第一个 Advisor 中存入数据
advisedRequest = advisedRequest.updateContext(context -> {
context.put("startTime", System.currentTimeMillis());
return context;
});
// 在后面的 Advisor 中取出数据
Object startTime = advisedResponse.adviseContext().get("startTime");
这在计算耗时、传递用户身份信息等场景中非常有用。
getOrder(),确保依赖关系正确CallAroundAdvisor 和 StreamAroundAdvisorMono/Flux 操作符进行灵活编排通过本文的学习,你应该已经掌握了 Spring AI Advisor 的核心概念和开发方法。自定义 Advisor 让我们能够以非侵入的方式扩展 AI 调用的能力,无论是记录日志、优化提示词,还是实现更复杂的推理增强,都能轻松应对。