Spring AI Advisors API
概述
Spring AI Advisors API提供了一种灵活而强大的方式来拦截、修改和增强Spring应用程序中AI驱动的交互。通过利用Advisors API,开发者可以创建更复杂、可重用和可维护的AI组件。
关键优势包括封装重复的生成AI模式,转换发送给大语言模型(LLMs)和从大语言模型接收的数据,以及跨各种模型和用例提供可移植性。
您可以使用ChatClient API配置现有的advisors,如以下示例所示:
java
ChatMemory chatMemory = ... // 初始化您的聊天记忆存储
VectorStore vectorStore = ... // 初始化您的向量存储
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // 聊天记忆advisor
QuestionAnswerAdvisor.builder(vectorStore).build() // 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由用于非流式场景的CallAdvisor和CallAdvisorChain,以及用于流式场景的StreamAdvisor和StreamAdvisorChain组成。它还包括ChatClientRequest来表示未密封的Prompt请求,ChatClientResponse用于聊天完成响应。两者都持有一个advise-context以在advisor链中共享状态。

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

-
Spring AI框架从用户的
Prompt创建一个ChatClientRequest,以及一个空的advisorcontext对象。 -
链中的每个advisor处理请求,可能修改它。或者,它可以通过不调用调用下一个实体来选择阻止请求。在后一种情况下,advisor负责填写响应。
-
框架提供的最终advisor将请求发送给
Chat Model。 -
聊天模型的响应然后通过advisor链传回并转换为
ChatClientResponse。后者包括共享的advisorcontext实例。 -
每个advisor可以处理或修改响应。
-
最终的
ChatClientResponse通过提取ChatCompletion返回给客户端。
Advisor顺序
advisor在链中的执行顺序由getOrder()方法确定。需要理解的关键点:
- 具有较低顺序值的advisors首先执行。
- advisor链作为堆栈操作:
- 链中的第一个advisor是第一个处理请求的。
- 它也是最后一个处理响应的。
- 要控制执行顺序:
- 将顺序设置为接近
Ordered.HIGHEST_PRECEDENCE以确保advisor在链中首先执行(请求处理最先,响应处理最后)。 - 将顺序设置为接近
Ordered.LOWEST_PRECEDENCE以确保advisor在链中最后执行(请求处理最后,响应处理最先)。
- 将顺序设置为接近
- 较高的值被解释为较低的优先级。
- 如果多个advisors具有相同的顺序值,它们的执行顺序不保证。
注意: 顺序和执行序列之间的明显矛盾是由于advisor链的堆栈特性:
- 具有最高优先级(最低顺序值)的advisor被添加到堆栈的顶部。
- 在堆栈展开时,它将是第一个处理请求的。
- 在堆栈回绕时,它将是最后一个处理响应的。
提醒一下,以下是Spring Ordered接口的语义:
java
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();
}
提示: 对于需要在输入和输出两侧都在链中首位的用例:
- 为每一侧使用单独的advisors。
- 使用不同的顺序值配置它们。
- 使用advisor上下文在它们之间共享状态。
API概述
主要的Advisor接口位于包org.springframework.ai.chat.client.advisor.api中。以下是创建自己的advisor时会遇到的关键接口:
java
public interface Advisor extends Ordered {
String getName();
}
同步和响应式Advisors的两个子接口是:
java
public interface CallAdvisor extends Advisor {
ChatClientResponse adviseCall(
ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}
和
java
public interface StreamAdvisor extends Advisor {
Flux<ChatClientResponse> adviseStream(
ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}
要继续Advice链,在您的Advice实现中使用CallAdvisorChain和StreamAdvisorChain:
接口是:
java
public interface CallAdvisorChain extends AdvisorChain {
/**
* 使用给定的请求调用链中的下一个{@link CallAdvisor}。
*/
ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
/**
* 返回在创建时包含在此链中的所有{@link CallAdvisor}实例的列表。
*/
List<CallAdvisor> getCallAdvisors();
}
和
java
public interface StreamAdvisorChain extends AdvisorChain {
/**
* 使用给定的请求调用链中的下一个{@link StreamAdvisor}。
*/
Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
/**
* 返回在创建时包含在此链中的所有{@link StreamAdvisor}实例的列表。
*/
List<StreamAdvisor> getStreamAdvisors();
}
实现Advisor
要创建advisor,实现CallAdvisor或StreamAdvisor(或两者)。要实现的关键方法是用于非流式的nextCall()或用于流式advisors的nextStream()。
示例
我们将提供一些动手示例来说明如何实现用于观察和增强用例的advisors。
日志记录Advisor
我们可以实现一个简单的日志记录advisor,在调用链中的下一个advisor之前记录ChatClientRequest,之后记录ChatClientResponse。注意advisor只观察请求和响应,不修改它们。此实现支持非流式和流式场景。
java
public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {
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 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); // 3
}
private void logRequest(ChatClientRequest request) {
logger.debug("request: {}", request);
}
private void logResponse(ChatClientResponse chatClientResponse) {
logger.debug("response: {}", chatClientResponse);
}
}
- 为advisor提供唯一名称。
- 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。
MessageAggregator是一个实用程序类,将Flux响应聚合到单个ChatClientResponse中。这对于观察整个响应而不是流中的单个项目的日志记录或其他处理很有用。请注意,您不能在MessageAggregator中更改响应,因为它是只读操作。
重读(Re2)Advisor
"重新阅读改进大语言模型中的推理"文章介绍了一种称为重新阅读(Re2)的技术,改进了大语言模型的推理能力。Re2技术需要像这样增强输入提示:
css
{Input_Query}
Read the question again: {Input_Query}
实现将Re2技术应用于用户输入查询的advisor可以这样做:
java
public class ReReadingAdvisor implements BaseAdvisor {
private static final String DEFAULT_RE2_ADVISE_TEMPLATE = """
{re2_input_query}
Read the question again: {re2_input_query}
""";
private final String re2AdviseTemplate;
private int order = 0;
public ReReadingAdvisor() {
this(DEFAULT_RE2_ADVISE_TEMPLATE);
}
public ReReadingAdvisor(String re2AdviseTemplate) {
this.re2AdviseTemplate = re2AdviseTemplate;
}
@Override
public ChatClientRequest before(ChatClientRequest chatClientRequest, AdvisorChain advisorChain) { // 1
String augmentedUserText = PromptTemplate.builder()
.template(this.re2AdviseTemplate)
.variables(Map.of("re2_input_query", chatClientRequest.prompt().getUserMessage().getText()))
.build()
.render();
return chatClientRequest.mutate()
.prompt(chatClientRequest.prompt().augmentUserMessage(augmentedUserText))
.build();
}
@Override
public ChatClientResponse after(ChatClientResponse chatClientResponse, AdvisorChain advisorChain) {
return chatClientResponse;
}
@Override
public int getOrder() { // 2
return this.order;
}
public ReReadingAdvisor withOrder(int order) {
this.order = order;
return this;
}
}
before方法应用重新阅读技术增强用户的输入查询。- 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。
Spring AI内置Advisors
Spring AI框架提供几个内置的advisors来增强您的AI交互。以下是可用advisors的概述:
聊天记忆Advisors
这些advisors在聊天记忆存储中管理对话历史:
-
MessageChatMemoryAdvisor检索记忆并将其作为消息集合添加到提示中。这种方法保持对话历史的结构。注意,并非所有AI模型都支持这种方法。
-
PromptChatMemoryAdvisor检索记忆并将其合并到提示的系统文本中。
-
VectorStoreChatMemoryAdvisor从VectorStore检索记忆并将其添加到提示的系统文本中。此advisor有助于高效搜索和检索大型数据集中的相关信息。
问答Advisor
-
QuestionAnswerAdvisor此advisor使用向量存储提供问答能力,实现朴素RAG(检索增强生成)模式。
-
RetrievalAugmentationAdvisorAdvisor使用
org.springframework.ai.rag包中定义的构建块实现常见的检索增强生成(RAG)流程,遵循模块化RAG架构。
推理Advisor
-
ReReadingAdvisor为LLM推理实现重新阅读策略,称为RE2,以增强输入阶段的理解。基于文章:重新阅读改进LLMs中的推理。
内容安全Advisor
-
SafeGuardAdvisor一个简单的advisor,旨在防止模型生成有害或不当内容。
流式与非流式

