第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

相关推荐
泯泷14 小时前
让 Agent 分清“马上办、等一等、顺手办”
人工智能·llm·agent
鲜于言悠90514 小时前
Claude Fable 5.2 登场,专治复杂推理与长任务
人工智能
联犀15 小时前
如何工程化降低语音交互AI设备的延迟(小智ai,芙崽这样的ai设备)
人工智能
杉岩数据15 小时前
杉岩数据董事长陈坚:从数据存储到行业智能——杉岩的AI时代战略布局
人工智能
冬奇Lab15 小时前
Loop 工程到底是高深的新技术,还是炒冷饭?用训练一个识别狗的模型讲明白
人工智能
冬奇Lab15 小时前
一天一个开源项目(第260篇):Plannotator —— 给 AI 编码 Agent 配一块「可批注的审阅台」,计划和代码改动都能在浏览器里逐条批示
人工智能·开源·claude
泯泷15 小时前
语音,正在成为 AI Agent 最自然的接口
人工智能·llm·agent
杨凯凡17 小时前
IT日报|2026-11-13:GitHub 大规模凭据攻击、Signal 形式化验证、Claude 新模型与 AI 安全诉讼
人工智能·安全·github
budingxiaomoli17 小时前
Spring统一功能处理
java·后端·spring
一个天蝎座 白勺 程序猿17 小时前
别让收藏夹替你学习:我搭AI知识体系的完整方法,脚本直接拿去用
人工智能·学习·ai·ai学习