Spring AI Advisors:从链式增强到递归顾问

Spring AI Advisors:从链式增强到递归顾问

一、Advisors API 在做什么

Advisor 所对应的 Advisors API ,提供了一种灵活而强大的方式来拦截、修改和增强 Spring 应用程序中 AI 驱动的交互

当使用用户文本调用 AI 模型时,常见的模式是用上下文数据附加或增强提示。此上下文数据可以是不同类型,常见类型包括:

  • 你自己的数据:这是 AI 模型尚未训练的数据。即使模型看到过类似数据,附加的上下文数据在生成响应时也具有优先权。
  • 对话历史记录 :聊天模型的 API 是无状态的。若你在某次交互中告诉模型你的名字,它在后续交互中不会自动记住;每次请求都需要发送对话历史,才能在生成响应时考虑先前的交互。

借助 Advisors API,开发者可以封装重复的生成式 AI 模式转换 发往大型语言模型(LLM)及从其接收的数据,并在不同模型与用例之间提供可移植性


二、在 ChatClient 里配置 Advisor:AdvisorSpec

ChatClient 的流畅 API 提供 AdvisorSpec 接口,用于配置 Advisor。该接口支持:添加参数、一次设置多个参数、向链中加入一个或多个 Advisor。

java 复制代码
interface AdvisorSpec {
    AdvisorSpec param(String k, Object v);
    AdvisorSpec params(Map<String, Object> p);
    AdvisorSpec advisors(Advisor... advisors);
    AdvisorSpec advisors(List<Advisor> advisors);
}

重要约定:Advisor 在链中的添加顺序会影响执行逻辑;每个 Advisor 都会以某种方式修改提示或上下文,前一个 Advisor 的变更会传递给链中的下一个。

2.1 运行时注册示例(记忆 + RAG)

java 复制代码
ChatClient.builder(chatModel)
    .build()
    .prompt()
    .advisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build()
    )
    .user(userText)
    .call()
    .content();

在此配置中,MessageChatMemoryAdvisor 会先执行 ,将对话历史加入提示;随后 QuestionAnswerAdvisor 会基于用户问题以及已加入的对话历史执行检索,从而可能得到更相关的结果。

2.2 关于执行顺序的补充说明(材料中的两种视角)

  • 视角 A(advisors() 注册与 order)

    • .advisors(A, B, C) 传入的顺序即注册顺序
    • 最终执行顺序 :先按 order 升序(小 → 大)排序。
    • 若两个 Advisor 的 order 相同 ,则按你传入的顺序执行。
    • 一句话order 优先;仅当 order 相同时,才看传入顺序。
  • 内置 Advisor 的默认 order

    • Spring AI 官方内置 Advisor 的默认 order 全部为 0
    • 因此 MessageChatMemoryAdvisorQuestionAnswerAdvisor 默认均为 order = 0 ,执行顺序为:MessageChatMemoryAdvisor,后 QuestionAnswerAdvisor(与传入顺序一致)。

2.3 构建期默认注册与运行时参数

建议在构建时使用构建器的 defaultAdvisors() 注册 Advisor;运行时可通过 advisors(advisor -> advisor.param(...)) 传入参数。

java 复制代码
ChatMemory chatMemory = ... // 初始化聊天记忆
VectorStore vectorStore = ... // 初始化向量存储

var chatClient = ChatClient.builder(chatModel)
    .defaultAdvisors(
        MessageChatMemoryAdvisor.builder(chatMemory).build(),
        QuestionAnswerAdvisor.builder(vectorStore).build()
    )
    .build();

var conversationId = "678";

String response = this.chatClient.prompt()
    .advisors(advisor -> advisor.param(ChatMemory.CONVERSATION_ID, conversationId))
    .user(userText)
    .call()
    .content();

三、与检索、日志相关的内置能力(节选)

3.1 问答 Advisor 与检索增强生成

QuestionAnswerAdvisor 使用向量存储提供问答能力,实现 Naive RAG(检索增强生成) 模式。更完整的 RAG 流程还可参阅官方Spring AI中的 检索增强生成 相关指南;另有 RetrievalAugmentationAdvisor ,基于 org.springframework.ai.rag 中的构建块与模块化 RAG 架构实现常见 RAG 流。

3.2 日志:SimpleLoggerAdvisor

SimpleLoggerAdvisor 会记录 ChatClientrequestresponse 数据,便于调试与监控 AI 交互。

在创建 ChatClient 或单次 prompt 时将其加入 Advisor 链;建议放在链的末尾

java 复制代码
ChatResponse response = ChatClient.create(chatModel).prompt()
    .advisors(new SimpleLoggerAdvisor())
    .user("Tell me a joke?")
    .call()
    .chatResponse();

