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

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

核心组件

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

Advisors API Classes

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

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

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

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

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

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

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

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

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

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

Advisor 顺序

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

  • 订单值较低的 Advisor 首先执行。

  • Advisor 链作为一个堆栈运行

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

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

  • 要控制执行顺序

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

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

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

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

顺序与执行序列之间的看似矛盾是由于 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();

}

同步和响应式 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();

}

实现 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() { (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);
	}

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

重读 (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) { (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;
	}

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

Spring AI 内置 Advisor

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

聊天记忆 Advisor

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

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

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

  • VectorStoreChatMemoryAdvisor

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

问答 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.
推理 Advisor
  • ReReadingAdvisor

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

内容安全 Advisor
  • SafeGuardAdvisor

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

流式与非流式

Advisors Streaming vs Non-Streaming Flow
  • 非流式 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
            });
}

最佳实践

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

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

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

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

API 破坏性更改

Advisor 接口

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

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

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

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

    • CallAroundAdvisor

    • StreamAroundAdvisor

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

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

    • CallAroundAdvisorCallAdvisorStreamAroundAdvisorStreamAdvisorCallAroundAdvisorChainCallAdvisorChainStreamAroundAdvisorChainStreamAdvisorChain

    • AdvisedRequestChatClientRequestAdivsedResponseChatClientResponse

上下文映射处理

  • 在 1.0 M2 中

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

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

  • 在 1.0 M3 中

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

    • 该映射是不可变的。

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

© . This site is unofficial and not affiliated with VMware.