第9集:Spring AI Advisor链:像Spring MVC拦截器一样优雅

上集回顾:老张通过MCP协议接入了企业所有系统,工具调用变得简单了。但老李(总监)提出了新要求:"代码太乱了,检索、记忆、安全校验都堆在一起,不好维护。"


一、故事继续:代码混乱的代价

周三下午,老张的客服系统代码:

java 复制代码
@Service
public class CustomerServiceAgent {
    
    // 1. 检索知识库
    List<Document> docs = vectorStore.similaritySearch(
        SearchRequest.builder().query(query).topK(5).build()
    );
    
    // 2. 获取对话历史(最近10条)
    List<Message> history = chatMemory.get(userId, 10);
    
    // 3. 安全检查
    if (isSensitive(query)) {
        return "这个问题我不能回答";
    }
    
    // 4. 调用工具
    String toolResult = orderTools.queryOrder(orderId);
    
    // 5. 构建Prompt
    String prompt = buildPrompt(history, docs, toolResult, query);
    
    // 6. 调用AI
    String answer = chatClient.prompt().user(prompt).call().content();
    
    // 7. 保存历史(逐条 add)
    chatMemory.add(userId, new UserMessage(query));
    chatMemory.add(userId, new AssistantMessage(answer));
    
    return answer;
}

老李的批评:"小张啊,这代码能跑,但维护起来太痛苦了。换个向量库、改个安全策略,都要动这里。"


二、技术引入:什么是Advisor?

2.1 Advisor的本质

Advisor = 对请求/响应的增强器

复制代码
原始请求 → [Advisor1] → [Advisor2] → [Advisor3] → AI → [Advisor3] → [Advisor2] → [Advisor1] → 响应

老张的类比:"就像Spring MVC的拦截器链,每个Advisor做一件事,组合起来就是完整功能。"

2.2 Advisor vs 手写代码

方式 优点 缺点
手写逻辑 灵活 耦合严重、难测试
Advisor链 解耦、可复用、易测试 需要理解链式执行

三、内置Advisor一览

3.1 Spring AI内置Advisor

Advisor 作用 类比
QuestionAnswerAdvisor RAG检索增强 给AI一本参考书
MessageChatMemoryAdvisor 会话记忆 给AI一个记事本
ToolCallingAdvisor 工具调用 给AI一双操作手
SafeGuardAdvisor 内容安全 给AI一个过滤器
ReReadingAdvisor RE2重读策略 让AI多读一遍
SimpleLoggerAdvisor 请求日志 AI的日志本

3.2 使用示例

java 复制代码
@Service
public class EnhancedAgent {
    
    @Autowired
    private ChatClient chatClient;
    
    @Autowired
    private VectorStore vectorStore;
    
    @Autowired
    private ChatMemory chatMemory;
    
    @Autowired
    private OrderTools orderTools;
    
    public String chat(String userId, String question) {
        return chatClient.prompt()
            .user(question)
            
            // 第1层:RAG检索
            .advisors(new QuestionAnswerAdvisor(vectorStore))
            
            // 第2层:对话记忆
            .advisors(new MessageChatMemoryAdvisor(chatMemory, userId))
            
            // 第3层:工具调用
            .advisors(new ToolCallingAdvisor(List.of(
                FunctionToolCallback.builder("queryOrder", orderTools::queryOrder)
                    .description("查询订单状态")
                    .inputType(String.class)
                    .build()
            )))
            
            // 第4层:内容安全
            .advisors(new SafeGuardAdvisor())
            
            .call()
            .content();
    }
}

老张的发现:"哇!代码从30行变成10行,而且每层职责清晰!"


四、自定义Advisor

4.1 场景:业务日志记录

java 复制代码
@Component
public class BusinessLogAdvisor implements CallAdvisor {
    
    @Override
    public ChatClientResponse adviseCall(
        ChatClientRequest request,
        CallAdvisorChain chain
    ) {
        long startTime = System.currentTimeMillis();
        
        // 执行后续链
        ChatClientResponse response = chain.nextAdviseCall(request);
        
        // 记录日志
        log.info("用户{}提问:{},耗时:{}ms,结果长度:{}",
            request.getContext().getUserId(),
            request.getPrompt().getTextContents(),
            System.currentTimeMillis() - startTime,
            response.getResult().getOutput().getText().length()
        );
        
        return response;
    }
}

4.2 场景:敏感词过滤

java 复制代码
@Component
public class SensitiveWordAdvisor implements CallAdvisor {
    
    private static final List<String> SENSITIVE_WORDS = 
        List.of("政治敏感词", "广告关键词", "...");
    
