跳到主要内容
版本:2.0.1

Advisors API

QWen 3.8 中英对照 Advisors Advisors API

Spring AI Advisors API 提供了一种灵活且强大的方式,用于拦截、修改和增强 Spring 应用程序中的 AI 交互。借助 Advisors API,开发人员可以创建更加复杂、可复用且易于维护的 AI 组件。

主要优势包括:封装重复出现的生成式 AI 模式,转换发送给大型语言模型(LLMs)的数据以及从 LLMs 接收的数据,并在多种模型和使用场景中提供可移植性。

您可以通过 ChatClient API 配置现有的 advisors,示例如下:

ChatMemory chatMemory = ... // Initialize your chat memory store
VectorStore vectorStore = ... // Initialize your vector store

var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
QuestionAnswerAdvisor.builder(vectorStore).build() // RAG advisor
)
.build();

var conversationId = "678";

String response = this.chatClient.prompt()
// Set advisor parameters at runtime
.advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
.user(userText)
.call()
.content();

建议使用构建器的 defaultAdvisors() 方法在构建时注册 advisors。

Advisor 也参与可观测性(Observability)技术栈,因此您可以查看与其执行相关的指标(metrics)和追踪(traces)。

核心组件​

API 由 CallAdvisor 和 CallAdvisorChain(用于非流式场景)以及 StreamAdvisor 和 StreamAdvisorChain(用于流式场景)组成。此外,它还包含 ChatClientRequest,用于表示未封装的 Prompt 请求;以及 ChatClientResponse,用于表示 Chat Completion 响应。两者都持有 advise-context,以便在 advisor 链中共享状态。

Advisor API 类

adviseCall() 和 adviseStream() 是核心的 Advisor 方法,通常执行诸如检查未密封的 Prompt 数据、定制和增强 Prompt 数据、调用 Advisor 链中的下一个实体、可选地阻止请求、检查聊天补全响应以及抛出异常以指示处理错误等操作。

此外,getOrder() 方法用于确定链中顾问(advisor)的顺序,而 getName() 方法则提供唯一的顾问名称。

Advisor Chain(由 Spring AI 框架创建)支持按照各 advisor 的 getOrder() 值对多个 advisor 进行排序,并按顺序依次调用。值较小的 advisor 会先执行。自动添加的最后一个 advisor 负责将请求发送给 LLM。

以下流程图展示了顾问链与 Chat Model 之间的交互:

Advisors API Flow

  1. Spring AI 框架从用户的 Prompt 及一个空的 advisor context 对象创建一个 ChatClientRequest。

  2. 链中的每个 advisor 都会处理请求,并可能对其进行修改。或者,它可以选择阻断请求,即不调用 invoke 来执行下一个实体。在后一种情况下,由该 advisor 负责填充响应。

  3. 由框架提供的最后一个 advisor 会将请求发送给 Chat Model。

  4. Chat Model 的响应随后通过 advisor 链回传,并转换为 ChatClientResponse。其中包含共享的 advisor context 实例。

  5. 每个 advisor 都可以处理或修改响应。

  6. 最终通过提取 ChatCompletion,将 ChatClientResponse 返回给客户端。

Advisor Order​

链中顾问(advisors)的执行顺序由 getOrder() 方法决定。需要理解的关键点:

  • 订单值(order value)较低的顾问(Advisor)会先执行。

  • 顾问链(advisor chain)作为一个栈(stack)运行:

    • 链中的第一个顾问最先处理请求。

    • 它也是最后处理响应的顾问。

  • 若要控制执行顺序:

    • 将 order 设置为接近 Ordered.HIGHEST_PRECEDENCE,以确保顾问在链中首先执行(请求处理时最先,响应处理时最后)。

    • 将 order 设置为接近 Ordered.LOWEST_PRECEDENCE,以确保顾问在链中最后执行(请求处理时最后,响应处理时最先)。

  • 值越大,解释为优先级越低。

  • 如果多个顾问具有相同的 order 值,则它们的执行顺序不予保证。

