05_Spring AI 干货笔记之 Advisors API

新星杯·14天创作挑战营·第17期 10w+人浏览 727人参与

一、Advisors API

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

其核心优势在于封装重复的生成式 AI 模式、转换发送至大语言模型及从其接收的数据,并提供跨不同模型和用例的可移植性。

您可以使用 ChatClient API 配置现有的 Advisors,如下例所示:

ChatMemory chatMemory = ... // 初始化您的对话记忆存储
VectorStore vectorStore = ... // 初始化您的向量存储

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(), // 对话记忆 Advisor
        QuestionAnswerAdvisor.builder(vectorStore).build()    // RAG Advisor
    )
    .build();

var conversationId = "678";

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

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

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

  • 了解问答顾问

  • 了解对话记忆顾问

二、核心组件

该 API 包含用于非流式场景的 CallAdvisor 和 CallAdvisorChain,以及用于流式场景的 StreamAdvisor 和 StreamAdvisorChain。它还包含代表未最终确定的 Prompt 请求的 ChatClientRequest,和代表对话补全响应的 ChatClientResponse。两者都持有一个 advise-context 以便在 Advisor 链中共享状态。
Advisor API 类
adviseCall() 和 adviseStream() 是关键方法,通常执行诸如检查未最终确定的 Prompt 数据、定制和增强 Prompt 数据、调用 Advisor 链中的下一个实体、可选择性地阻塞请求、检查对话补全响应以及抛出异常以指示处理错误等操作。

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

由 Spring AI 框架创建的 Advisor 链,允许按 getOrder() 值排序依次调用多个 Advisor。数值较小的先执行。最后一个 Advisor 由框架自动添加,负责将请求发送给 LLM。

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

  • Spring AI 框架根据用户的 Prompt 创建一个 ChatClientRequest 以及一个空的 Advisor 上下文对象。

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

  • 由框架提供的最终 Advisor 将请求发送给聊天模型。

  • 聊天模型的响应随后传回 Advisor 链,并转换为 ChatClientResponse。该响应包含了共享的 Advisor 上下文实例。

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

  • 最终通过提取 ChatCompletion 将 ChatClientResponse 返回给客户端。

2.1 Advisor 顺序

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

  • 具有较低顺序值的 Advisor 先执行。

  • Advisor 链像栈一样运作:

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

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

  • 要控制执行顺序:

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

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

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

  • 如果多个 Advisor 具有相同的顺序值,它们的执行顺序无法保证。

顺序与执行序列之间看似矛盾是由于 Advisor 链的栈式特性造成的:

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

  • 随着栈的展开,它将最先处理请求。

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

作为提醒,以下是 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();
}

对于需要在链的输入和输出两侧都处于最先位置的用例:

  • 为每一侧使用单独的 Advisor。

  • 为它们配置不同的顺序值。

  • 使用 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 实现中使用 CallAdvisorChain 和 StreamAdvisorChain:

接口是:

public interface CallAdvisorChain extends AdvisorChain {

	/**
	 * 使用给定的请求调用 {@link CallAdvisorChain} 中的下一个 {@link CallAdvisor}。
	 */
	ChatClientResponse nextCall(ChatClientRequest chatClientRequest);

	/**
	 * 返回此链创建时包含的所有 {@link CallAdvisor} 实例的列表。
	 */
	List<CallAdvisor> getCallAdvisors();

}

public interface StreamAdvisorChain extends AdvisorChain {

	/**
	 * 使用给定的请求调用 {@link StreamAdvisorChain} 中的下一个 {@link StreamAdvisor}。
	 */
	Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);

	/**
	 * 返回此链创建时包含的所有 {@link StreamAdvisor} 实例的列表。
	 */
	List<StreamAdvisor> getStreamAdvisors();

}

四、实现 Advisor

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

4.1 示例

我们将提供几个实践示例来说明如何实现用于观察和增强用例的 Advisor。

4.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);
	}

}
  • 为 Advisor 提供唯一名称。

  • 您可以通过设置顺序值来控制执行顺序。数值较小的先执行。

  • MessageAggregator 是一个工具类,它将 Flux 响应聚合为一个单一的 ChatClientResponse。这对于记录日志或其他需要观察整个响应而非流中单个项的处理非常有用。请注意,您不能在 MessageAggregator 中更改响应,因为这是一个只读操作。

4.1.2 重读 (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) { 
		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 方法应用重读技术来增强用户的输入查询。

  • 您可以通过设置顺序值来控制执行顺序。数值较小的先执行。

4.1.3 Spring AI 内置 Advisor

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

4.1.3.1 对话记忆 Advisor

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

  • MessageChatMemoryAdvisor

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

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

    • 从 VectorStore 检索记忆并将其添加到提示词的系统文本中。该 Advisor 对于从大型数据集中高效搜索和检索相关信息非常有用。
4.1.3.2 问答 Advisor
  • QuestionAnswerAdvisor

    • 该 Advisor 使用向量存储来提供问答能力,实现了朴素 RAG(检索增强生成)模式。
  • RetrievalAugmentationAdvisor

    • 实现常见检索增强生成(RAG)流程的 Advisor,使用 org.springframework.ai.rag 包中定义的构建块,并遵循模块化 RAG 架构。
4.1.3.3 推理 Advisor
4.1.3.4 内容安全 Advisor
  • SafeGuardAdvisor

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

4.2 流式与非流式

Advisor 流式与非流式流程

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

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

@Override
public Flux<ChatClientResponse> adviseStream(ChatClientRequest chatClientRequest, StreamAdvisorChain chain) {

    return  Mono.just(chatClientRequest)
            .publishOn(Schedulers.boundedElastic())
            .map(request -> {
                // 这可以由阻塞和非阻塞线程执行。
                // Advisor 在 next 之前的处理部分
            })
            .flatMapMany(request -> chain.nextStream(request))
            .map(response -> {
                // Advisor 在 next 之后的处理部分
            });
}

4.3 最佳实践

  • 保持 Advisor 专注于特定任务,以获得更好的模块化。

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

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

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

五、重大 API 变更

5.1 Advisor 接口

  • 在 1.0 M2 中,存在独立的 RequestAdvisor 和 ResponseAdvisor 接口。

    • RequestAdvisor 在 ChatModel.call 和 ChatModel.stream 方法之前调用。

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

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

    • CallAroundAdvisor

    • StreamAroundAdvisor

    • 先前属于 ResponseAdvisor 的 StreamResponseMode 已被移除。

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

    • CallAroundAdvisor → CallAdvisor, StreamAroundAdvisor → StreamAdvisor, CallAroundAdvisorChain → CallAdvisorChain, StreamAroundAdvisorChain → StreamAdvisorChain。

    • AdvisedRequest → ChatClientRequest, AdivsedResponse → ChatClientResponse。

5.2 上下文 Map 处理

  • 在 1.0 M2 中:

    • 上下文 Map 是一个独立的方法参数。

    • Map 是可变的,并沿着链传递。

  • 在 1.0 M3 中:

    • 上下文 Map 现在是 AdvisedRequest 和 AdvisedResponse 记录的一部分。

    • Map 是不可变的。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

腾飞开源

您的支持是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值