跳到主要内容

Advisors API

DeepSeek V3 中英对照 Advisors Advisors API

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

主要优势包括封装重复的生成式 AI 模式、转换发送到和来自大型语言模型(LLMs)的数据,以及提供跨各种模型和用例的可移植性。

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

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

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

Advisors 也参与可观测性堆栈,因此你可以查看与其执行相关的指标和追踪信息。

核心组件

该 API 包含 CallAroundAdvisorCallAroundAdvisorChain 用于非流式场景,以及 StreamAroundAdvisorStreamAroundAdvisorChain 用于流式场景。它还包含 AdvisedRequest 用于表示未封装的 Prompt 请求,AdvisedResponse 用于表示 Chat Completion 响应。两者都持有一个 advise-context,用于在 Advisor 链中共享状态。

Advisors API 类

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

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

由 Spring AI 框架创建的 Advisor Chain 允许顺序调用多个 advisor,这些 advisor 按照它们的 getOrder() 值进行排序。数值较小的 advisor 会优先执行。最后一个 advisor 是自动添加的,它会将请求发送到 LLM。

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

Advisors API 流程图

  1. Spring AI 框架从用户的 Prompt 创建一个 AdvisedRequest,同时创建一个空的 AdvisorContext 对象。

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

  3. 框架提供的最后一个 advisor 将请求发送到 Chat Model

  4. 然后,Chat Model 的响应通过 advisor 链传递回来,并转换为 AdvisedResponse。随后包括共享的 AdvisorContext 实例。

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

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

顾问顺序

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

  • 具有较低顺序值的 Advisors 会先执行。

  • Advisor 链以栈的方式运行:

    • 链中的第一个 Advisor 会首先处理请求。

    • 它也会最后处理响应。

  • 要控制执行顺序:

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

    • 将顺序值设置为接近 Ordered.LOWEST_PRECEDENCE,以确保 Advisors 在链中最后执行(最后处理请求,首先处理响应)。

  • 较高的值会被解释为较低的优先级。

  • 如果多个 Advisors 具有相同的顺序值,则它们的执行顺序无法保证。

备注

顺序和执行序列之间的表面矛盾是由于顾问链的栈式特性造成的:* 优先级最高(顺序值最低)的顾问会被添加到栈的顶部。* 当栈展开时,它将首先处理请求。* 当栈回卷时,它将最后处理响应。

提醒一下,以下是 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();
}
java

:::提示
对于需要在输入和输出两侧都位于链首的使用场景:

  1. 为每一侧使用单独的 advisor。

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

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

API 概述

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

public interface Advisor extends Ordered {

String getName();

}
java

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

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

}
java

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

}
java

要继续 Advice 链,可以在你的 Advice 实现中使用 CallAroundAdvisorChainStreamAroundAdvisorChain

接口是

public interface CallAroundAdvisorChain {

AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);

}
java

public interface StreamAroundAdvisorChain {

Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);

}
java

实现一个 Advisor

要创建一个 advisor,需要实现 CallAroundAdvisorStreamAroundAdvisor(或两者)。对于非流式 advisor,需要实现的关键方法是 nextAroundCall(),而对于流式 advisor,则需要实现 nextAroundStream()

示例

我们将提供一些实践示例,来说明如何实现用于观察和增强用例的 advisors。

日志记录顾问

我们可以实现一个简单的日志记录通知器,它会在调用链中的下一个通知器之前记录 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
}
}
java
  • 为 advisor 提供一个唯一的名称。

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

  • MessageAggregator 是一个实用工具类,用于将 Flux 响应聚合为一个单一的 AdvisedResponse。这对于日志记录或其他需要观察整个响应而非流中单个项目的处理非常有用。请注意,你不能在 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 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();
}
}
java
  • before 方法通过应用 Re-Reading 技术增强用户的输入查询。

  • aroundCall 方法拦截非流式请求并应用 Re-Reading 技术。

  • aroundStream 方法拦截流式请求并应用 Re-Reading 技术。

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

  • 为 advisor 提供一个唯一的名称。

Spring AI 内置 Advisor

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

聊天记忆顾问

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

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

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

  • VectorStoreChatMemoryAdvisor

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

问答顾问
  • QuestionAnswerAdvisor

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

内容安全顾问
  • SafeGuardAdvisor

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

流式与非流式

Advisors 流式与非流式流程

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

  • 流式顾问将请求和响应作为连续流处理,使用响应式编程概念(例如,使用 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
});
}
java

最佳实践

  1. 让顾问专注于特定任务,以提高模块化程度。

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

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

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

向后兼容性

important

AdvisedRequest 类已移至新的包中。

破坏性 API 变更

Spring AI 顾问链从 1.0 M2 到 1.0 M3 版本经历了重大变化。以下是关键的修改内容:

顾问接口

  • 在 1.0 M2 版本中,存在独立的 RequestAdvisorResponseAdvisor 接口。

    • RequestAdvisorChatModel.callChatModel.stream 方法之前被调用。

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

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

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • 之前作为 ResponseAdvisor 一部分的 StreamResponseMode 已被移除。

上下文映射处理

  • 在 1.0 M2 中:

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

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

  • 在 1.0 M3 中:

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

    • 映射是不可变的。

    • 要更新上下文,请使用 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()); // Add multiple key-value pairs
context.put("lastBefore", getName()); // Add a single key-value pair
return context;
});

// Method implementation continues...
}
java