Advisors API

Spring AI Advisors API 提供了一种灵活且强大的方式来拦截、修改和增强 Spring 应用程序中 AI 驱动的交互。 通过利用 Advisors API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。 其主要优势包括封装重复的生成式 AI 模式、转换发送到大型语言模型 (LLM) 和从 LLM 接收的数据,以及在各种模型和用例之间提供可移植性。 您可以使用 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();

建议在构建时使用 builder 的 defaultAdvisors() 方法注册 advisors。 Advisors 也参与可观测性堆栈,因此您可以查看与其执行相关的指标和跟踪。

核心组件

该 API 包含用于非流式场景的 CallAdvisorCallAdvisorChain,以及用于流式场景的 StreamAdvisorStreamAdvisorChain。 它还包括用于表示未密封 Prompt 请求的 ChatClientRequest 和用于 Chat Completion 响应的 ChatClientResponse。两者都包含一个 advise-context 以在 advisor 链中共享状态。

advisors api classes

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

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

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

以下流程图说明了 advisor 链和 Chat Model 之间的交互:

advisors flow
  1. Spring AI 框架根据用户的 Prompt 和一个空的 advisor context 对象创建 ChatClientRequest

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

  3. 由框架提供的最终 advisor 将请求发送到 Chat Model

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

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

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

Advisor 顺序

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

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

  • advisor 链作为堆栈运行:

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

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

  • 要控制执行顺序:

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

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

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

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

顺序和执行序列之间看似矛盾的原因是 advisor 链的堆栈式性质:

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

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

  • 当堆栈回溯时,它将是最后一个处理响应的。

提醒一下,以下是 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. 用不同的顺序值配置它们。

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

}

要继续 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();

}

实现 Advisor

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

示例

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

日志 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() { [id="CO1-1"][id="CO1-1"][id="CO1-1"](1)
		return this.getClass().getSimpleName();
	}

	@Override
	public int getOrder() { [id="CO1-2"][id="CO1-2"][id="CO1-2"](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); [id="CO1-3"][id="CO1-3"][id="CO1-3"](3)
	}

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

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

}
 <1> 为 advisor 提供唯一的名称。
 <1> 您可以通过设置顺序值来控制执行顺序。值越低,执行越早。
 <1> `MessageAggregator` 是一个实用程序类,它将 Flux 响应聚合为单个 ChatClientResponse。
这对于日志记录或其他观察整个响应而不是流中单个项目的处理非常有用。
请注意,您不能在 `MessageAggregator` 中更改响应,因为它是只读操作。

复读 (Re2) Advisor

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

{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) { [id="CO2-1"][id="CO1-4"][id="CO1-4"](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() { [id="CO2-2"][id="CO1-5"][id="CO1-5"](2)
		return this.order;
	}

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

}
<1>  `before` 方法通过应用复读技术来增强用户的输入查询。
<1>  您可以通过设置顺序值来控制执行顺序。值越低,执行越早。

Spring AI 内置 Advisors

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

聊天记忆 Advisors

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

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

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

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

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

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

推理 Advisor
  • `ReReadingAdvisor`实现 LLM 推理的复读策略,称为 RE2,以增强输入阶段的理解。 基于文章:[Re-Reading Improves Reasoning in LLMs]([role="bare"][role="bare"][role="bare"]https://arxiv.org/pdf/2309.06275)。

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

流式与非流式

advisors non stream vs stream
  • 非流式 advisors 处理完整的请求和响应。

  • 流式 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. 保持 advisors 专注于特定任务以获得更好的模块化。

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

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

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

破坏性 API 更改

Advisor 接口

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

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

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

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

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • StreamResponseMode(以前是 ResponseAdvisor 的一部分)已被删除。

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

    • CallAroundAdvisorCallAdvisorStreamAroundAdvisorStreamAdvisorCallAroundAdvisorChainCallAdvisorChainStreamAroundAdvisorChainStreamAdvisorChain

    • AdvisedRequestChatClientRequestAdivsedResponseChatClientResponse

上下文 Map 处理

  • 在 1.0 M2 中:

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

    • 该 map 是可变的并沿链传递。

  • 在 1.0 M3 中:

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

    • 该 map 是不可变的。

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