侧边栏壁纸
博主头像
牧云

怀璧慎显,博识谨言。

  • 累计撰写 120 篇文章
  • 累计创建 13 个标签
  • 累计收到 8 条评论

目 录CONTENT

文章目录

Advisors API

秋之牧云
2026-03-03 / 0 评论 / 0 点赞 / 3 阅读 / 0 字

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() 方法注册 Advisor。

1. 核心组件

该 API 包含用于非流式场景的 CallAdvisorCallAdvisorChain,以及用于流式场景的 StreamAdvisorStreamAdvisorChain。它还包括用于表示未封装的 Prompt 请求(用户最初提交的、尚未经过 Advisor 链处理的原始 Prompt 请求)的 ChatClientRequest,以及用于聊天完成响应的 ChatClientResponse。两者都包含一个 advise-context 以在 Advisor 链中共享状态。

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

此外,getOrder() 方法确定 Advisor 在链中的顺序,而 getName() 提供唯一的 Advisor 名称。

由 Spring AI 框架创建的 Advisor 链允许按其 getOrder() 值排序的多个 Advisor 顺序调用。值越低,执行越早。自动添加的最后一个 Advisor 将请求发送到 LLM。

以下流程图说明了 Advisor 链和聊天模型之间的交互

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

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

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

  4. 然后,聊天模型的响应通过 Advisor 链传递回并转换为 ChatClientResponse。稍后包括共享的 Advisor context 实例。

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

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

1.1. Advisor 顺序

Advisor 在链中的执行顺序由 getOrder() 方法确定。需要理解的关键点是

  • 值较低的 Advisor 首先执行。

  • Advisor 链作为一个堆栈运行

  • 链中的第一个 Advisor 是第一个处理请求的。

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

  • 要控制执行顺序

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

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

  • 较高的值表示较低的优先级。

  • 如果多个 Advisor 具有相同的顺序值,则其执行顺序不确定。

顺序与执行序列之间的看似矛盾是由于 Advisor 链的堆栈式性质

  • 优先级最高(顺序值最低)的 Advisor 被添加到堆栈顶部。

  • 随着堆栈展开,它将是第一个处理请求的。

  • 随着堆栈回卷,它将是最后一个处理响应的。

对于需要在输入和输出端都排在链中首位的用例

  1. 为每一端使用单独的 Advisor。

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

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

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

}
public interface StreamAdvisor extends Advisor {

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

}

要继续 Advice 链,请在 Advice 实现中使用 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();

}

3. 实现 Advisor

要创建 Advisor,请实现 CallAdvisorStreamAdvisor(或两者)。要实现的关键方法是非流式 Advisor 的 nextCall() 或流式 Advisor 的 nextStream()

3.1. 示例

3.1.1. 日志 Advisor

我们可以实现一个简单的日志 Advisor,它在调用链中下一个 Advisor 之前记录 ChatClientRequest,并在之后记录 ChatClientResponse。请注意,该 Advisor 只观察请求和响应,而不修改它们。此实现支持非流式和流式场景。

public class SimpleLoggerAdvisor implements CallAdvisor, StreamAdvisor {

	private static final Logger logger = LoggerFactory.getLogger(SimpleLoggerAdvisor.class);

	@Override
	public String getName() {
		return this.getClass().getSimpleName();
	}

	@Override
	public int getOrder() {
		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);
	}

	private void logRequest(ChatClientRequest request) {
		logger.debug("request: {}", request);
	}

	private void logResponse(ChatClientResponse chatClientResponse) {
		logger.debug("response: {}", chatClientResponse);
	}

}

MessageAggregator (聚合器)是一个实用程序类,它将 Flux 响应聚合到一个 ChatClientResponse 中。这对于记录或观察整个响应而不是流中单个项目的其他处理可能很有用。请注意,您不能在 MessageAggregator 中更改响应,因为它是一个只读操作。

3.1.2. 重读 (Re2) Advisor

重读提高大型语言模型的推理能力》文章介绍了一种名为重读 (Re2) 的技术,可以提高大型语言模型的推理能力。Re2 技术需要像这样增强输入 Prompt

{Input_Query}
Read the question again: {Input_Query}

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

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) {
		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() {
		return this.order;
	}

	public ReReadingAdvisor withOrder(int order) {
		this.order = order;
		return this;
	}

}

before 方法使用重读技术增强用户输入查询。

3.1.3. Spring AI 内置 Advisor

Spring AI 框架提供了几个内置 Advisor 来增强您的 AI 交互。以下是可用 Advisor 的概述

3.1.3.1. 聊天记忆 Advisor

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

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

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

  • VectorStoreChatMemoryAdvisor

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

3.1.3.2. 问答 Advisor
  • QuestionAnswerAdvisor

此 Advisor 使用向量存储提供问答功能,实现 Naive RAG(检索增强生成)模式。

  • RetrievalAugmentationAdvisor

Advisor that implements common Retrieval Augmented Generation (RAG) flows using the building blocks defined in the org.springframework.ai.rag package and following the Modular RAG Architecture.