备注

顺序与执行序列之间看似矛盾之处,源于建议者链(advisor chain)的栈式特性:

  • 具有最高优先级(最低 order 值)的建议者会被添加到栈顶。
  • 随着栈展开(unwind),它将最先处理请求。
  • 随着栈回退(rewind),它将最后处理响应。

作为提醒,以下是 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();
}
提示

对于需要在输入端和输出端都位于链首的使用场景:

  1. 为每一端使用独立的 advisor。
  2. 为它们配置不同的 order 值。
  3. 使用 advisor context 在它们之间共享状态。

API 概述​

主要的 Advisor 接口位于包 org.springframework.ai.chat.client.advisor.api 中。创建自定义 Advisor 时,您将会遇到以下关键接口:

public interface Advisor extends Ordered {

String getName();

}

同步和响应式 Advisor 的两个子接口是

public interface CallAdvisor extends Advisor {

ChatClientResponse adviseCall(
ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);

}

and

public interface StreamAdvisor extends Advisor {

Flux<ChatClientResponse> adviseStream(
ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);

}

要在 Advice 中继续调用 Advice 链,请在你的 Advice 实现中使用 CallAdvisorChain 和 StreamAdvisorChain:

这些接口是

public interface CallAdvisorChain extends AdvisorChain {

/**
* Invokes the next {@link CallAdvisor} in the {@link CallAdvisorChain} with the given
* request.
*/
ChatClientResponse nextCall(ChatClientRequest chatClientRequest);

/**
* Returns the list of all the {@link CallAdvisor} instances included in this chain at
* the time of its creation.
*/
List<CallAdvisor> getCallAdvisors();

}

and

public interface StreamAdvisorChain extends AdvisorChain {

/**
* Invokes the next {@link StreamAdvisor} in the {@link StreamAdvisorChain} with the
* given request.
*/
Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);

/**
* Returns the list of all the {@link StreamAdvisor} instances included in this chain
* at the time of its creation.
*/
List<StreamAdvisor> getStreamAdvisors();

}

实现 Advisor​

要创建一个顾问,需实现 CallAdvisor 或 StreamAdvisor(或同时实现两者)。需要实现的关键方法是非流式场景下的 nextCall() 或流式场景下的 nextStream()。

示例​

我们将提供几个动手示例,以说明如何实现顾问(Advisors)来观察和增强用例。

日志顾问​

我们可以实现一个简单的日志记录切面,在调用链中下一个切面前记录 ChatClientRequest,在调用后记录 ChatClientResponse。请注意,该切面仅观察请求和响应,不会对其进行修改。此实现同时支持非流式和流式场景。

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 提供一个唯一的名称。

  • 你可以通过设置 order 值来控制执行顺序。值越小,执行优先级越高。

  • MessageAggregator 是一个实用类,用于将 Flux 响应聚合为单个 ChatClientResponse。这对于日志记录或其他需要观察整个响应(而非流中的单个项)的处理场景非常有用。注意,由于这是一个只读操作,你无法在 MessageAggregator 中修改响应。

重读(Re2)顾问​

"重新阅读提升大语言模型的推理能力" 一文介绍了一种名为 Re-Reading (Re2) 的技术,该技术能够提升大语言模型的推理能力。Re2 技术需要对输入提示词进行如下增强:

{Input_Query}
Read the question again: {Input_Query}

实现一个将 Re2 技术应用于用户输入查询的顾问可以这样做:

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 方法通过应用 Re-Reading 技术来增强用户的输入查询。

  • 您可以通过设置顺序值来控制执行顺序。较小的值会先执行。

Spring AI 内置顾问​

Spring AI 框架提供了多个内置的 advisors 以增强您的 AI 交互体验。以下是可用的 advisors 概览:

聊天记忆顾问​