    @Override
    public ChatClientResponse adviseCall(
        ChatClientRequest request,
        CallAdvisorChain chain
    ) {
        // 检查输入
        for (String word : SENSITIVE_WORDS) {
            if (request.getPrompt().getTextContents().contains(word)) {
                return ChatClientResponse.builder()
                    .result(new ChatResponse(List.of(
                        new Generation(new AssistantMessage("抱歉,该问题涉及敏感内容")
                    )))
                    .build();
            }
        }
        
        // 执行后续
        ChatClientResponse response = chain.nextAdviseCall(request);
        
        // 检查输出
        String content = response.getResult().getOutput().getText();
        for (String word : SENSITIVE_WORDS) {
            if (content.contains(word)) {
                return ChatClientResponse.builder()
                    .result(new ChatResponse(List.of(
                        new Generation(new AssistantMessage("抱歉,该回答包含敏感内容")
                    )))
                    .build();
            }
        }
        
        return response;
    }
}

五、Advisor链的执行顺序

5.1 顺序规则

复制代码
Advisor顺序:值越小越先处理请求,越后处理响应(栈式)
java 复制代码
// 定义顺序
@Bean
public Advisor ragAdvisor(VectorStore store) {
    return new QuestionAnswerAdvisor(store) {
        @Override
        public int getOrder() { return Integer.MIN_VALUE; }  // 最先执行
    };
}

@Bean
public Advisor memoryAdvisor(ChatMemory memory) {
    return new MessageChatMemoryAdvisor(memory) {
        @Override
        public int getOrder() { return 0; }
    };
}

@Bean
public Advisor toolAdvisor() {
    return new ToolCallingAdvisor(...) {
        @Override
        public int getOrder() { return Integer.MAX_VALUE; }  // 最后执行
    };
}

5.2 执行流程图

复制代码
请求进入
    ↓
[SafeGuardAdvisor] 安全检查(最外层)
    ↓
[BusinessLogAdvisor] 日志记录
    ↓
[QuestionAnswerAdvisor] RAG检索
    ↓
[MessageChatMemoryAdvisor] 加载记忆
    ↓
[ToolCallingAdvisor] 工具调用(最内层)
    ↓
    AI模型
    ↓
[ToolCallingAdvisor] 处理工具结果
    ↓
[MessageChatMemoryAdvisor] 保存记忆
    ↓
[QuestionAnswerAdvisor] 增强Prompt
    ↓
[BusinessLogAdvisor] 记录响应
    ↓
[SafeGuardAdvisor] 最终检查
    ↓
返回给用户

六、实战:老张的重构

6.1 重构后的代码

java 复制代码
@Configuration
public class AgentConfig {
    
    @Bean
    public Advisor[] advisors(
        VectorStore vectorStore,
        ChatMemory chatMemory,
        OrderTools orderTools,
        McpClient mcpClient
    ) {
        return new Advisor[] {
            // 顺序从高到低
            new SafeGuardAdvisor(),                    // 安全
            new BusinessLogAdvisor(),                  // 日志
            new QuestionAnswerAdvisor(vectorStore),    // RAG
            new MessageChatMemoryAdvisor(chatMemory),  // 记忆
            new ToolCallingAdvisor(mcpClient),         // 工具
        };
    }
}

@Service
public class CustomerServiceAgent {
    
    @Autowired
    private ChatClient chatClient;
    
    public String chat(String userId, String question) {
        return chatClient.prompt()
            .user(question)
            .advisors(advisors)  // 一次性注入所有Advisor
            .call()
            .content();
    }
}

老张的感慨:"以前30行的代码,现在5行搞定。以后加新功能,加个Advisor就行,不用动主逻辑。"


七、下一集预告

老张的代码终于优雅了,但总监老李又提了挑战:"现在AI只会按固定流程走,能不能让它自己规划、自主决策?"

第10集《Agent不是遥不可及:ChatClient + @Tool 实现你的第一个Agent》将讲述如何让AI从"被动回答"变成"主动行动"。


📦 完整代码

GitHub分支:https://github.com/yinclei/SpringAITutorial/chapter09-advisor-chain


💬 互动环节

老兵问答:

你在项目中用过AOP或拦截器吗?

A. 用过Spring AOP

B. 用过MyBatis拦截器

C. 知道概念但没实操过

D. 完全没用过

下周一晚9点,我们继续!


作者:Java老兵搞AI | 关注我,看Java如何玩转AI

相关推荐
AI_AGENT_DEV_AI2 小时前
AI 英语在线考试智能体的开发
人工智能
玩AI的奶茶2 小时前
GPU 算力租赁平台横向对比:六家主流平台,24GB 档价差 85%
服务器·人工智能·gpu算力·token·算力租赁
马剑威(威哥爱编程)2 小时前
【HarmonyOS 7 小艺智能体实战】03:第一次建智能体——在小艺开放平台把骨架立起来
人工智能·华为·harmonyos
johnsong2 小时前
AI前沿日报 — 2026年10月23日:信任崩塌
人工智能
youdexiang2 小时前
Android录音APP高精度转写怎么选才不踩坑
人工智能·音视频
承渊政道2 小时前
HIL-UMI论文解读:用手持示范补齐机器人操作短板——对启元Q1机器人开发的启发
人工智能·深度学习·机器人·论文解读·具身智能·启元q1
解决问题no解决代码问题2 小时前
AI 研发:一致性漂移的加速陷阱
人工智能
启信慧眼.2 小时前
怎么用 AI 做供应商尽调
人工智能
视图猿人2 小时前
ROS2 nav2全局规划器和局部规划器参数说明
人工智能