Advisors API
Spring AI Advisors API 提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中的 AI 驱动交互。 通过利用 Advisors API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。
主要优势包括封装重复的生成式 AI 模式、转换发送到大型语言模型(LLMs)的数据以及提供跨各种模型和用例的可移植性。
您可以使用 ChatClient API 配置现有的 advisors,如下例所示:
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()
方法在构建时注册 advisors。
Advisors 也参与可观察性堆栈,因此您可以查看与其执行相关的指标和跟踪。
核心组件
API 包含用于非流式场景的 CallAroundAdvisor
和 CallAroundAdvisorChain
,以及用于流式场景的 StreamAroundAdvisor
和 StreamAroundAdvisorChain
。
它还包括 AdvisedRequest
用于表示未密封的 Prompt 请求,AdvisedResponse
用于 Chat Completion 响应。两者都包含一个 advise-context
用于在 advisor 链中共享状态。

nextAroundCall()
和 nextAroundStream()
是关键 advisor 方法,通常执行以下操作:检查未密封的 Prompt 数据、自定义和增强 Prompt 数据、调用 advisor 链中的下一个实体、可选地阻止请求、检查聊天完成响应,以及抛出异常以指示处理错误。
此外,getOrder()
方法确定 advisor 在链中的顺序,而 getName()
提供唯一的 advisor 名称。
由 Spring AI 框架创建的 Advisor Chain 允许按 getOrder()
值排序的多个 advisors 顺序调用。
较低的值首先执行。
最后一个 advisor(自动添加)将请求发送到 LLM。
以下流程图说明了 advisor 链与 Chat Model 之间的交互:

-
Spring AI 框架从用户的
Prompt
创建一个AdvisedRequest
,同时创建一个空的AdvisorContext
对象。 -
链中的每个 advisor 处理请求,可能会修改它。或者,它可以选择通过不调用下一个实体来阻止请求。在后一种情况下,advisor 负责填写响应。
-
框架提供的最终 advisor 将请求发送到
Chat Model
。 -
Chat Model 的响应然后通过 advisor 链传回并转换为
AdvisedResponse
。后者包含共享的AdvisorContext
实例。 -
每个 advisor 可以处理或修改响应。
-
通过提取
ChatCompletion
将最终的AdvisedResponse
返回给客户端。
Advisor 顺序
链中 advisors 的执行顺序由 getOrder()
方法确定。需要理解的关键点:
-
具有较低顺序值的 advisors 首先执行。
-
advisor 链作为堆栈运行:
-
链中的第一个 advisor 是第一个处理请求的。
-
它也是最后一个处理响应的。
-
-
要控制执行顺序:
-
将顺序设置为接近
Ordered.HIGHEST_PRECEDENCE
以确保 advisor 在链中首先执行(请求处理时首先,响应处理时最后)。 -
将顺序设置为接近
Ordered.LOWEST_PRECEDENCE
以确保 advisor 在链中最后执行(请求处理时最后,响应处理时首先)。
-
-
较高的值被解释为较低的优先级。
-
如果多个 advisors 具有相同的顺序值,它们的执行顺序不能保证。
顺序和执行序列之间的看似矛盾是由于 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>较高的值被解释为较低的优先级。因此,
* 具有最低值的对象具有最高优先级(某种程度上
* 类似于 Servlet {@code load-on-startup} 值)。
* <p>相同的顺序值将导致受影响对象的任意排序位置。
* @return 顺序值
* @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();
}
同步和响应式 Advisors 的两个子接口是
public interface CallAroundAdvisor extends Advisor {
/**
* 包装 ChatModel#call(Prompt) 方法的环绕通知。
* @param advisedRequest 建议的请求
* @param chain advisor 链
* @return 响应
*/
AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}
和
public interface StreamAroundAdvisor extends Advisor {
/**
* 包装建议请求调用的环绕通知。
* @param advisedRequest 建议的请求
* @param chain 要执行的 advisors 链
* @return 建议请求的结果
*/
Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
要在 Advice 实现中继续 Advice 链,请使用 CallAroundAdvisorChain
和 StreamAroundAdvisorChain
:
接口是
public interface CallAroundAdvisorChain {
AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
}
和
public interface StreamAroundAdvisorChain {
Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
}
实现 Advisor
要创建 advisor,请实现 CallAroundAdvisor
或 StreamAroundAdvisor
(或两者)。要实现的关键方法是用于非流式的 nextAroundCall()
或用于流式的 nextAroundStream()
。
示例
我们将提供几个实践示例来说明如何实现用于观察和增强用例的 advisors。
日志记录 Advisor
我们可以实现一个简单的日志记录 advisor,它在调用链中的下一个 advisor 之前记录 AdvisedRequest
,之后记录 AdvisedResponse
。
注意,advisor 只观察请求和响应,不修改它们。
此实现支持非流式和流式场景。
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 | 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。 |
3 | MessageAggregator 是一个实用类,它将 Flux 响应聚合为单个 AdvisedResponse。
这对于记录或观察整个响应而不是流中单个项目的其他处理很有用。
注意,您不能在 MessageAggregator 中修改响应,因为它是只读操作。 |
重读 (Re2) Advisor
"Re-Reading Improves Reasoning in Large Language Models" 文章介绍了一种称为重读 (Re2) 的技术,该技术提高了大型语言模型的推理能力。 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 | 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。 |
5 | 为 advisor 提供唯一名称。 |
Spring AI 内置 Advisors
Spring AI 框架提供了几个内置的 advisors 来增强您的 AI 交互。以下是可用的 advisors 概述:
流式与非流式

-
非流式 advisors 处理完整的请求和响应。
-
流式 advisors 将请求和响应作为连续流处理,使用响应式编程概念(例如,使用 Flux 处理响应)。
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return Mono.just(advisedRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// 这可以由阻塞和非阻塞线程执行。
// Advisor 在 next 部分之前
})
.flatMapMany(request -> chain.nextAroundStream(request))
.map(response -> {
// Advisor 在 next 部分之后
});
}
API 重大变更
Spring AI Advisor Chain 从版本 1.0 M2 到 1.0 M3 经历了重大变化。以下是主要修改:
Advisor 接口
-
在 1.0 M2 中,有单独的
RequestAdvisor
和ResponseAdvisor
接口。-
RequestAdvisor
在ChatModel.call
和ChatModel.stream
方法之前被调用。 -
ResponseAdvisor
在这些方法之后被调用。
-
-
在 1.0 M3 中,这些接口已被替换为:
-
CallAroundAdvisor
-
StreamAroundAdvisor
-
-
StreamResponseMode
(以前是ResponseAdvisor
的一部分)已被删除。
上下文映射处理
-
在 1.0 M2 中:
-
上下文映射是一个单独的方法参数。
-
映射是可变的,并沿链传递。
-
-
在 1.0 M3 中:
-
上下文映射现在是
AdvisedRequest
和AdvisedResponse
记录的一部分。 -
映射是不可变的。
-
要更新上下文,请使用
updateContext
方法,该方法创建一个包含更新内容的新不可修改映射。
-
在 1.0 M3 中更新上下文的示例:
@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
this.advisedRequest = advisedRequest.updateContext(context -> {
context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName()); // 添加多个键值对
context.put("lastBefore", getName()); // 添加单个键值对
return context;
});
// 方法实现继续...
}