跳到主要内容

Advisors(顾问)系统

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

核心优势

  • 封装重复出现的生成式 AI 模式
  • 转换发送到大语言模型(LLM)和从 LLM 返回的数据
  • 提供跨各种模型和用例的可移植性

基本使用

你可以使用 ChatClient API 配置现有的顾问:

ChatMemory chatMemory = ...; // 初始化聊天记忆存储
VectorStore vectorStore = ...; // 初始化向量存储

var chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
MessageChatMemoryAdvisor.builder(chatMemory).build(), // 聊天记忆顾问
QuestionAnswerAdvisor.builder(vectorStore).build() // RAG 顾问
)
.build();

var conversationId = "678";

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

建议在构建时使用 defaultAdvisors() 方法注册顾问。

Advisors 也会参与可观测性栈,所以你可以看到与它们执行相关的指标和追踪。

核心组件

API 由以下组件组成:

  • CallAdvisorCallAdvisorChain 用于非流式场景
  • StreamAdvisorStreamAdvisorChain 用于流式场景
  • ChatClientRequest 表示未密封的提示请求
  • ChatClientResponse 表示聊天完成响应

两者都持有一个 advise-context 来在顾问链中共享状态。

关键方法

adviseCall()adviseStream() 是关键的顾问方法,通常执行以下操作:

  • 检查未密封的提示数据
  • 自定义和增强提示数据
  • 调用链中的下一个实体
  • 可选择阻止请求
  • 检查聊天完成响应
  • 抛出异常表示处理错误

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

执行顺序

顾问链由 Spring AI 框架创建,允许按 getOrder() 值排序的顺序调用多个顾问。值越小越先执行。最后一个顾问(自动添加)将请求发送给 LLM。

执行顺序规则

  • 值越小的顾问越先执行
  • 顾问链像栈一样工作:
    • 链中的第一个顾问最先处理请求
    • 也是最后一个处理响应
  • 要控制执行顺序:
    • 设置接近 Ordered.HIGHEST_PRECEDENCE 的顺序,确保顾问在链中首先执行(请求处理最先,响应处理最后)
    • 设置接近 Ordered.LOWEST_PRECEDENCE 的顺序,确保顾问在链中最后执行(请求处理最后,响应处理最先)

内置顾问

Spring AI 框架提供了几个内置顾问来增强你的 AI 交互:

聊天记忆顾问

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

MessageChatMemoryAdvisor

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

PromptChatMemoryAdvisor

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

VectorStoreChatMemoryAdvisor

从 VectorStore 检索记忆并添加到提示的系统文本中。这个顾问对于从大数据集中高效搜索和检索相关信息很有用。

问答顾问

QuestionAnswerAdvisor

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

RetrievalAugmentationAdvisor

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

推理顾问

ReReadingAdvisor

实现 LLM 推理的重新阅读策略,称为 RE2,以增强输入阶段的理解。

内容安全顾问

SafeGuardAdvisor

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

自定义顾问

非流式顾问

public class LoggingAdvisor implements CallAdvisor {

@Override
public String getName() { (1)
return "logging-advisor";
}

@Override
public int getOrder() { (2)
return 0;
}

@Override
public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundChain chain) {
// 请求前处理
System.out.println("请求:" + advisedRequest.userText());

// 调用下一个顾问或最终的 ChatModel
AdvisedResponse response = chain.nextAroundCall(advisedRequest);

// 响应后处理
System.out.println("响应:" + response.response().getResult().getOutput().getContent());

return response;
}
}
  1. 为顾问提供唯一名称
  2. 通过设置顺序值来控制执行顺序。值越小越先执行

流式顾问

public class StreamingLoggingAdvisor implements StreamAdvisor {

@Override
public String getName() {
return "streaming-logging-advisor";
}

@Override
public int getOrder() {
return 0;
}

@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundChain chain) {
// 请求前处理
System.out.println("流式请求:" + advisedRequest.userText());

return chain.nextAroundStream(advisedRequest)
.doOnNext(response -> {
// 每个响应块的处理
System.out.println("流式响应块:" + response.response().getResult().getOutput().getContent());
})
.doOnComplete(() -> {
// 流完成处理
System.out.println("流式响应完成");
});
}
}

Re-Reading (Re2) 顾问

"Re-Reading Improves Reasoning in Large Language Models" 文章介绍了一种称为 Re-Reading (Re2) 的技术,可以提高大语言模型的推理能力。Re2 技术需要像这样增强输入提示:

{Input_Query}
Read the question again: {Input_Query}

实现应用 Re2 技术的顾问如下:

public class ReReadingAdvisor implements CallAdvisor {

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 AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundChain chain) { (1)
String augmentedUserText = PromptTemplate.builder()
.template(this.re2AdviseTemplate)
.variables(Map.of("re2_input_query", advisedRequest.userText()))
.build()
.render();

AdvisedRequest augmentedRequest = AdvisedRequest.from(advisedRequest)
.withUserText(augmentedUserText)
.build();

return chain.nextAroundCall(augmentedRequest);
}

@Override
public String getName() {
return "re-reading-advisor";
}

@Override
public int getOrder() { (2)
return this.order;
}

public ReReadingAdvisor withOrder(int order) {
this.order = order;
return this;
}
}
  1. before 方法使用 Re-Reading 技术增强用户的输入查询
  2. 通过设置顺序值来控制执行顺序。值越小越先执行

流式 vs 非流式

执行流程对比

  • 非流式顾问:处理完整的请求和响应
  • 流式顾问:处理连续的请求和响应流,使用响应式编程概念(例如,响应用 Flux)

流式顾问实现模式

@Override
public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundChain chain) {

return Mono.just(advisedRequest)
.publishOn(Schedulers.boundedElastic())
.map(request -> {
// 这里可以使用阻塞和非阻塞线程执行
// 下一个顾问之前的处理
})
.flatMapMany(request -> chain.nextAroundStream(request))
.map(response -> {
// 下一个顾问之后的处理
});
}

最佳实践

  1. 职责单一:保持顾问专注于特定任务以提高模块化
  2. 状态共享:必要时使用 adviseContext 在顾问间共享状态
  3. 双重实现:为最大灵活性同时实现流式和非流式版本
  4. 顺序考虑:仔细考虑链中顾问的顺序以确保正确的数据流

API 变更历史

顾问接口变更

  • 1.0 M2 中,有单独的 RequestAdvisorResponseAdvisor 接口
    • RequestAdvisorChatModel.callChatModel.stream 方法之前调用
    • ResponseAdvisor 在这些方法之后调用
  • 1.0 M3 中,这些接口被替换为:
    • CallAroundAdvisor
    • StreamAroundAdvisor
  • ResponseAdvisor 中的 StreamResponseMode 已被移除
  • 1.0.0 中这些接口被替换:
    • CallAroundAdvisorCallAdvisor
    • StreamAroundAdvisorStreamAdvisor
    • CallAroundAdvisorChainCallAdvisorChain
    • StreamAroundAdvisorChainStreamAdvisorChain
    • AdvisedRequestChatClientRequest
    • AdvisedResponseChatClientResponse

上下文映射处理

  • 1.0 M2 中:
    • 上下文映射是单独的方法参数
    • 映射是可变的并沿链传递
  • 1.0 M3 中:
    • 上下文映射现在是 AdvisedRequestAdvisedResponse 记录的一部分
    • 映射是不可变的
    • 要更新上下文,使用 updateContext 方法,它使用更新的内容创建新的不可修改映射