顾问 API

Spring AI 顾问 API 提供了一种灵活而强大的方法来拦截、修改和增强 Spring 应用程序中 AI 驱动的交互。通过利用顾问 API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。

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

您可以使用聊天客户端 API配置现有顾问,如下例所示

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
        new QuestionAnswerAdvisor(vectorStore, SearchRequest.defaults()) // 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();

建议使用构建器的 defaultAdvisors() 方法在构建时注册顾问。

顾问也参与可观测性堆栈,因此您可以查看与其执行相关的指标和跟踪。

核心组件

API 包含用于非流式场景的 CallAroundAdvisorCallAroundAdvisorChain,以及用于流式场景的 StreamAroundAdvisorStreamAroundAdvisorChain。它还包括 AdvisedRequest 用于表示未密封的 Prompt 请求,AdvisedResponse 用于聊天完成响应。两者都包含一个 advise-context 用于在顾问链之间共享状态。

Advisors API Classes

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

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

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

以下流程图说明了顾问链与聊天模型之间的交互

Advisors API Flow
  1. Spring AI 框架根据用户的 Prompt 和一个空的 AdvisorContext 对象创建一个 AdvisedRequest

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

  3. 框架提供的最终顾问将请求发送到 聊天模型

  4. 然后将聊天模型的响应传递回顾问链并转换为 AdvisedResponse。后者包括共享的 AdvisorContext 实例。

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

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

顾问顺序

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

  • 顺序值较低的顾问首先执行。

  • 顾问链像栈一样工作

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

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

  • 要控制执行顺序

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

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

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

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

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

提醒一下,以下是 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. 对每一侧使用单独的顾问。

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

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

API 概述

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

public interface Advisor extends Ordered {

	String getName();

}

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

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

}

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

}

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

这些接口是

public interface CallAroundAdvisorChain {

	AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);

}

public interface StreamAroundAdvisorChain {

	Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);

}

实现顾问

要创建顾问,请实现 CallAroundAdvisorStreamAroundAdvisor(或两者)。要实现的关键方法是用于非流式的 nextAroundCall() 或用于流式顾问的 nextAroundStream()

示例

我们将提供一些动手示例来说明如何实现顾问以观察和增强用例。

日志记录顾问

我们可以实现一个简单的日志记录顾问,在调用链中下一个顾问之前记录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)
	}
}
1 为顾问提供一个唯一的名称。
2 可以通过设置 order 值来控制执行顺序。较低的值先执行。
3 MessageAggregator是一个实用程序类,它将 Flux 响应聚合到单个 AdvisedResponse 中。这对于记录或其他观察整个响应而不是流中单个项目的处理很有用。请注意,您不能在MessageAggregator中更改响应,因为它是一个只读操作。

重新阅读 (Re2) 顾问

重新阅读提高大型语言模型的推理能力”文章介绍了一种称为重新阅读 (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)
			.withUserText("""
			    {re2_input_query}
			    Read the question again: {re2_input_query}
			    """)
			.withUserParams(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();
	}
}
1 before方法通过应用重新阅读技术来增强用户的输入查询。
2 aroundCall方法拦截非流式请求并应用重新阅读技术。
3 aroundStream方法拦截流式请求并应用重新阅读技术。
4 可以通过设置 order 值来控制执行顺序。较低的值先执行。
5 为顾问提供一个唯一的名称。

Spring AI 内置顾问

Spring AI 框架提供了一些内置顾问来增强您的 AI 交互。以下是可用顾问的概述

聊天记忆顾问

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

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

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

  • VectorStoreChatMemoryAdvisor

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

问答顾问
  • QuestionAnswerAdvisor

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

内容安全顾问
  • SafeGuardAdvisor

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

流式与非流式

Advisors Streaming vs Non-Streaming Flow
  • 非流式顾问使用完整的请求和响应。

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

最佳实践

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

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

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

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

向后兼容性

AdvisedRequest类已移动到一个新包中。虽然RequestResponseAdvisor接口仍然可用,但它被标记为已弃用,并将在 M3 版本左右移除。建议对新实现使用新的CallAroundAdvisorStreamAroundAdvisor接口。

重大 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...
}