使用“org.springframework.ai.RAG”包中定义的构建块并遵循模块化RAG架构实现通用检索增强生成(RAG)流的顾问。

3.1.3.3. 推理 Advisor
  • ReReadingAdvisor

实现了 LLM 推理的重读策略,称为 RE2,以增强输入阶段的理解。基于文章:[重读提高 LLM 中的推理能力](arxiv.org/pdf/2309.06275)。

3.1.3.4. 内容安全 Advisor
  • SafeGuardAdvisor

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

3.2. 流式与非流式

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

  • 流式 Advisor 使用响应式编程概念(例如,用于响应的 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
            });
}

3.3. 最佳实践

  1. 让 Advisor 专注于特定任务以获得更好的模块化。

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

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

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

4. API 破坏性更改

4.1. Advisor 接口

  • 在 1.0 M2 中,有单独的 RequestAdvisorResponseAdvisor 接口。

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

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

  • 在 1.0 M3 中,这些接口已被以下接口替换

  • CallAroundAdvisor

  • StreamAroundAdvisor

  • 以前作为 ResponseAdvisor 一部分的 StreamResponseMode 已被删除。

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

  • CallAroundAdvisorCallAdvisorStreamAroundAdvisorStreamAdvisorCallAroundAdvisorChainCallAdvisorChainStreamAroundAdvisorChainStreamAdvisorChain

  • AdvisedRequestChatClientRequestAdivsedResponseChatClientResponse

4.2. 上下文映射处理

  • 在 1.0 M2 中

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

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

  • 在 1.0 M3 中

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

  • 该映射是不可变的。

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

5. 递归顾问

递归顾问是一种特殊类型的顾问,可以多次循环执行下游顾问链。当您需要重复调用 LLM 直到满足某个条件时,此模式非常有用,例如:

  • 循环执行工具调用直到不再需要调用工具

  • 验证结构化输出并在验证失败时重试

  • 使用对请求的修改来实现评估逻辑

  • 使用对请求的修改来实现重试逻辑

CallAdvisorChain.copy(CallAdvisor after) 方法是实现递归顾问模式的关键实用程序。它创建一个新的顾问链,其中仅包含原始链中指定顾问之后的顾问,并允许递归顾问根据需要调用此子链。这种方法确保了:

  • 递归顾问可以循环执行链中剩余的顾问

  • 链中的其他顾问可以观察和拦截每次迭代

  • 顾问链保持正确的顺序和可观察性

  • 递归顾问不会重新执行在其之前执行过的顾问

5.1. 内置递归顾问

Spring AI 提供了两个内置的递归顾问来演示这种模式

5.1.1. ToolCallAdvisor

ToolCallAdvisor 将工具调用循环作为顾问链的一部分实现,而不是依赖模型的内部工具执行。这使得链中的其他顾问能够拦截和观察工具调用过程。

主要功能

  • 通过设置 setInternalToolExecutionEnabled(false) 禁用模型的内部工具执行

  • 循环执行顾问链,直到不再存在工具调用

  • 支持“直接返回”功能——当工具执行具有 returnDirect=true 时,它会中断工具调用循环并将工具执行结果直接返回给客户端应用程序,而不是将其发送回 LLM

  • 使用 callAdvisorChain.copy(this) 创建子链以进行递归调用

  • 包含空安全检查,以处理聊天响应可能为空的情况

示例用法

var toolCallAdvisor = ToolCallAdvisor.builder()
    .toolCallingManager(toolCallingManager)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 300)
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(toolCallAdvisor)
    .build();
5.1.1.1. 直接返回功能

“直接返回”功能允许工具绕过 LLM,将其结果直接返回给客户端应用程序。这在以下情况下很有用:

  • 工具的输出是最终答案,不需要 LLM 处理

  • 您希望通过避免额外的 LLM 调用来减少延迟

  • 工具结果应按原样返回,无需解释

当工具执行具有 returnDirect=true 时,ToolCallAdvisor 将:

  1. 正常执行工具调用

  2. ToolExecutionResult 中检测 returnDirect 标志

  3. 跳出工具调用循环

  4. 将工具执行结果作为 ChatResponse 直接返回给客户端应用程序,其中工具的输出作为生成内容

5.1.2. StructuredOutputValidationAdvisor

StructuredOutputValidationAdvisor 根据生成的 JSON Schema 验证结构化 JSON 输出,并在验证失败时重试调用,最多重试指定次数。

主要功能

  • 从预期输出类型自动生成 JSON Schema

  • 根据 Schema 验证 LLM 响应

  • 如果验证失败,重试调用,最多可配置次数

  • 在重试尝试时通过验证错误消息增强提示,以帮助 LLM 纠正其输出

  • 使用 callAdvisorChain.copy(this) 创建子链以进行递归调用

  • 可选支持自定义 ObjectMapper 用于 JSON 处理

示例用法

var validationAdvisor = StructuredOutputValidationAdvisor.builder()
    .outputType(MyResponseType.class)
    .maxRepeatAttempts(3)
    .advisorOrder(BaseAdvisor.HIGHEST_PRECEDENCE + 1000)
    .build();

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(validationAdvisor)
    .build();

0

评论区