Advisor API
本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。 |
Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该 API,开发者能构建更复杂、可复用且易维护的 AI 组件。
核心优势包括:封装可复用的生成式 AI 模式、转换与大语言模型(LLM)交互的数据、实现跨模型与用例的可移植性。
如以下示例所示,可通过 ChatClient API 配置现有 Advisor:
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
QuestionAnswerAdvisor.builder((vectorStore).builder() // RAG advisor
)
)
.build();
var conversationId = "678";
String response = this.chatClient.prompt()
// 运行时设置 advisor 参数
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.user(userText)
.call()
.content();
建议在构建时使用构建器(builder
)的 defaultAdvisors()
方法注册顾问。
Advisor 组件同时接入可观测性体系,支持查看其执行相关的指标与追踪数据。
核心组件
API 包括用于非流式场景的 CallAroundAdvisor
和 CallAroundAdvisorChain
,以及用于流式场景的 StreamAroundAdvisor
和 StreamAroundAdvisorChain
。它还包括代表未密封(unsealed)提示请求的 AdvisedRequest
和代表聊天完成响应的 AdvisedResponse
。两者都持有一个 advise-context
,以便在 Advisor 链中共享状态。

核心 Advisor 方法 nextAroundCall()
与 nextAroundStream()
主要执行以下操作:检查未密封的 Prompt 数据、定制与增强 Prompt 内容、调用 Advisor 链中的下一实体、选择性阻断请求、分析聊天完成响应,并通过抛出异常标识处理错误。
此外,getOrder()
方法决定 Advisor 在链中的执行顺序,而 getName()
则提供唯一的 Advisor 标识名称。
Spring AI 框架构建的 Advisor Chain 支持按 getOrder()
值排序依次调用多个 Advisor — 数值越小优先级越高。链末自动加入的最终顾问会将请求发送至大语言模型(LLM)。
以下流程图展示了 Advisor 链与聊天模型的交互过程:

