深入浅出 Spring AI Advisor:自定义你的智能助手拦截器

前言

在构建基于 Spring AI 的智能应用时,我们经常需要在 AI 请求和响应的处理流程中插入一些额外的逻辑,比如记录日志、优化提示词、对结果进行二次处理等。Spring AI 提供了一套优雅的 Advisor(拦截器) 机制,让你可以像切面编程一样,在 AI 调用的前后加入自定义逻辑。

我将以新手友好的方式,带你理解 Advisor 的核心概念,并手把手教你实现两个实用的自定义 Advisor:一个日志记录器和一个能提升推理能力的 Re-Reading Advisor。

一、什么是 Advisor?

简单来说,Advisor 就像是一个 "拦截器" 。当你的程序调用 AI 模型时,请求会依次经过一系列 Advisor,每个 Advisor 都可以:

  • 在请求发送前修改请求(比如改写用户的问题)
  • 在响应返回后处理响应(比如记录日志、转换格式)

这种设计让你能够将横切关注点(如日志、监控、提示词优化)与核心业务逻辑分离,代码更加清晰、可维护。

二、Advisor 的两种类型

Spring AI 提供了两种 Advisor 接口,分别对应两种调用场景:

接口 适用场景 核心方法
CallAroundAdvisor 非流式请求(一次性返回完整结果) aroundCall
StreamAroundAdvisor 流式请求(数据分块返回) aroundStream

最佳实践:如果你的 Advisor 同时支持流式和非流式调用,建议同时实现两个接口。

三、自定义 Advisor 的核心要素

实现一个 Advisor 需要关注以下几点:

  1. 实现对应接口 :根据场景选择 CallAroundAdvisor 和/或 StreamAroundAdvisor
  2. 实现核心方法 :在 aroundCallaroundStream 中编写你的逻辑
  3. 设置执行顺序 :通过 getOrder() 指定优先级(值越小越先执行)
  4. 提供唯一名称 :通过 getName() 返回一个标识符

四、实战一:自定义日志 Advisor

背景

Spring AI 内置了 SimpleLoggerAdvisor,但它以 Debug 级别输出日志。在默认的 Spring Boot 项目中,Info 级别下看不到日志输出。因此,我们需要一个更精简、可自定义级别的日志记录器。

需求

  • 打印 Info 级别 日志
  • 只输出 用户提示词AI 回复的文本内容

代码实现

typescript 复制代码
package com.swl.baoaiagent.advisor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.advisor.api.*;
import org.springframework.ai.chat.model.MessageAggregator;
import reactor.core.publisher.Flux;

@Slf4j
public class MyLoggerAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

    // 请求前:打印用户提示词
    private void before(AdvisedRequest advisedRequest) {
        log.info("Ai Request: {}", advisedRequest.userText());
    }

    // 响应后:打印 AI 回复
    private void observeAfter(AdvisedResponse advisedResponse) {
        log.info("Ai Response: {}", advisedResponse.response().getResult().getOutput().getText());
    }

    @Override
    public AdvisedResponse aroundCall(AdvisedRequest advisedRequest, CallAroundAdvisorChain chain) {
        before(advisedRequest);
        AdvisedResponse advisedResponse = chain.nextAroundCall(advisedRequest);
        observeAfter(advisedResponse);
        return advisedResponse;
    }

    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        before(advisedRequest);
        Flux<AdvisedResponse> adviseResponses = chain.nextAroundStream(advisedRequest);
        // 流式场景需要聚合消息后才能打印完整内容
        return new MessageAggregator().aggregateAdvisedResponse(adviseResponses, this::observeAfter);
    }

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

    @Override
    public int getOrder() {
        return 0; // 优先级最高,最先执行
    }
}

关键点解析

  • 流式处理 :流式响应是分块返回的,直接打印会看到多次输出。我们使用 MessageAggregator 将分块数据聚合成完整消息后,再打印一次日志。
  • 执行顺序getOrder() 返回 0,表示这个日志拦截器会优先执

五、实战二:Re-Reading Advisor(提升推理能力)

背景

研究发现,让 AI 把问题再读一遍 可以显著提升推理能力。这种技术被称为 Re-Reading(Re2),虽然效果显著,但 成本会翻倍(因为prompt长度翻倍),所以面向 C 端用户时要谨慎使用。

原理

原始请求:

请解释一下量子计算的基本原理