要看到日志,需将 Advisor 包的日志级别设为 DEBUG (写入 application.propertiesapplication.yaml):

properties 复制代码
logging.level.org.springframework.ai.chat.client.advisor=DEBUG

可通过构造函数自定义从 AdvisedRequestChatResponse 中记录的内容,并指定 order

java 复制代码
SimpleLoggerAdvisor(
    Function<ChatClientRequest, String> requestToString,
    Function<ChatResponse, String> responseToString,
    int order
)

示例:

java 复制代码
SimpleLoggerAdvisor customLogger = new SimpleLoggerAdvisor(
    request -> "Custom request: " + request.prompt().getUserMessage(),
    response -> "Custom response: " + response.getResult(),
    0
);

注意:生产环境需谨慎记录敏感信息。


四、BaseAdvisor:同步与流式的统一模板

BaseAdvisor 是 Spring AI 中统一同步 / 流式 调用的模板化增强接口,用于减少重复代码,使自定义增强逻辑(如提示词改写、日志、安全校验)同时适配 call()stream()

4.1 设计要点

  • 顶层关系BaseAdvisor 继承 CallAdvisorStreamAdvisor (或旧版 CallAroundAdvisor / StreamAroundAdvisor),作为双模式增强的统一入口。
  • 模板方法 :通过 default 方法封装通用流程(before → 调用链 → after ),开发者主要实现 before / after,无需分别手写同步与流式的样板代码。
  • 职责 :拦截并增强请求/响应;请求阶段可做改写、注入上下文等;响应阶段可做解析、过滤、日志等;并支持链式执行与 getOrder() 顺序控制。

4.2 核心方法(简化理解)

java 复制代码
public interface BaseAdvisor extends CallAdvisor, StreamAdvisor {

    @Override
    default ChatClientResponse adviseCall(ChatClientRequest request, CallAdvisorChain chain) {
        ChatClientRequest processed = before(request, chain);
        ChatClientResponse response = chain.nextCall(processed);
        return after(response, chain);
    }

    @Override
    default Flux<ChatClientResponse> adviseStream(ChatClientRequest request, StreamAdvisorChain chain) {
        return Mono.just(request)
            .map(req -> before(req, chain))
            .flatMapMany(chain::nextStream)
            .map(res -> after(res, chain));
    }

    ChatClientRequest before(ChatClientRequest request, AdvisorChainContext context);

    ChatClientResponse after(ChatClientResponse response, AdvisorChainContext context);

    int getOrder(); // 值越小优先级越高,默认 0
}
  • before:请求发往模型前执行。
  • after:模型返回响应后执行。
  • getOrder :多个 Advisor 时的执行顺序,值越小越先执行

五、核心类型、链路与整体数据流

5.1 核心组件

  • 非流式CallAdvisorCallAdvisorChain
  • 流式StreamAdvisorStreamAdvisorChain
  • 请求 / 响应ChatClientRequest (未封装的 Prompt 请求)、ChatClientResponse(聊天完成响应)。
  • 二者均包含 advise-context ,用于在 Advisor 链中共享状态

adviseCall()adviseStream() 是关键方法,典型工作包括:检查 Prompt 数据、自定义与增强 Prompt、调用链中下一节点、可选地阻止请求、检查聊天完成响应、在错误时抛异常等。

此外,getOrder() 决定链中顺序,getName() 提供唯一名称。

5.2 框架中的请求---响应路径

  1. 框架根据用户 Prompt 与一个空的 Advisor context 创建 ChatClientRequest
  2. 链上每个 Advisor 处理请求,可能修改 ;也可不调用下一节点从而阻止请求(此时由该 Advisor 负责填充响应)。
  3. 框架提供的最后一个 Advisor 将请求发往 Chat Model
  4. 模型响应沿链回传 ,并转换为 ChatClientResponse,其中包含共享的 Advisor context。
  5. 每个 Advisor 仍可处理或修改响应。
  6. 最终提取 ChatCompletion ,将 ChatClientResponse 返回给客户端。

5.3 getOrder() 与「堆栈」语义

  • order 值较小的 Advisor 会先执行
  • Advisor 链被描述为以堆栈 方式工作:
    • 链中第一个 Advisor 是第一个处理请求的,也是最后一个处理响应的。
  • 若希望某 Advisor 在输入与输出两端 都处在链的「首位」类语义,材料建议:为输入端与输出端分别使用不同 Advisor ,用不同 order 配置,并通过 Advisor 上下文共享状态。