- 非流式advisors处理完整的请求和响应。
- 流式advisors将请求和响应作为连续流处理,使用响应式编程概念(例如,响应使用Flux)。
java
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain chain) {
return Mono.just(chatClientRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// 这可以由阻塞和非阻塞线程执行。
// Advisor在next部分之前
})
.flatMapMany(request -> chain.nextStream(request))
.map(response -> {
// Advisor在next部分之后
});
}
最佳实践
- 保持advisors专注于特定任务以获得更好的模块化。
- 必要时使用
adviseContext在advisors之间共享状态。 - 为最大灵活性实现advisor的流式和非流式版本。
- 仔细考虑链中advisors的顺序以确保正确的数据流。
破坏性API更改
Advisor接口
- 在1.0 M2中,有单独的
RequestAdvisor和ResponseAdvisor接口。RequestAdvisor在ChatModel.call和ChatModel.stream方法之前调用。ResponseAdvisor在这些方法之后调用。
- 在1.0 M3中,这些接口已被替换为:
CallAroundAdvisorStreamAroundAdvisor
- 以前是
ResponseAdvisor一部分的StreamResponseMode已被删除。 - 在1.0.0中,这些接口已被替换:
CallAroundAdvisor→CallAdvisor,StreamAroundAdvisor→StreamAdvisor,CallAroundAdvisorChain→CallAdvisorChain和StreamAroundAdvisorChain→StreamAdvisorChain。AdvisedRequest→ChatClientRequest和AdvisedResponse→ChatClientResponse。
上下文映射处理
- 在1.0 M2中:
- 上下文映射是单独的方法参数。
- 映射是可变的并沿链传递。
- 在1.0 M3中:
- 上下文映射现在是
AdvisedRequest和AdvisedResponse记录的一部分。 - 映射是不可变的。
- 要更新上下文,使用
updateContext方法,该方法创建一个具有更新内容的新不可修改映射。
- 上下文映射现在是