Spring AI Advisors:从链式增强到递归顾问
一、Advisors API 在做什么
Advisor 所对应的 Advisors API ,提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中 AI 驱动的交互。
当使用用户文本调用 AI 模型时,常见的模式是用上下文数据附加或增强提示。此上下文数据可以是不同类型,常见类型包括:
- 你自己的数据:这是 AI 模型尚未训练的数据。即使模型看到过类似数据,附加的上下文数据在生成响应时也具有优先权。
- 对话历史记录 :聊天模型的 API 是无状态的。若你在某次交互中告诉模型你的名字,它在后续交互中不会自动记住;每次请求都需要发送对话历史,才能在生成响应时考虑先前的交互。
借助 Advisors API,开发者可以封装重复的生成式 AI 模式 、转换 发往大型语言模型(LLM)及从其接收的数据,并在不同模型与用例之间提供可移植性。
二、在 ChatClient 里配置 Advisor:AdvisorSpec
ChatClient 的流畅 API 提供 AdvisorSpec 接口,用于配置 Advisor。该接口支持:添加参数、一次设置多个参数、向链中加入一个或多个 Advisor。
java
interface AdvisorSpec {
AdvisorSpec param(String k, Object v);
AdvisorSpec params(Map<String, Object> p);
AdvisorSpec advisors(Advisor... advisors);
AdvisorSpec advisors(List<Advisor> advisors);
}
重要约定:Advisor 在链中的添加顺序会影响执行逻辑;每个 Advisor 都会以某种方式修改提示或上下文,前一个 Advisor 的变更会传递给链中的下一个。
2.1 运行时注册示例(记忆 + RAG)
java
ChatClient.builder(chatModel)
.build()
.prompt()
.advisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.user(userText)
.call()
.content();
在此配置中,MessageChatMemoryAdvisor 会先执行 ,将对话历史加入提示;随后 QuestionAnswerAdvisor 会基于用户问题以及已加入的对话历史执行检索,从而可能得到更相关的结果。
2.2 关于执行顺序的补充说明(材料中的两种视角)
-
视角 A(
advisors()注册与 order).advisors(A, B, C)传入的顺序即注册顺序。- 最终执行顺序 :先按
order升序(小 → 大)排序。 - 若两个 Advisor 的
order相同 ,则按你传入的顺序执行。 - 一句话 :order 优先;仅当 order 相同时,才看传入顺序。
-
内置 Advisor 的默认 order
- Spring AI 官方内置 Advisor 的默认
order全部为0。 - 因此
MessageChatMemoryAdvisor与QuestionAnswerAdvisor默认均为order = 0,执行顺序为:先MessageChatMemoryAdvisor,后QuestionAnswerAdvisor(与传入顺序一致)。
- Spring AI 官方内置 Advisor 的默认
2.3 构建期默认注册与运行时参数
建议在构建时使用构建器的 defaultAdvisors() 注册 Advisor;运行时可通过 advisors(advisor -> advisor.param(...)) 传入参数。
java
ChatMemory chatMemory = ... // 初始化聊天记忆
VectorStore vectorStore = ... // 初始化向量存储
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(),
QuestionAnswerAdvisor.builder(vectorStore).build()
)
.build();
var conversationId = "678";
String response = this.chatClient.prompt()
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.user(userText)
.call()
.content();
三、与检索、日志相关的内置能力(节选)
3.1 问答 Advisor 与检索增强生成
QuestionAnswerAdvisor 使用向量存储提供问答能力,实现 Naive RAG(检索增强生成) 模式。更完整的 RAG 流程还可参阅官方Spring AI中的 检索增强生成 相关指南;另有 RetrievalAugmentationAdvisor ,基于 org.springframework.ai.rag 中的构建块与模块化 RAG 架构实现常见 RAG 流。
3.2 日志:SimpleLoggerAdvisor
SimpleLoggerAdvisor 会记录 ChatClient 的 request 与 response 数据,便于调试与监控 AI 交互。
在创建 ChatClient 或单次 prompt 时将其加入 Advisor 链;建议放在链的末尾。
java
ChatResponse response = ChatClient.create(chatModel).prompt()
.advisors(new SimpleLoggerAdvisor())
.user("Tell me a joke?")
.call()
.chatResponse();
要看到日志,需将 Advisor 包的日志级别设为 DEBUG (写入 application.properties 或 application.yaml):
properties
logging.level.org.springframework.ai.chat.client.advisor=DEBUG
可通过构造函数自定义从 AdvisedRequest 与 ChatResponse 中记录的内容,并指定 order:
java
SimpleLoggerAdvisor(
Function<ChatClientRequest, String> requestToString,
Function<ChatResponse, String> responseToString,
int order
)
示例:
java
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
request -> "Custom request: " + request.prompt().getUserMessage(),
response -> "Custom response: " + response.getResult(),
0
);
注意:生产环境需谨慎记录敏感信息。
四、BaseAdvisor:同步与流式的统一模板
BaseAdvisor 是 Spring AI 中统一同步 / 流式 调用的模板化增强接口,用于减少重复代码,使自定义增强逻辑(如提示词改写、日志、安全校验)同时适配 call() 与 stream()。
4.1 设计要点
- 顶层关系 :
BaseAdvisor继承CallAdvisor与StreamAdvisor(或旧版CallAroundAdvisor/StreamAroundAdvisor),作为双模式增强的统一入口。 - 模板方法 :通过
default方法封装通用流程(before → 调用链 → after ),开发者主要实现before/after,无需分别手写同步与流式的样板代码。 - 职责 :拦截并增强请求/响应;请求阶段可做改写、注入上下文等;响应阶段可做解析、过滤、日志等;并支持链式执行与
getOrder()顺序控制。
4.2 核心方法(简化理解)
java
public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {
@Override
default ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
ChatClientRequest processed = before(request, chain);
ChatClientResponse response = chain.nextCall(processed);
return after(response, chain);
}
@Override
default Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
return Mono.just(request)
.map(req -> before(req, chain))
.flatMapMany(chain::nextStream)
.map(res -> after(res, chain));
}
ChatClientRequest before(ChatClientRequest request, AdvisorChainContext context);
ChatClientResponse after(ChatClientResponse response, AdvisorChainContext context);
int getOrder(); // 值越小优先级越高,默认 0
}
before:请求发往模型前执行。after:模型返回响应后执行。getOrder:多个 Advisor 时的执行顺序,值越小越先执行。
五、核心类型、链路与整体数据流
5.1 核心组件
- 非流式 :
CallAdvisor、CallAdvisorChain。 - 流式 :
StreamAdvisor、StreamAdvisorChain。 - 请求 / 响应 :
ChatClientRequest(未封装的 Prompt 请求)、ChatClientResponse(聊天完成响应)。 - 二者均包含 advise-context ,用于在 Advisor 链中共享状态。
adviseCall() 与 adviseStream() 是关键方法,典型工作包括:检查 Prompt 数据、自定义与增强 Prompt、调用链中下一节点、可选地阻止请求、检查聊天完成响应、在错误时抛异常等。
此外,getOrder() 决定链中顺序,getName() 提供唯一名称。
5.2 框架中的请求---响应路径
- 框架根据用户 Prompt 与一个空的 Advisor context 创建
ChatClientRequest。 - 链上每个 Advisor 处理请求,可能修改 ;也可不调用下一节点从而阻止请求(此时由该 Advisor 负责填充响应)。
- 框架提供的最后一个 Advisor 将请求发往 Chat Model。
- 模型响应沿链回传 ,并转换为
ChatClientResponse,其中包含共享的 Advisor context。 - 每个 Advisor 仍可处理或修改响应。
- 最终提取
ChatCompletion,将ChatClientResponse返回给客户端。
5.3 getOrder() 与「堆栈」语义
- order 值较小的 Advisor 会先执行。
- Advisor 链被描述为以堆栈 方式工作:
- 链中第一个 Advisor 是第一个处理请求的,也是最后一个处理响应的。
- 若希望某 Advisor 在输入与输出两端 都处在链的「首位」类语义,材料建议:为输入端与输出端分别使用不同 Advisor ,用不同 order 配置,并通过 Advisor 上下文共享状态。
与 Spring Ordered 接口一致:
Ordered.HIGHEST_PRECEDENCE:更小数值,更高优先级。Ordered.LOWEST_PRECEDENCE:更大数值,更低优先级。- 较高 order 值表示较低优先级。
java
public interface Ordered {
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
int getOrder();
}
材料中强调:优先级最高(order 值最低)的 Advisor 相当于在堆栈顶部 ;展开时最先处理请求,回卷时最后处理响应------这与「谁先谁后」的直观列表顺序容易混淆,需结合 order 与堆栈式回卷一起理解。
六、主要接口位置与如何实现 Advisor
主要接口位于包 org.springframework.ai.chat.client.advisor.api。
java
public interface Advisor extends Ordered {
String getName();
}
同步与响应式子接口:
java
public interface CallAdvisor extends Advisor {
ChatClientResponse adviseCall(
ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}
public interface StreamAdvisor extends Advisor {
Flux<ChatClientResponse> adviseStream(
ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}
要继续 Advice 链,在实现中调用:
java
public interface CallAdvisorChain extends AdvisorChain {
ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
List<CallAdvisor> getCallAdvisors();
}
public interface StreamAdvisorChain extends AdvisorChain {
Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
List<StreamAdvisor> getStreamAdvisors();
}
实现时:非流式实现 adviseCall ,流式实现 adviseStream(或同时实现两者)。
七、实践示例:日志 Advisor 与 Re2 Advisor
7.1 简单日志 Advisor(观察而不修改)
下列实现在调用下一 Advisor 之前 记录 ChatClientRequest,在之后 记录 ChatClientResponse,不修改请求与响应;同时覆盖非流式与流式。
java
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
@Override
public String getName() {
return this.getClass().getSimpleName();
}
@Override
public int getOrder() {
return 0;
}
@Override
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
logRequest(chatClientRequest);
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
logResponse(chatClientResponse);
return chatClientResponse;
}
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest,
StreamAdvisorChain streamAdvisorChain) {
logRequest(chatClientRequest);
Flux<ChatClientResponse> chatClientResponses = streamAdvisorChain.nextStream(chatClientRequest);
return new ChatClientMessageAggregator().aggregateChatClientResponse(chatClientResponses, this::logResponse);
}
private void logRequest(ChatClientRequest request) {
logger.debug("request: {}", request);
}
private void logResponse(ChatClientResponse chatClientResponse) {
logger.debug("response: {}", chatClientResponse);
}
}
说明摘要:
getName():提供唯一名称。getOrder():控制顺序,值越低越早。MessageAggregator:将Flux响应聚合为单个ChatClientResponse,便于整体记录或观察;材料注明其为只读,不能在聚合器中改响应。
7.2 重读(Re2)Advisor
《重读提高大型语言模型的推理能力》一文中的 Re2 技术,需要将用户查询增强为类似形式:
text
{Input_Query}
Read the question again: {Input_Query}
基于 BaseAdvisor 的示例实现要点:
- 在
before中用模板把用户消息改写为上述结构。 after中可直接返回原响应。- 通过
withOrder(int order)或字段设置getOrder()。
(完整代码见给定材料中的 ReReadingAdvisor 类。)
八、Spring AI 内置 Advisor 概览
聊天记忆类
MessageChatMemoryAdvisor:从记忆检索并作为消息集合加入 Prompt;维护对话结构;并非所有模型都支持。PromptChatMemoryAdvisor:检索记忆并合并到 系统文本。VectorStoreChatMemoryAdvisor:从 VectorStore 检索记忆并写入系统文本,适合大规模检索。
问答 / RAG
QuestionAnswerAdvisor:基于向量存储的 Naive RAG。RetrievalAugmentationAdvisor:基于org.springframework.ai.rag与模块化 RAG 架构的常见 RAG 流。
推理
ReReadingAdvisor:实现 RE2 重读策略以加强输入阶段理解(参见原文 arxiv 论文链接)。
内容安全
SafeGuardAdvisor:用于降低有害或不适当内容生成风险。