与 Spring Ordered 接口一致:

  • Ordered.HIGHEST_PRECEDENCE:更小数值,更高优先级。
  • Ordered.LOWEST_PRECEDENCE:更大数值,更低优先级。
  • 较高 order 值表示较低优先级
java 复制代码
public interface Ordered {
    int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;
    int LOWEST_PRECEDENCE = Integer.MAX_VALUE;
    int getOrder();
}

材料中强调:优先级最高(order 值最低)的 Advisor 相当于在堆栈顶部 ;展开时最先处理请求,回卷时最后处理响应------这与「谁先谁后」的直观列表顺序容易混淆,需结合 order堆栈式回卷一起理解。


六、主要接口位置与如何实现 Advisor

主要接口位于包 org.springframework.ai.chat.client.advisor.api

java 复制代码
public interface Advisor extends Ordered {
    String getName();
}

同步与响应式子接口:

java 复制代码
public interface CallAdvisor extends Advisor {
    ChatClientResponse adviseCall(
        ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain);
}

public interface StreamAdvisor extends Advisor {
    Flux<ChatClientResponse> adviseStream(
        ChatClientRequest chatClientRequest, StreamAdvisorChain streamAdvisorChain);
}

要继续 Advice 链,在实现中调用:

java 复制代码
public interface CallAdvisorChain extends AdvisorChain {
    ChatClientResponse nextCall(ChatClientRequest chatClientRequest);
    List<CallAdvisor> getCallAdvisors();
}

public interface StreamAdvisorChain extends AdvisorChain {
    Flux<ChatClientResponse> nextStream(ChatClientRequest chatClientRequest);
    List<StreamAdvisor> getStreamAdvisors();
}

实现时:非流式实现 adviseCall ,流式实现 adviseStream(或同时实现两者)。


七、实践示例:日志 Advisor 与 Re2 Advisor

7.1 简单日志 Advisor(观察而不修改)

下列实现在调用下一 Advisor 之前 记录 ChatClientRequest,在之后 记录 ChatClientResponse不修改请求与响应;同时覆盖非流式与流式。

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

说明摘要:

  • getName():提供唯一名称。
  • getOrder():控制顺序,值越低越早。
  • MessageAggregator :将 Flux 响应聚合为单个 ChatClientResponse,便于整体记录或观察;材料注明其为只读,不能在聚合器中改响应。

7.2 重读(Re2)Advisor

《重读提高大型语言模型的推理能力》一文中的 Re2 技术,需要将用户查询增强为类似形式:

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

基于 BaseAdvisor 的示例实现要点:

  • before 中用模板把用户消息改写为上述结构。
  • after 中可直接返回原响应。
  • 通过 withOrder(int order) 或字段设置 getOrder()

(完整代码见给定材料中的 ReReadingAdvisor 类。)


八、Spring AI 内置 Advisor 概览

聊天记忆类

  • MessageChatMemoryAdvisor :从记忆检索并作为消息集合加入 Prompt;维护对话结构;并非所有模型都支持。
  • PromptChatMemoryAdvisor :检索记忆并合并到 系统文本
  • VectorStoreChatMemoryAdvisor :从 VectorStore 检索记忆并写入系统文本,适合大规模检索。

问答 / RAG

  • QuestionAnswerAdvisor:基于向量存储的 Naive RAG。
  • RetrievalAugmentationAdvisor :基于 org.springframework.ai.rag 与模块化 RAG 架构的常见 RAG 流。

推理

  • ReReadingAdvisor :实现 RE2 重读策略以加强输入阶段理解(参见原文 arxiv 论文链接)。

内容安全

  • SafeGuardAdvisor:用于降低有害或不适当内容生成风险。
相关推荐
敖正炀4 小时前
ReentrantReadWriteLock、ReentrantLock、synchronized 对比
java
37手游后端团队4 小时前
Claude Code 指南:终端 AI 编程助手的正确打开方式
人工智能·后端
cike_y4 小时前
Java反序列化漏洞-Shiro721流程分析
java·反序列化·shiro框架
极创信息4 小时前
信创系统认证服务怎么做?从适配到验收全流程指南
java·大数据·运维·tomcat·健康医疗
格鸰爱童话4 小时前
向AI学习项目技能(六)
java·人工智能·spring boot·python·学习
H_老邪4 小时前
spring boot 学习之路-1.0
spring boot·后端·学习
白宇横流学长4 小时前
停车场管理系统的设计与实现
java
Flittly4 小时前
【SpringAIAlibaba新手村系列】(18)Agent 智能体与今日菜单应用
java·spring boot·agent
树獭叔叔5 小时前
Claude Code Skill 系统:懒加载的 Agent 行动说明
后端·aigc·openai