news 2026/10/24 2:13:24

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
第9集:Spring AI Advisor链:像Spring MVC拦截器一样优雅

上集回顾:老张通过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作用类比
QuestionAnswerAdvisorRAG检索增强给AI一本参考书
MessageChatMemoryAdvisor会话记忆给AI一个记事本
ToolCallingAdvisor工具调用给AI一双操作手
SafeGuardAdvisor内容安全给AI一个过滤器
ReReadingAdvisorRE2重读策略让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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/24 2:09:07

外包和驻场的经历,技术简历上怎么写才不吃亏

外包、驻场、人力外协&#xff0c;这几种形式在国内技术就业里占的比例不小。转岗到甲方或者产品公司时&#xff0c;这段经历怎么写是个真问题。 常见的两种处理都有毛病。一种是只写甲方名字&#xff0c;不提外包公司&#xff0c;这在背调时会直接对不上。另一种是老老实实写外…

作者头像 李华
网站建设 2026/10/24 2:07:42

Java学业质量分析系统开发实战与架构解析

1. 项目概述&#xff1a;学业质量分析系统的核心价值这个基于Java技术栈的学生学业质量分析系统&#xff0c;本质上是一个教育数据挖掘与可视化平台。我在实际开发中发现&#xff0c;传统教务系统往往只提供基础的成绩录入和查询功能&#xff0c;而无法从海量学生数据中提取有价…

作者头像 李华