Re2 改写后的请求:

请解释一下量子计算的基本原理

Read the question again: 请解释一下量子计算的基本原理行。

代码实现

typescript 复制代码
package com.swl.baoaiagent.advisor;

import org.springframework.ai.chat.client.advisor.api.*;
import reactor.core.publisher.Flux;

import java.util.HashMap;
import java.util.Map;

public class ReReadingAdvisor implements CallAroundAdvisor, StreamAroundAdvisor {

    // 在请求前改写 Prompt
    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) {
        advisedRequest = before(advisedRequest);
        return chain.nextAroundCall(advisedRequest);
    }

    @Override
    public Flux<AdvisedResponse> aroundStream(AdvisedRequest advisedRequest, StreamAroundAdvisorChain chain) {
        advisedRequest = before(advisedRequest);
        return chain.nextAroundStream(advisedRequest);
    }

    @Override
    public String getName() {
        return this.getClass().getSimpleName();
    }

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

关键点解析

  • Prompt 改写 :我们通过 before 方法,将原始问题和"再读一遍"的指令拼接成新的提示词。
  • 用户参数传递 :使用 userParams 保留了原始输入,方便后续追踪。

六、执行顺序的重要性

当多个 Advisor 同时存在时,getOrder() 决定了它们的执行顺序:

  • 值越小,优先级越高,越先执行
  • 日志 Advisor 通常放在最前面,以便记录原始请求
  • 业务逻辑相关的 Advisor 可以放在后面
scss 复制代码
请求 → Order=0 (日志) → Order=1 (Re2) → Order=2 (其他) → AI 模型

七、进阶技巧:在 Advisor 之间共享状态

有时我们需要在多个 Advisor 之间传递数据,可以通过 adviseContext 实现:

ini 复制代码
// 在第一个 Advisor 中存入数据
advisedRequest = advisedRequest.updateContext(context -> {
    context.put("startTime", System.currentTimeMillis());
    return context;
});

// 在后面的 Advisor 中取出数据
Object startTime = advisedResponse.adviseContext().get("startTime");

这在计算耗时、传递用户身份信息等场景中非常有用。


八、最佳实践总结

  1. 单一职责:每个 Advisor 只做一件事,避免功能混杂
  2. 注意顺序 :合理设置 getOrder(),确保依赖关系正确
  3. 双模式支持 :尽量同时实现 CallAroundAdvisorStreamAroundAdvisor
  4. 避免耗时操作:Advisor 中不要执行数据库查询、远程调用等耗时操作
  5. 优雅处理异常:确保 Advisor 不会因为异常导致整个链路中断
  6. 流式场景用 Reactor :复杂流式处理时,可以使用 Mono/Flux 操作符进行灵活编排

结语

通过本文的学习,你应该已经掌握了 Spring AI Advisor 的核心概念和开发方法。自定义 Advisor 让我们能够以非侵入的方式扩展 AI 调用的能力,无论是记录日志、优化提示词,还是实现更复杂的推理增强,都能轻松应对。

相关推荐
Riden刘1 小时前
从 Cursor 到 MySQL,MCP如何让 AI 真正“帮你干活”?
ai编程
gyx_这个杀手不太冷静2 小时前
OpenCode 深度解析:架构设计、工具链集成与工程化实践
前端·架构·ai编程
Peter·Pan爱编程2 小时前
深度解析MiniMax M2.7:当AI学会“自我进化”,以及如何通过Ollama本地体验最强Agent
人工智能·ai编程·agent skills·openclaw
MegaDataFlowers3 小时前
快速上手Spring
java·后端·spring
大傻^3 小时前
Spring AI Alibaba Function Calling:外部工具集成与业务函数注册
java·人工智能·后端·spring·springai·springaialibaba
SuniaWang3 小时前
《Spring AI + 大模型全栈实战》学习手册系列 · 专题四:《Ollama 模型管理与调优:让 AI 模型在低配服务器上流畅运行》
人工智能·学习·spring
小旭95273 小时前
Spring MVC :从入门到精通(下)
java·后端·spring·mvc
胡少侠73 小时前
RAG 向量持久化:用 ChromaDB 替换内存存储,支持 Metadata 溯源
ai·agent·rag·chromadb
大傻^4 小时前
Spring AI Alibaba 项目初始化:Maven依赖与YAML配置全解析
人工智能·spring·maven·springai·springaialibaba·评估框架