-
Spring AI 框架将用户的 Prompt 封装为
AdvisedRequest
对象,并创建空的AdvisorContext
上下文。 -
链中每个 Advisor 依次处理请求并可进行修改,也可选择阻断请求(不调用下一实体)。若选择阻断,该 Advisor 需负责填充响应内容。
-
框架提供的最终 Advisor 将请求发送至聊天模型。
-
聊天模型的响应会逆向传回 Advisor 链,被转换为包含共享
AdvisorContext
实例的AdvisedResponse
对象。 -
每个 Advisor 均可处理或修改该响应。
-
通过提取
ChatCompletion
内容,最终生成的AdvisedResponse
将返回给客户端。
Advisor 顺序
链中 Advisor 的执行顺序由 getOrder()
方法决定。需要了解的要点:
-
数值越小的 Advisor 越优先执行。
-
Advisor 链采用栈式结构运作:
-
链首 Advisor 最先处理请求。
-
同时也是最后处理响应的环节。
-
-
控制执行顺序:
-
将
order
值设为接近Ordered.HIGHEST_PRECEDENCE
可确保 Advisor 优先执行(请求处理时最先触发,响应处理时最后触发)。 -
将
order
值设为接近Ordered.LOWEST_PRECEDENCE
可确保 Advisor 最后执行(请求处理时最后触发,响应处理时最先触发)。
-
-
数值越大表示优先级越低。
-
若多个 Advisor 的
order
值相同,其执行顺序无法保证。
执行顺序与数值看似矛盾的现象源于 Advisor 链的栈式特性:
|
以下是 Spring Ordered
接口的语义说明:
public interface Ordered {
/**
* 最高优先级的常量值。
* @see java.lang.Integer#MIN_VALUE
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* 最低优先级的常量值。
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
/**
* 获取此对象的顺序值。
* <p>Higher values are interpreted as lower priority. As a consequence,
* the object with the lowest value has the highest priority (somewhat
* analogous to Servlet {@code load-on-startup} values).
* <p>Same order values will result in arbitrary sort positions for the
* affected objects.
* @return the order value
* @see #HIGHEST_PRECEDENCE
* @see #LOWEST_PRECEDENCE
*/
int getOrder();
}
对于需要在链中输入输出两端都优先处理的使用场景:
|
API 概览
核心 Advisor 接口位于 org.springframework.ai.chat.client.advisor.api
包中。开发自定义 Advisor 时需关注以下关键接口:
public interface Advisor extends Ordered {
String getName();
}
同步式与响应式 Advisor 的两个子接口分别为:
public interface CallAroundAdvisor extends Advisor {
/**
* Around advice that wraps the ChatModel#call(Prompt) method.
* @param advisedRequest the advised request
* @param chain the advisor chain
* @return the response
*/
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}
以及:
public interface StreamAroundAdvisor extends Advisor {
/**
* Around advice that wraps the invocation of the advised request.
* @param advisedRequest the advised request
* @param chain the chain of advisors to execute
* @return the result of the advised request
*/
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
要在 Advisor 实现中延续 Advisor 链,需使用 CallAroundAdvisorChain
与 StreamAroundAdvisorChain
:
接口是:
public interface CallAroundAdvisorChain {
AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
}
以及:
public interface StreamAroundAdvisorChain {
Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
}
实现 Advisor
创建 Advisor 需实现 CallAroundAdvisor
或 StreamAroundAdvisor
(或两者)。核心实现方法是:非流式用 nextAroundCall()
,流式用 nextAroundStream()
。
示例
通过具体示例演示如何实现观察型与增强型 Advisor。
日志 Advisor
我们可以实现一个简单的日志 Advisor,在调用链中下一 Advisor 前后,分别记录 AdvisedRequest
和 AdvisedResponse
。该实现仅观察请求与响应而不修改,同时支持非流式与流式场景。
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);
@Override
public String getName() { (1)
return this.getClass().getSimpleName();
}
@Override
public int getOrder() { (2)
return 0;
}
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
logger.debug("BEFORE: {}", advisedRequest);
AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
logger.debug("AFTER: {}", advisedResponse);
return advisedResponse;
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
logger.debug("BEFORE: {}", advisedRequest);
Flux<AdvisedResponse> advisedResponses = chain.nextAroundStream(advisedRequest);
return new MessageAggregator().aggregateAdvisedResponse(advisedResponses,
advisedResponse -> logger.debug("AFTER: {}", advisedResponse)); (3)
}
}
1 | 为 Advisor 提供一个唯一的名称。 |
2 | 你可以通过设置 order 值来控制执行顺序。较低的值优先执行。 |
3 | MessageAggregator 是工具类,用于将 Flux 响应聚合为单个 AdvisedResponse 。适用于需观察完整响应(而非流中单项)的日志记录等处理场景。注意:此为只读操作,不可修改响应内容。 |
重读(Re2)Advisor
论文 重读可提高大型语言模型的推理能力 提出的 Re-Reading(Re2)技术,通过以下方式增强输入提示词来优化模型推理表现:
{Input_Query} Read the question again: {Input_Query}
实现应用 Re2 技术的 Advisor 可按如下方式处理用户输入查询:
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
private AdvisedRequest before(AdvisedRequest advisedRequest) { (1)
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) { (2)
return chain.nextAroundCall(this.before(advisedRequest));
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) { (3)
return chain.nextAroundStream(this.before(advisedRequest));
}
@Override
public int getOrder() { (4)
return 0;
}
@Override
public String getName() { (5)
return this.getClass().getSimpleName();
}
}
1 | before 方法通过应用重读技术增强用户输入查询。 |
2 | aroundCall 方法拦截非流式请求并应用重读技术。 |
3 | aroundStream 方法拦截流式请求并应用重读技术。 |
4 | 通过设置 order 值控制执行顺序 — 数值越小优先级越高。 |
5 | 为 Advisor 提供唯一标识名称。 |
Spring AI 内置 Advisor
Spring AI 框架提供了多个内置 Advisor,以增强你的 AI 交互。以下是可用 Advisor 的概述:
流式与非流式对比

-
非流式 Advisor 处理完整请求与响应。
-
流式 Advisor 采用响应式编程理念(如
Flux
处理响应),以连续流形式处理请求与响应。
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return Mono.just(advisedRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// This can be executed by blocking and non-blocking Threads.
// Advisor before next section
})
.flatMapMany(request -> chain.nextAroundStream(request))
.map(response -> {
// Advisor after next section
});
}
重大 API 变更
Spring AI Advisor 链从 1.0 M2 到 1.0 M3 版本发生重大变更,主要调整如下:
Advisor 接口
-
1.0 M2 版本中曾存在独立的
RequestAdvisor
与ResponseAdvisor
接口。-
RequestAdvisor
在ChatModel.call
与ChatModel.stream
方法前触发。 -
ResponseAdvisor
则在上述方法执行后调用。
-
-
1.0 M3 版本中这些接口已被替换为:
-
CallAroundAdvisor
-
StreamAroundAdvisor
-
-
原属
ResponseAdvisor
的StreamResponseMode
已被移除。
上下文 Map 处理
-
在 1.0 M2 中:
-
上下文 Map 曾作为独立的方法参数存在。
-
该 Map 为可变对象并在链中传递。
-
-
在 1.0 M3 中:
-
上下文 Map 现已整合至
AdvisedRequest
与AdvisedResponse
记录中。 -
Map 为不可变对象。
-
需通过
updateContext
方法更新上下文 - 该方法会生成包含更新内容的新不可变 Map。
-
1.0 M3 中更新上下文的示例:
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
this.advisedRequest = advisedRequest.updateContext(context -> {
context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName()); // Add multiple key-value pairs
context.put("lastBefore", getName()); // Add a single key-value pair
return context;
});
// Method implementation continues...
}