顾问
Spring AI 顾问 API 为您的 Spring 应用程序提供了一种灵活而强大的方式来拦截、修改和增强 AI 驱动的交互。通过利用顾问 API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。
顾问 API
Spring AI 顾问 API 为您的 Spring 应用程序提供了一种灵活而强大的方式来拦截、修改和增强 AI 驱动的交互。通过利用顾问 API,开发人员可以创建更复杂、可重用和可维护的 AI 组件。
主要优势包括封装重复出现的生成式 AI 模式、转换发送到大型语言模型(LLM)和从其接收的数据,以及在各种模型和用例之间提供可移植性。
您可以使用 ChatClient API 配置现有的顾问,如下例所示:
var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
new MessageChatMemoryAdvisor(chatMemory), // chat-memory advisor
new QuestionAnswerAdvisor(vectorStore) // 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();
建议在构建时使用 builder 的 defaultAdvisors()
方法注册顾问。
顾问也参与可观察性堆栈,因此您可以查看与其执行相关的指标和跟踪。
核心组件
该 API 包含用于非流式场景的 CallAroundAdvisor
和 CallAroundAdvisorChain
,以及用于流式场景的 StreamAroundAdvisor
和 StreamAroundAdvisorChain
。它还包括 AdvisedRequest
来表示未密封的 Prompt 请求,AdvisedResponse
用于聊天完成响应。两者都持有一个 advise-context
来在顾问链中共享状态。
nextAroundCall()
和 nextAroundStream()
是关键的顾问方法,通常执行以下操作:
- 检查未密封的 Prompt 数据
- 自定义和增强 Prompt 数据
- 调用顾问链中的下一个实体
- 可选地阻止请求
- 检查聊天完成响应
- 抛出异常以指示处理错误
此外,getOrder()
方法确定链中的顾问顺序,而 getName()
提供唯一的顾问名称。
顾问链由 Spring AI 框架创建,允许按照 getOrder()
值的顺序依次调用多个顾问。较低的值先执行。最后一个自动添加的顾问将请求发送到 LLM。
以下流程图展示了顾问链与 Chat Model 之间的交互:
- Spring AI 框架从用户的
AdvisedRequest
中创建一个Prompt
,并生成一个空的AdvisorContext
对象。 - 每个链中的顾问都会处理请求,可能会修改它。或者,它可以选择阻止请求,不调用下一个实体。在后一种情况下,顾问负责填写响应。
- 最终的顾问,由框架提供,将请求发送到 Chat Model 。
- Chat Model 的响应然后通过顾问链传递并转换成
AdvisedResponse
。后来包含共享的AdvisorContext
实例。 - 每个顾问都可以处理或修改回复。
- 最终
AdvisedResponse
通过提取ChatCompletion
返回给客户端。
顾问顺序
链中顾问的执行顺序由 getOrder()
方法决定。需要理解的要点:
- 具有较低顺序值的顾问先执行。
- 顾问链作为堆栈运行:
- 链中的第一个顾问是第一个处理请求的。
- 它也是最后一个处理响应的。
- 要控制执行顺序:
- 将顺序设置为接近
Ordered.HIGHEST_PRECEDENCE
以确保顾问在链中首先执行(对于请求处理是第一个,对于响应处理是最后一个)。 - 将顺序设置为接近
Ordered.LOWEST_PRECEDENCE
以确保顾问在链中最后执行(对于请求处理是最后一个,对于响应处理是第一个)。
- 将顺序设置为接近
- 较高的值被解释为较低的优先级。
- 如果多个顾问具有相同的顺序值,则它们的执行顺序不能保证。
顺序和执行序列之间的表面矛盾是由于顾问链的堆栈性质造成的:
- 具有最高优先级(最低顺序值)的顾问被添加到堆栈顶部。
- 当堆栈展开时,它将是第一个处理请求的。
- 当堆栈重新卷起时,它将是最后一个处理响应的。
对于需要在输入和输出两端都排在第一位的用例: 为每一端使用单独的顾问。用不同的顺序值配置它们。使用顾问上下文在它们之间共享状态。
作为提醒,以下是 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();
}
对于需要在输入和输出两侧都作为链中的第一个用例:
- 为每一方使用单独的顾问。
- 用不同的顺序值配置它们。
- 使用顾问上下文在它们之间共享状态。
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 {
/**
* 环绕建议,包装 ChatModel#stream(Prompt) 方法。
* @param advisedRequest 建议的请求
* @param chain 顾问链
* @return 响应
*/
AdvisedResponse aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain);
}
要在 Advice 实现中继续链式调用,请使用 CallAroundAdvisorChain
和 StreamAroundAdvisorChain
:
接口是:
public interface CallAroundAdvisorChain {
AdvisedResponse nextAroundCall(AdvisedRequest advisedRequest);
}
和
public interface StreamAroundAdvisorChain {
AdvisedResponse nextAroundStream(AdvisedRequest advisedRequest);
}
实现顾问
要创建顾问,需要实现 CallAroundAdvisor
或 StreamAroundAdvisor
(或两者)。要实现的关键方法是非流式的 nextAroundCall()
或流式的 nextAroundStream()
。
示例
我们将提供几个实践示例来说明如何实现用于观察和增强用例的顾问。
日志顾问
我们可以实现一个简单的日志顾问,在调用链中的下一个顾问之前记录 AdvisedRequest
,之后记录 AdvisedResponse
。请注意,顾问只观察请求和响应,不修改它们。此实现支持流式和非流式场景。
public class SimpleLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
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 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));
}
}
重读(Re2)顾问
《重读提高大型语言模型推理能力》文章介绍了一种称为重读(Re2)的技术,可以提高大型语言模型的推理能力。Re2 技术需要增强输入提示。
{Input_Query}
Read the question again: {Input_Query}
实现一个顾问,将 Re2 技术应用于用户的输入查询,可以这样做:
public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {
private AdvisedRequest before(AdvisedRequest advisedRequest) {
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) {
return chain.nextAroundCall(this.before(advisedRequest));
}
@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
return chain.nextAroundStream(this.before(advisedRequest));
}
@Override
public int getOrder() {
return 0;
}
@Override
public String getName() {
return this.getClass().getSimpleName();
}
}
Spring AI 内置顾问
Spring AI 框架提供了几个内置顾问来增强您的 AI 交互。以下是可用顾问的概述:
聊天记忆顾问
这些顾问在聊天记忆存储中管理对话历史:
MessageChatMemoryAdvisor
检索记忆并将其作为消息集合添加到提示中。这种方法保持了对话历史的结构。注意,并非所有 AI 模型都支持这种方法。PromptChatMemoryAdvisor
检索记忆并将其合并到提示的系统文本中。VectorStoreChatMemoryAdvisor
从向量存储中检索记忆并将其添加到提示的系统文本中。这个顾问对于高效搜索和检索大型数据集中的相关信息很有用。
问答顾问
QuestionAnswerAdvisor
这个顾问使用向量存储来提供问答功能,实现 RAG(检索增强生成)模式。
内容安全顾问
SafeGuardAdvisor
一个简单的顾问,旨在防止模型生成有害或不当的内容。
流式与非流式
- 非流式顾问处理完整的请求和响应。
- 流式顾问使用响应式编程概念(例如,用于响应的 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 类已移至新包。虽然 RequestResponseAdvisor 接口仍然可用,但它已被标记为已弃用,将在 M3 版本左右删除。建议在新实现中使用新的 CallAroundAdvisor 和 StreamAroundAdvisor 接口。
重大 API 更改
Spring AI 顾问链从 1.0 M2 版本到 1.0 M3 版本经历了重大变化。以下是主要修改:
顾问接口
- 在 1.0 M2 中,有单独的
RequestAdvisor
和ResponseAdvisor
接口。RequestAdvisor
在ChatModel.call
和ChatModel.stream
方法之前调用。ResponseAdvisor
在这些方法之后调用。
- 在 1.0 M3 中,这些接口已被替换为:
CallAroundAdvisor
StreamAroundAdvisor
- 之前属于
ResponseAdvisor
的StreamResponseMode
已被移除。
上下文映射处理
- 在 1.0 M2 中:
- 上下文映射是一个单独的方法参数。
- 映射是可变的并沿链传递。
- 在 1.0 M3 中:
- 上下文映射现在是
AdvisedRequest
和AdvisedResponse
记录的一部分。 - 映射是不可变的。
- 要更新上下文,请使用
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...
}