跳到主要内容

顾问 API

Deepseek 3.2 中英对照 Advisors Advisors API

Spring AI Advisors API 为 Spring 应用程序中的 AI 驱动交互提供了灵活且强大的拦截、修改和增强方式。通过利用 Advisors API,开发者可以创建更复杂、可复用且可维护的 AI 组件。

主要优势包括封装重复出现的生成式AI模式、转换发送至大型语言模型(LLM)及从其返回的数据,以及提供跨多种模型和用例的可移植性。

您可以使用 ChatClient API 来配置现有的 advisor,如下例所示:

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。

顾问同样参与可观测性栈的构建,因此你可以查看与他们执行相关的指标和追踪信息。

核心组件

该API包含用于非流式场景的CallAdvisorCallAdvisorChain,以及用于流式场景的StreamAdvisorStreamAdvisorChain。同时包含代表未封装Prompt请求的ChatClientRequest,以及表示Chat Completion响应的ChatClientResponse。两者都包含用于在顾问链中共享状态的advise-context

Advisors API 类

adviseCall()adviseStream() 是关键的建议方法,通常执行以下操作:检查未密封的提示数据、自定义和增强提示数据、调用建议链中的下一个实体、可选地阻止请求、检查聊天完成响应,以及抛出异常以指示处理错误。

此外,getOrder() 方法决定了顾问在链中的顺序,而 getName() 则提供了一个唯一的顾问名称。

Spring AI框架创建的Advisor Chain允许按照各advisor的getOrder()值顺序依次调用多个advisor。数值较小的advisor会优先执行。最后一个advisor会自动添加,负责将请求发送给大语言模型(LLM)。

下图展示了 advisor chain 与 Chat Model 之间的交互流程:

Advisors API 流程

  1. Spring AI 框架根据用户的 Prompt 创建 ChatClientRequest,同时创建一个空的顾问 context 对象。

  2. 链中的每个顾问都会处理该请求,并可能对其进行修改。或者,它可以选择通过不调用下一个实体来阻止该请求。在后一种情况下,顾问负责填写响应。

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

  4. 然后,Chat Model 的响应会通过顾问链传递回来,并转换为 ChatClientResponse。后者包含共享的顾问 context 实例。

  5. 每个顾问都可以处理或修改该响应。

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

顾问顺序

链中通知器的执行顺序由 getOrder() 方法决定。需要理解的关键点包括:

  • 数值较小的通知器(advisor)会优先执行。

  • 通知器链采用栈式结构运行:

    • 链中的第一个通知器最先处理请求。

    • 同时它也是最后一个处理响应的。

  • 要控制执行顺序:

    • 将 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();
}
提示

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

  1. 为两端分别使用独立的advisor。

  2. 为它们配置不同的order值。

  3. 利用advisor上下文在它们之间共享状态。

API 概览

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

public interface Advisor extends Ordered {

String getName();

}

同步和反应式Advisors的两个子接口是

public interface CallAdvisor extends Advisor {

ChatClientResponse adviseCall(
ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);

}

public interface StreamAdvisor extends Advisor {

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

}

要继续建议链,请在您的建议实现中使用 CallAdvisorChainStreamAdvisorChain

接口如下

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();

}

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();

}

实现顾问

要创建顾问,请实现 CallAdvisorStreamAdvisor(或两者都实现)。需要实现的关键方法是针对非流式顾问的 nextCall(),或针对流式顾问的 nextStream()

示例

我们将提供几个实际案例,来说明如何实现用于观察和增强用例的顾问程序。

日志顾问

我们可以实现一个简单的日志记录通知器,在调用链中下一个通知器之前记录 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);
}

}
  • 为顾问提供一个唯一名称。

  • 您可以通过设置 order 值来控制执行的顺序。值越小,执行越早。

  • MessageAggregator 是一个实用类,它将 Flux 响应聚合为单个 ChatClientResponse。这对于日志记录或其他需要观察整个响应而非流中单个项目的处理非常有用。请注意,您不能在 MessageAggregator 中更改响应,因为这是一个只读操作。

重读(Re2)顾问

"Re-Reading Improves Reasoning in Large Language Models" 这篇文章介绍了一种名为 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 方法通过应用重读技术增强用户的输入查询。

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

Spring AI 内置顾问

Spring AI 框架提供了多个内置的顾问器来增强你的 AI 交互体验。以下是可用顾问器的概述:

聊天记忆顾问

这些顾问负责管理聊天记忆存储中的对话历史:

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

    检索记忆并将其整合到提示的系统文本中。

  • VectorStoreChatMemoryAdvisor

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

问题解答顾问
  • QuestionAnswerAdvisor

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

  • RetrievalAugmentationAdvisor

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

    在 LLM 推理中实现一种称为 RE2 的重读策略,以增强输入阶段的理解。基于文章:[Re-Reading Improves Reasoning in LLMs](arxiv.org/pdf/2309.06275)。

内容安全顾问
  • SafeGuardAdvisor

    一个简单的顾问模块,旨在防止模型生成有害或不适当的内容。

流式与非流式

Advisors Streaming vs Non-Streaming Flow

  • 非流式顾问处理完整的请求和响应。

  • 流式顾问将请求和响应作为连续流进行处理,采用响应式编程概念(例如,使用 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. 让顾问专注于特定任务,以获得更好的模块化。

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

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

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

API 重大变更

顾问接口

  • 在 1.0 M2 版本中,存在独立的 RequestAdvisorResponseAdvisor 接口。
    • RequestAdvisorChatModel.callChatModel.stream 方法之前被调用。
    • ResponseAdvisor 在这些方法之后被调用。
  • 在 1.0 M3 版本中,这些接口已被替换为:
    • CallAroundAdvisor
    • StreamAroundAdvisor
  • 原先属于 ResponseAdvisorStreamResponseMode 已被移除。
  • 在 1.0.0 版本中,这些接口已被替换:
    • CallAroundAdvisorCallAdvisorStreamAroundAdvisorStreamAdvisorCallAroundAdvisorChainCallAdvisorChain 以及 StreamAroundAdvisorChainStreamAdvisorChain
    • AdvisedRequestChatClientRequest 以及 AdivsedResponseChatClientResponse

上下文映射处理

  • 在 1.0 M2 版本中:

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

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

  • 在 1.0 M3 版本中:

    • 上下文映射现在成为了 AdvisedRequestAdvisedResponse 记录的一部分。

    • 该映射是不可变的。

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

章节概要