上集回顾:老张通过MCP协议接入了企业所有系统,工具调用变得简单了。但老李(总监)提出了新要求:“代码太乱了,检索、记忆、安全校验都堆在一起,不好维护。”
一、故事继续:代码混乱的代价
周三下午,老张的客服系统代码:
@ServicepublicclassCustomerServiceAgent{// 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. 调用工具StringtoolResult=orderTools.queryOrder(orderId);// 5. 构建PromptStringprompt=buildPrompt(history,docs,toolResult,query);// 6. 调用AIStringanswer=chatClient.prompt().user(prompt).call().content();// 7. 保存历史(逐条 add)chatMemory.add(userId,newUserMessage(query));chatMemory.add(userId,newAssistantMessage(answer));returnanswer;}老李的批评:“小张啊,这代码能跑,但维护起来太痛苦了。换个向量库、改个安全策略,都要动这里。”
二、技术引入:什么是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 使用示例
@ServicepublicclassEnhancedAgent{@AutowiredprivateChatClientchatClient;@AutowiredprivateVectorStorevectorStore;@AutowiredprivateChatMemorychatMemory;@AutowiredprivateOrderToolsorderTools;publicStringchat(StringuserId,Stringquestion){returnchatClient.prompt().user(question)// 第1层:RAG检索.advisors(newQuestionAnswerAdvisor(vectorStore))// 第2层:对话记忆.advisors(newMessageChatMemoryAdvisor(chatMemory,userId))// 第3层:工具调用.advisors(newToolCallingAdvisor(List.of(FunctionToolCallback.builder("queryOrder",orderTools::queryOrder).description("查询订单状态").inputType(String.class).build())))// 第4层:内容安全.advisors(newSafeGuardAdvisor()).call().content();}}老张的发现:“哇!代码从30行变成10行,而且每层职责清晰!”
四、自定义Advisor
4.1 场景:业务日志记录
@ComponentpublicclassBusinessLogAdvisorimplementsCallAdvisor{@OverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){longstartTime=System.currentTimeMillis();// 执行后续链ChatClientResponseresponse=chain.nextAdviseCall(request);// 记录日志log.info("用户{}提问:{},耗时:{}ms,结果长度:{}",request.getContext().getUserId(),request.getPrompt().getTextContents(),System.currentTimeMillis()-startTime,response.getResult().getOutput().getText().length());returnresponse;}}4.2 场景:敏感词过滤
@ComponentpublicclassSensitiveWordAdvisorimplementsCallAdvisor{privatestaticfinalList<String>SENSITIVE_WORDS=List.of("政治敏感词","广告关键词","...");@OverridepublicChatClientResponseadviseCall(ChatClientRequestrequest,CallAdvisorChainchain){// 检查输入for(Stringword:SENSITIVE_WORDS){if(request.getPrompt().getTextContents().contains(word)){returnChatClientResponse.builder().result(newChatResponse(List.of(newGeneration(newAssistantMessage("抱歉,该问题涉及敏感内容")))).build();}}// 执行后续ChatClientResponseresponse=chain.nextAdviseCall(request);// 检查输出Stringcontent=response.getResult().getOutput().getText();for(Stringword:SENSITIVE_WORDS){if(content.contains(word)){returnChatClientResponse.builder().result(newChatResponse(List.of(newGeneration(newAssistantMessage("抱歉,该回答包含敏感内容")))).build();}}returnresponse;}}五、Advisor链的执行顺序
5.1 顺序规则
Advisor顺序:值越小越先处理请求,越后处理响应(栈式)// 定义顺序@BeanpublicAdvisorragAdvisor(VectorStorestore){returnnewQuestionAnswerAdvisor(store){@OverridepublicintgetOrder(){returnInteger.MIN_VALUE;}// 最先执行};}@BeanpublicAdvisormemoryAdvisor(ChatMemorymemory){returnnewMessageChatMemoryAdvisor(memory){@OverridepublicintgetOrder(){return0;}};}@BeanpublicAdvisortoolAdvisor(){returnnewToolCallingAdvisor(...){@OverridepublicintgetOrder(){returnInteger.MAX_VALUE;}// 最后执行};}5.2 执行流程图
请求进入 ↓ [SafeGuardAdvisor] 安全检查(最外层) ↓ [BusinessLogAdvisor] 日志记录 ↓ [QuestionAnswerAdvisor] RAG检索 ↓ [MessageChatMemoryAdvisor] 加载记忆 ↓ [ToolCallingAdvisor] 工具调用(最内层) ↓ AI模型 ↓ [ToolCallingAdvisor] 处理工具结果 ↓ [MessageChatMemoryAdvisor] 保存记忆 ↓ [QuestionAnswerAdvisor] 增强Prompt ↓ [BusinessLogAdvisor] 记录响应 ↓ [SafeGuardAdvisor] 最终检查 ↓ 返回给用户六、实战:老张的重构
6.1 重构后的代码
@ConfigurationpublicclassAgentConfig{@BeanpublicAdvisor[]advisors(VectorStorevectorStore,ChatMemorychatMemory,OrderToolsorderTools,McpClientmcpClient){returnnewAdvisor[]{// 顺序从高到低newSafeGuardAdvisor(),// 安全newBusinessLogAdvisor(),// 日志newQuestionAnswerAdvisor(vectorStore),// RAGnewMessageChatMemoryAdvisor(chatMemory),// 记忆newToolCallingAdvisor(mcpClient),// 工具};}}@ServicepublicclassCustomerServiceAgent{@AutowiredprivateChatClientchatClient;publicStringchat(StringuserId,Stringquestion){returnchatClient.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