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

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

您可以使用 ChatClient API 配置现有增强器,如下例所示:

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory 增强器
        QuestionAnswerAdvisor.builder((vectorStore).builder() // RAG 增强器
    )
    .build();

var conversationId = "678";

String response = this.chatClient.prompt()
    // 在运行时设置增强器参数	
    .advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
    .user(userText)
    .call()
	.content();

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

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

核心组件

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

Advisors API 类

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

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

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

以下流程图说明了增强器链与聊天模型之间的交互:

Advisors API 流程

1

创建请求

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

2

处理请求

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

3

发送请求

框架提供的最后一个增强器将请求发送到 Chat Model

4

创建响应

聊天模型的响应然后通过增强器链传回并转换为 AdvisedResponse。后者包括共享的 AdvisorContext 实例。

5

处理响应

每个增强器可以处理或修改响应。

6

返回响应

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

增强器顺序

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

执行顺序和顺序值之间的表面矛盾是由于增强器链的堆栈性质:

  • 具有最高优先级(最低顺序值)的增强器被添加到堆栈顶部。
  • 当堆栈展开时,它将首先处理请求。
  • 当堆栈重绕时,它将最后处理响应。

作为提醒,以下是 Spring Ordered 接口的语义:

public interface Ordered {

    /**
     * 最高优先级值的常量。
     * @see java.lang.Integer#MIN_VALUE
     */
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

    /**
     * 最低优先级值的常量。
     * @see java.lang.Integer#MAX_VALUE
     */
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

    /**
     * 获取此对象的顺序值。
     * <p>较高的值被解释为较低的优先级。因此,
     * 具有最低值的对象具有最高优先级(有点类似于 Servlet {@code load-on-startup} 值)。
     * <p>相同的顺序值将导致受影响对象的任意排序位置。
     * @return 顺序值
     * @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 {
	/**
	 * 环绕通知,包装 ChatModel#call(Prompt) 方法。
	 * @param advisedRequest 建议的请求
	 * @param chain 增强器链
	 * @return 响应
	 */
	AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain);
}

public interface StreamAroundAdvisor extends Advisor {
	/**
	 * 环绕通知,包装建议请求的调用。
	 * @param advisedRequest 建议的请求
	 * @param chain 要执行的增强器链
	 * @return 建议请求的结果
	 */
	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. 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。
  3. MessageAggregator 是一个实用类,将 Flux 响应聚合为单个 AdvisedResponse。这对于记录或观察整个响应而不是流中的单个项目的其他处理很有用。注意,您不能在 MessageAggregator 中修改响应,因为它是只读操作。

重读 (Re2) 增强器

Re-Reading Improves Reasoning in Large Language Models” 文章介绍了一种称为重读 (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();
	}
}
  1. before 方法通过应用重读技术增强用户的输入查询。
  2. aroundCall 方法拦截非流式请求并应用重读技术。
  3. aroundStream 方法拦截流式请求并应用重读技术。
  4. 您可以通过设置顺序值来控制执行顺序。较低的值首先执行。
  5. 为增强器提供唯一名称。

Spring AI 内置增强器

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

流式与非流式

增强器流式与非流式流程

  • 非流式增强器处理完整的请求和响应。
  • 流式增强器将请求和响应作为连续流处理,使用响应式编程概念(例如,用于响应的 Flux)。

实现带有阻塞和非阻塞代码的流式增强器的示例:

@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
    
    return  Mono.just(advisedRequest)
            .publishOn(Schedulers.boundedElastic())
            .map(request -> {
                // 这可以由阻塞和非阻塞线程执行。
                // 增强器在下一个部分之前
            })
            .flatMapMany(request -> chain.nextAroundStream(request))
            .map(response -> {
                // 增强器在下一个部分之后
            });
}

最佳实践

专注责任

保持增强器专注于特定任务以获得更好的模块化。

共享状态

必要时使用 adviseContext 在增强器之间共享状态。

支持两种模式

实现增强器的流式和非流式版本以获得最大灵活性。

考虑顺序

仔细考虑增强器在链中的顺序以确保正确的数据流。

向后兼容性

AdvisedRequest 类已移动到新包。

API 重大变更

Spring AI 增强器链从版本 1.0 M2 到 1.0 M3 经历了重大变化。以下是主要修改:

增强器接口

  • 单独的 RequestAdvisorResponseAdvisor 接口
  • RequestAdvisorChatModel.callChatModel.stream 方法之前调用
  • ResponseAdvisor 在这些方法之后调用
  • 使用 StreamResponseMode 作为 ResponseAdvisor 的一部分

上下文映射处理

  • 上下文映射是一个单独的方法参数
  • 映射是可变的并沿链传递

在 1.0 M3 中更新上下文的示例:

@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {

    this.advisedRequest = advisedRequest.updateContext(context -> {
        context.put("aroundCallBefore" + getName(), "AROUND_CALL_BEFORE " + getName());  // 添加多个键值对
        context.put("lastBefore", getName());  // 添加单个键值对
        return context;
    });

    // 方法实现继续...
}

文档有误?请协助编辑

发现文档问题?点击此处直接在 GitHub 上编辑并提交 PR,帮助我们改进文档!