Advisor API

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springdoc.cn 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

Spring AI Advisor API 为拦截、修改和增强 Spring 应用中的 AI 交互提供了灵活强大的方式。通过该 API,开发者能构建更复杂、可复用且易维护的 AI 组件。

核心优势包括:封装可复用的生成式 AI 模式、转换与大语言模型(LLM)交互的数据、实现跨模型与用例的可移植性。

如以下示例所示,可通过 ChatClient API 配置现有 Advisor:

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
			MessageChatMemoryAdvisor.builder(chatMemory).build(), // chat-memory advisor
			QuestionAnswerAdvisor.builder((vectorStore).builder() // 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() 方法注册顾问。

Advisor 组件同时接入可观测性体系,支持查看其执行相关的指标与追踪数据。

核心组件

API 包括用于非流式场景的 CallAroundAdvisorCallAroundAdvisorChain,以及用于流式场景的 StreamAroundAdvisorStreamAroundAdvisorChain。它还包括代表未密封(unsealed)提示请求的 AdvisedRequest 和代表聊天完成响应的 AdvisedResponse。两者都持有一个 advise-context,以便在 Advisor 链中共享状态。

Advisors API 类

核心 Advisor 方法 nextAroundCall()nextAroundStream() 主要执行以下操作:检查未密封的 Prompt 数据、定制与增强 Prompt 内容、调用 Advisor 链中的下一实体、选择性阻断请求、分析聊天完成响应,并通过抛出异常标识处理错误。

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

Spring AI 框架构建的 Advisor Chain 支持按 getOrder() 值排序依次调用多个 Advisor — 数值越小优先级越高。链末自动加入的最终顾问会将请求发送至大语言模型(LLM)。

以下流程图展示了 Advisor 链与聊天模型的交互过程:

Advisors API Flow
  1. Spring AI 框架将用户的 Prompt 封装为 AdvisedRequest 对象,并创建空的 AdvisorContext 上下文。

  2. 链中每个 Advisor 依次处理请求并可进行修改,也可选择阻断请求(不调用下一实体)。若选择阻断,该 Advisor 需负责填充响应内容。

  3. 框架提供的最终 Advisor 将请求发送至聊天模型。

  4. 聊天模型的响应会逆向传回 Advisor 链,被转换为包含共享 AdvisorContext 实例的 AdvisedResponse 对象。

  5. 每个 Advisor 均可处理或修改该响应。

  6. 通过提取 ChatCompletion 内容,最终生成的 AdvisedResponse 将返回给客户端。

Advisor 顺序

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

  • 数值越小的 Advisor 越优先执行。

  • Advisor 链采用栈式结构运作:

    • 链首 Advisor 最先处理请求。

    • 同时也是最后处理响应的环节。

  • 控制执行顺序:

    • order 值设为接近 Ordered.HIGHEST_PRECEDENCE 可确保 Advisor 优先执行(请求处理时最先触发,响应处理时最后触发)。

    • order 值设为接近 Ordered.LOWEST_PRECEDENCE 可确保 Advisor 最后执行(请求处理时最后触发,响应处理时最先触发)。

  • 数值越大表示优先级越低。

  • 若多个 Advisor 的 order 值相同,其执行顺序无法保证。

执行顺序与数值看似矛盾的现象源于 Advisor 链的栈式特性:

  • 优先级最高(order 值最小)的 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>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. 通过配置不同的 order 值实现。

  3. 利用 advisor context 在它们之间共享状态。

API 概览

核心 Advisor 接口位于 org.springframework.ai.chat.client.advisor.api 包中。开发自定义 Advisor 时需关注以下关键接口:

public interface Advisor extends Ordered {

	String getName();

}

同步式与响应式 Advisor 的两个子接口分别为:

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

}

要在 Advisor 实现中延续 Advisor 链,需使用 CallAroundAdvisorChainStreamAroundAdvisorChain

接口是:

public interface CallAroundAdvisorChain {

	AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);

}

以及:

public interface StreamAroundAdvisorChain {

	Flux<AdvisedResponse> nextAroundStream(AdvisedRequest advisedRequest);

}

实现 Advisor

创建 Advisor 需实现 CallAroundAdvisorStreamAroundAdvisor(或两者)。核心实现方法是:非流式用 nextAroundCall() ,流式用 nextAroundStream()

示例

通过具体示例演示如何实现观察型与增强型 Advisor。

日志 Advisor

我们可以实现一个简单的日志 Advisor,在调用链中下一 Advisor 前后,分别记录 AdvisedRequestAdvisedResponse。该实现仅观察请求与响应而不修改,同时支持非流式与流式场景。

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 为 Advisor 提供一个唯一的名称。
2 你可以通过设置 order 值来控制执行顺序。较低的值优先执行。
3 MessageAggregator 是工具类,用于将 Flux 响应聚合为单个 AdvisedResponse。适用于需观察完整响应(而非流中单项)的日志记录等处理场景。注意:此为只读操作,不可修改响应内容。

重读(Re2)Advisor

论文 重读可提高大型语言模型的推理能力 提出的 Re-Reading(Re2)技术,通过以下方式增强输入提示词来优化模型推理表现:

{Input_Query}
Read the question again: {Input_Query}

实现应用 Re2 技术的 Advisor 可按如下方式处理用户输入查询:

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 通过设置 order 值控制执行顺序 — 数值越小优先级越高。
5 为 Advisor 提供唯一标识名称。

Spring AI 内置 Advisor

Spring AI 框架提供了多个内置 Advisor,以增强你的 AI 交互。以下是可用 Advisor 的概述:

Chat Memory Advisor

这些 Advisor 在聊天记忆库中管理对话历史:

  • MessageChatMemoryAdvisor

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

  • PromptChatMemoryAdvisor

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

  • VectorStoreChatMemoryAdvisor

    从向量存储库检索记忆内容并注入提示词的系统文本。该 Advisor 能高效搜索海量数据集中的相关信息。

问答(QA) Advisor
  • QuestionAnswerAdvisor

    该 Advisor 通过向量存储实现问答功能,采用检索增强生成(RAG)模式。

内容安全 Advisor
  • SafeGuardAdvisor

    基础防护型 Advisor,用于阻止模型生成有害或不恰当内容。

流式与非流式对比

流式与非流式对比
  • 非流式 Advisor 处理完整请求与响应。

  • 流式 Advisor 采用响应式编程理念(如 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. 保持 Advisor 功能单一化以提升模块性。

  2. 必要时通过 adviseContext 在 Advisor 间共享状态。

  3. 同时实现流式与非流式版本以获得最佳灵活性。

  4. 谨慎规划 Advisor 链顺序以确保数据流正确。

向后兼容性

AdvisedRequest 类已迁移至新包。

重大 API 变更

Spring AI Advisor 链从 1.0 M21.0 M3 版本发生重大变更,主要调整如下:

Advisor 接口

  • 1.0 M2 版本中曾存在独立的 RequestAdvisorResponseAdvisor 接口。

    • RequestAdvisorChatModel.callChatModel.stream 方法前触发。

    • ResponseAdvisor 则在上述方法执行后调用。

  • 1.0 M3 版本中这些接口已被替换为:

    • CallAroundAdvisor

    • StreamAroundAdvisor

  • 原属 ResponseAdvisorStreamResponseMode 已被移除。

上下文 Map 处理

  • 在 1.0 M2 中:

    • 上下文 Map 曾作为独立的方法参数存在。

    • 该 Map 为可变对象并在链中传递。

  • 在 1.0 M3 中:

    • 上下文 Map 现已整合至 AdvisedRequestAdvisedResponse 记录中。

    • Map 为不可变对象。

    • 需通过 updateContext 方法更新上下文 - 该方法会生成包含更新内容的新不可变 Map。

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