这些顾问在聊天记忆存储中管理对话历史记录:

  • MessageChatMemoryAdvisor

    检索记忆并将其作为消息集合添加到提示词中。这种方法保留了对话历史的结构。请注意,并非所有 AI 模型都支持这种方法。

  • VectorStoreChatMemoryAdvisor

    从 VectorStore 中检索记忆,并将其添加到提示词的 system 文本中。该 advisor 适用于从大型数据集中高效搜索和检索相关信息。

Question Answering Advisor​
  • QuestionAnswerAdvisor

    该顾问使用向量存储提供问答能力,实现了 Naive RAG(检索增强生成)模式。

  • RetrievalAugmentationAdvisor

    使用 org.springframework.ai.rag 包中定义的构建模块并遵循模块化 RAG 架构,实现了常见的检索增强生成(RAG)流程的顾问。

推理建议器​
Tool Calling Advisor​
  • ToolCallingAdvisor

    作为 advisor 链的一部分处理工具调用循环。该 advisor 始终由 ChatClient 自动注册(除非明确禁用),因此即使未配置静态工具,也支持由其他 advisor 在运行时注入的工具。它会执行模型请求的工具调用并返回结果,持续迭代直到不再需要工具调用。实现了 ToolAdvisor,这是一个标记接口,用于防止第二个 ToolCallingAdvisor 被自动注册。

    完整文档请参阅 Recursive Advisors - ToolCallingAdvisor 和 ChatClient - Tool Calling。

内容安全顾问​
  • SafeGuardAdvisor

    一个旨在防止模型生成有害或不恰当内容的简单顾问(advisor)。

流式与非流式​

 Advisors Streaming vs Non-Streaming Flow

  • 非流式顾问(Non-streaming advisors)处理完整的请求和响应。
  • 流式顾问(Streaming advisors)将请求和响应作为连续的数据流处理,使用响应式编程概念(例如,使用 Flux 处理响应)。
@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain chain) {

return Mono.just(chatClientRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// This can be executed by blocking and non-blocking Threads.
// Advisor before next section
})
.flatMapMany(request -> chain.nextStream(request))
.map(response -> {
// Advisor after next section
});
}

最佳实践​

  1. 保持 Advisor 专注于特定任务,以获得更好的模块性。

  2. 必要时使用 adviseContext 在 Advisor 之间共享状态。

  3. 为你的 Advisor 实现流式和非流式版本,以获得最大的灵活性。

  4. 仔细考虑 Advisor 链中 Advisor 的顺序,以确保数据流正确。

破坏性 API 变更​

顾问接口​

  • 在 1.0 M2 版本中,存在独立的 RequestAdvisor 和 ResponseAdvisor 接口。

    • RequestAdvisor 会在 ChatModel.call 和 ChatModel.stream 方法执行之前被调用。

    • ResponseAdvisor 会在这些方法执行之后被调用。

  • 在 1.0 M3 版本中,这些接口已被替换为:

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • 此前属于 ResponseAdvisor 的 StreamResponseMode 已被移除。

  • 在 1.0.0 版本中,这些接口已被替换:

    • CallAroundAdvisor → CallAdvisor,StreamAroundAdvisor → StreamAdvisor,CallAroundAdvisorChain → CallAdvisorChain 以及 StreamAroundAdvisorChain → StreamAdvisorChain。

    • AdvisedRequest → ChatClientRequest 以及 AdivsedResponse → ChatClientResponse。

上下文映射处理​

  • 在 1.0 M2 中:

    • 上下文映射是一个独立的方法参数。

    • 该映射是可变的,并沿着调用链传递。

  • 在 1.0 M3 中:

    • 上下文映射现在是 AdvisedRequest 和 AdvisedResponse 记录的一部分。

    • 该映射是不可变的。

    • 要更新上下文,请使用 updateContext 方法,该方法会创建一个包含更新内容的新不可变映射。

章节摘要​