3.3 Spring AI Advisors API

Spring AI Advisors API

概述

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

关键优势包括封装重复的生成AI模式,转换发送给大语言模型(LLMs)和从大语言模型接收的数据,以及跨各种模型和用例提供可移植性。

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

java 复制代码
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();

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

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

核心组件

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

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

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

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

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

  1. Spring AI框架从用户的Prompt创建一个ChatClientRequest,以及一个空的advisor context对象。

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

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

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

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

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

Advisor顺序

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

  • 具有较低顺序值的advisors首先执行。
  • advisor链作为堆栈操作:
    • 链中的第一个advisor是第一个处理请求的。
    • 它也是最后一个处理响应的。
  • 要控制执行顺序:
    • 将顺序设置为接近Ordered.HIGHEST_PRECEDENCE以确保advisor在链中首先执行(请求处理最先,响应处理最后)。
    • 将顺序设置为接近Ordered.LOWEST_PRECEDENCE以确保advisor在链中最后执行(请求处理最后,响应处理最先)。
  • 较高的值被解释为较低的优先级。
  • 如果多个advisors具有相同的顺序值,它们的执行顺序不保证。

注意: 顺序和执行序列之间的明显矛盾是由于advisor链的堆栈特性:

  • 具有最高优先级(最低顺序值)的advisor被添加到堆栈的顶部。
  • 在堆栈展开时,它将是第一个处理请求的。
  • 在堆栈回绕时,它将是最后一个处理响应的。

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

java 复制代码
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. 为每一侧使用单独的advisors。
  2. 使用不同的顺序值配置它们。
  3. 使用advisor上下文在它们之间共享状态。

API概述

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

java 复制代码
public interface Advisor extends Ordered {

	String getName();

}

同步和响应式Advisors的两个子接口是:

java 复制代码
public interface CallAdvisor extends Advisor {

	ChatClientResponse adviseCall(
		ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);

}

java 复制代码
public interface StreamAdvisor extends Advisor {

	Flux<ChatClientResponse> adviseStream(
		ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);

}

要继续Advice链,在您的Advice实现中使用CallAdvisorChainStreamAdvisorChain

接口是:

java 复制代码
public interface CallAdvisorChain extends AdvisorChain {

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

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

}

java 复制代码
public interface StreamAdvisorChain extends AdvisorChain {

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

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

}

实现Advisor

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

示例

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

日志记录Advisor

我们可以实现一个简单的日志记录advisor,在调用链中的下一个advisor之前记录ChatClientRequest,之后记录ChatClientResponse。注意advisor只观察请求和响应,不修改它们。此实现支持非流式和流式场景。

java 复制代码
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技术需要像这样增强输入提示:

css 复制代码
{Input_Query}
Read the question again: {Input_Query}

实现将Re2技术应用于用户输入查询的advisor可以这样做:

java 复制代码
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内置Advisors

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

聊天记忆Advisors

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

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

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

  • VectorStoreChatMemoryAdvisor

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

问答Advisor
  • QuestionAnswerAdvisor

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

  • RetrievalAugmentationAdvisor

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

推理Advisor
内容安全Advisor
  • SafeGuardAdvisor

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

流式与非流式

  • 非流式advisors处理完整的请求和响应。
  • 流式advisors将请求和响应作为连续流处理,使用响应式编程概念(例如,响应使用Flux)。
java 复制代码
@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部分之后
            });
}

最佳实践

  1. 保持advisors专注于特定任务以获得更好的模块化。
  2. 必要时使用adviseContext在advisors之间共享状态。
  3. 为最大灵活性实现advisor的流式和非流式版本。
  4. 仔细考虑链中advisors的顺序以确保正确的数据流。

破坏性API更改

Advisor接口

  • 在1.0 M2中,有单独的RequestAdvisorResponseAdvisor接口。
    • RequestAdvisorChatModel.callChatModel.stream方法之前调用。
    • ResponseAdvisor在这些方法之后调用。
  • 在1.0 M3中,这些接口已被替换为:
    • CallAroundAdvisor
    • StreamAroundAdvisor
  • 以前是ResponseAdvisor一部分的StreamResponseMode已被删除。
  • 在1.0.0中,这些接口已被替换:
    • CallAroundAdvisorCallAdvisorStreamAroundAdvisorStreamAdvisorCallAroundAdvisorChainCallAdvisorChainStreamAroundAdvisorChainStreamAdvisorChain
    • AdvisedRequestChatClientRequestAdvisedResponseChatClientResponse

上下文映射处理

  • 在1.0 M2中:
    • 上下文映射是单独的方法参数。
    • 映射是可变的并沿链传递。
  • 在1.0 M3中:
    • 上下文映射现在是AdvisedRequestAdvisedResponse记录的一部分。
    • 映射是不可变的。
    • 要更新上下文,使用updateContext方法,该方法创建一个具有更新内容的新不可修改映射。
相关推荐
爱吃程序猿的喵2 小时前
Spring Boot 常用注解全面解析:提升开发效率的利器
java·spring boot·后端
zyb_1234562 小时前
NestJS 集成 RabbitMQ(CloudAMQP)实战指南
后端
hzbigdog2 小时前
php的CSV大数据导入导出的通用处理类
大数据·后端·php
WZTTMoon2 小时前
从 “完整对象” 视角看Spring 循环依赖
java·spring boot·后端·spring
间彧3 小时前
如何在CI/CD流水线中自动化实现镜像扫描和推送到Harbor?
后端
9ilk3 小时前
【基于one-loop-per-thread的高并发服务器】--- 自主实现HttpServer
linux·运维·服务器·c++·笔记·后端
间彧3 小时前
Kubernetes无缝集成Harbor,实现CI/CD流水线
后端
会编程的吕洞宾3 小时前
Java中的“万物皆对象”:一场编程界的哲学革命
java·后端
会编程的吕洞宾4 小时前
Java封装:修仙界的"护体罡气"
java·后端