Advisors API
Spring AI Advisors API 提供了一种灵活且强大的方式,用于拦截、修改和增强您 Spring 应用中的 AI 驱动交互。通过利用 Advisors API,开发者可以创建更复杂、可复用且更易于维护的 AI 组件。
其主要优势包括封装重复出现的生成式 AI 模式,转换发送到大型语言模型 (LLMs) 和从大型语言模型接收的数据,以及在各种模型和用例之间提供可移植性。
您可以使用 ChatClient API 配置现有的建议器,示例如下
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
new QuestionAnswerAdvisor(vectorStore) // RAG advisor
)
.build();
String response = this.chatClient.prompt()
// Set advisor parameters at runtime
.advisors(advisor -> advisor.param("chat_memory_conversation_id", "678")
.param("chat_memory_response_size", 100))
.user(userText)
.call()
.content();
建议在构建时使用构建器的 defaultAdvisors()
方法注册建议器。
建议器也参与到可观测性堆栈中,因此您可以查看与其执行相关的指标和跟踪信息。
核心组件
该 API 包含用于非流式场景的 CallAroundAdvisor
和 CallAroundAdvisorChain
,以及用于流式场景的 StreamAroundAdvisor
和 StreamAroundAdvisorChain
。它还包括用于表示未密封的 Prompt 请求的 AdvisedRequest
,以及用于 Chat Completion 响应的 AdvisedResponse
。两者都持有一个 advise-context
,用于在建议器链中共享状态。

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

-
Spring AI 框架会根据用户的
Prompt
创建一个AdvisedRequest
,同时创建一个空的AdvisorContext
对象。 -
链中的每个建议器都会处理请求,并可能对其进行修改。或者,它也可以选择通过不调用下一个实体来阻止请求。在后一种情况下,建议器负责填充响应。
-
由框架提供的最后一个建议器将请求发送给聊天模型。
-
然后,聊天模型的响应会通过建议器链传递回来,并转换为
AdvisedResponse
。后者包含了共享的AdvisorContext
实例。 -
每个建议器都可以处理或修改响应。
-
最终的
AdvisedResponse
通过提取ChatCompletion
返回给客户端。
建议器顺序
建议器在链中的执行顺序由 getOrder()
方法决定。需要理解的关键点:
-
order 值较低的建议器首先执行。
-
建议器链作为栈运行
-
链中的第一个建议器是第一个处理请求的。
-
它也是最后一个处理响应的。
-
-
要控制执行顺序
-
将 order 值设置为接近
Ordered.HIGHEST_PRECEDENCE
,以确保建议器在链中首先执行(先处理请求,后处理响应)。 -
将 order 值设置为接近
Ordered.LOWEST_PRECEDENCE
,以确保建议器在链中最后执行(后处理请求,先处理响应)。
-
-
值越高表示优先级越低。
-
如果多个建议器具有相同的 order 值,其执行顺序不保证。
order 值与执行顺序看似矛盾,原因在于建议器链的栈状特性
|
提醒一下,以下是 Spring Ordered
接口的语义
public interface Ordered {
/**
* Constant for the highest precedence value.
* @see java.lang.Integer#MIN_VALUE
*/
int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
/**
* Constant for the lowest precedence value.
* @see java.lang.Integer#MAX_VALUE
*/
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
/**
* Get the order value of this object.
* <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 概览
主要的建议器接口位于 org.springframework.ai.chat.client.advisor.api
包中。以下是您创建自己的建议器时会遇到的关键接口
public interface Advisor extends Ordered {
String getName();
}
同步和响应式建议器的两个子接口是
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);
}
要在您的 Advice 实现中继续 Advice 链,请使用 CallAroundAdvisorChain
和 StreamAroundAdvisorChain
接口是
public interface CallAroundAdvisorChain {
AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
}
和
public interface StreamAroundAdvisorChain {
Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);
}
实现一个建议器
要创建一个建议器,请实现 CallAroundAdvisor
或 StreamAroundAdvisor
(或两者)。需要实现的关键方法是用于非流式的 nextAroundCall()
或用于流式的 nextAroundStream()
。
示例
我们将提供一些动手示例,以说明如何实现用于观察和增强用例的建议器。
日志建议器
我们可以实现一个简单的日志建议器,它在调用链中的下一个建议器之前记录 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 | 提供建议器的唯一名称。 |
2 | 您可以通过设置 order 值来控制执行顺序。值较低的先执行。 |
3 | MessageAggregator 是一个工具类,它将 Flux 响应聚合到一个 AdvisedResponse 中。这对于记录日志或观察整个响应而非流中单个项的其他处理非常有用。请注意,您不能在 MessageAggregator 中更改响应,因为它是一个只读操作。 |
重复阅读 (Re2) 建议器
《重复阅读提高了大型语言模型的推理能力》这篇文章介绍了一种名为重复阅读 (Re2) 的技术,可以提高大型语言模型的推理能力。Re2 技术需要像这样增强输入提示词:
{Input_Query} Read the question again: {Input_Query}
实现一个将 Re2 技术应用于用户输入查询的建议器可以像这样完成:
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 | 提供建议器的唯一名称。 |
Spring AI 内置建议器
Spring AI 框架提供了几个内置的建议器,以增强您的 AI 交互。以下是可用建议器的概览:
流式 vs 非流式

-
非流式建议器处理完整的请求和响应。
-
流式建议器使用响应式编程概念(例如,用于响应的 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 建议器链从版本 1.0 M2 到 1.0 M3 发生了重大变化。以下是主要修改内容:
建议器接口
-
在 1.0 M2 中,存在独立的
RequestAdvisor
和ResponseAdvisor
接口。-
RequestAdvisor
在ChatModel.call
和ChatModel.stream
方法之前被调用。 -
ResponseAdvisor
在这些方法之后被调用。
-
-
在 1.0 M3 中,这些接口已被以下接口取代:
-
CallAroundAdvisor
-
StreamAroundAdvisor
-
-
StreamResponseMode
,之前是ResponseAdvisor
的一部分,已被移除。
上下文 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...
}