记录AI学习之路Day15:浅记简单的SpringAI框架使用(advisor的使用)
浅记简单的SpringAI框架使用 — Advisor 的使用一、什么是 Advisor1.1 一句话概括Advisor 是 Spring AI 中的对话拦截器链——在每次与 LLM 交互的前后自动注入记忆、检索知识、改写问题、记录日志。没有 Advisor 用户消息 → LLM → 回复 有 Advisor 用户消息 → [记忆增强] → [RAG 检索] → [改写优化] → LLM → [日志记录] → 回复 每个 Advisor 是一个切面在 LLM 调用前后执行自己的逻辑。1.2 类比 Spring AOP// AOP方法执行前后加逻辑Around(execution(* com.example.*.*(..)))publicObjectaround(ProceedingJoinPointpjp){// 前置增强Objectresultpjp.proceed();// 方法执行// 后置增强returnresult;}// AdvisorLLM 调用前后加逻辑Advisoradvisor(request,chain)-{// 前置修改 prompt、注入上下文AdvisedRequestmodifieddoSomething(request);// 调用下一个 Advisor 或 LLMChatResponseresponsechain.next(modified);// 后置修改回复、记录日志returndoSomethingElse(response);};二、为什么需要 Advisor场景做一个智能客服要求 1. 记得客户之前说过什么记忆 2. 回答时检索产品手册RAG 3. 不能回答政治敏感问题安全护栏 4. 每次对话记录日志审计 传统写法 把所有逻辑揉在 Service 层 → 300 行代码改一处动全身 Advisor 写法 memoryAdvisor → ragAdvisor → guardAdvisor → logAdvisor → LLM 每个 Advisor 只做一件事自由组合插拔式增减三、快速开始3.1 依赖dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-advisors/artifactId/dependency!-- 聊天记忆 --dependencygroupIdorg.springframework.ai/groupIdartifactIdspring-ai-starter-chat-memory/artifactId/dependency3.2 第一个 Advisor 示例RestControllerpublicclassChatController{privatefinalChatClientchatClient;publicChatController(ChatClient.Builderbuilder){this.chatClientbuilder// 链式绑定 Advisor.defaultAdvisors(newSimpleLoggerAdvisor(),// ① 日志newChatMemoryAdvisor(memory),// ② 记忆newQuestionAnswerAdvisor(vectorStore)// ③ RAG).build();}PostMapping(/chat)publicStringchat(RequestBodyStringmessage){returnchatClient.prompt().user(message).call().content();}}## 《汽车保养》参数文档 保养周期总览以 5000 公里或 6 个月为小保养单位 | 里程/时间 | 保养类型 | 核心项目 | 预计费用区间 | |-----------|----------|----------|--------------| | 5000km/6个月 | 小保养 | 机油 机滤 | 300-600 元 | | 10000km/12个月 | 小保养 | 机油 机滤 空气滤 | 400-700 元 | | 20000km/24个月 | 中保养 | 汽油滤 空调滤 火花塞检查 | 800-1500 元 | | 40000km/48个月 | 大保养 | 变速箱油 刹车油 冷却液 | 2500-4500 元 | | 60000km/72个月 | 大保养 | 正时皮带/链条检查 减震器检查 | 3500-6000 元 |对话流程 用户: 6个月以内汽车多久保养一次 → ① SimpleLoggerAdvisor → 记录收到消息 6个月以内汽车多久保养一次 → ② ChatMemoryAdvisor → 注入历史对话上下文 → ③ QuestionAnswerAdvisor → 从向量库检索《汽车保养》参数文档 → LLM → 6个月以内汽车多久保养一次参考汽车保养{时间:6个月 保养类型小保养 核心项目机油 机滤 保养费用300-600 元} → ① SimpleLoggerAdvisor → 记录返回回复 xxx四、内置 Advisor 详解4.1 ChatMemoryAdvisor — 对话记忆作用把历史对话存起来每次请求自动注入上下文。ConfigurationpublicclassMemoryConfig{BeanpublicChatMemorychatMemory(){// 基于 JdbcTemplate 的持久化记忆存 MySQLreturnnewJdbcChatMemory(jdbcTemplate);}BeanpublicChatClientchatClient(ChatClient.Builderbuilder,ChatMemorymemory){returnbuilder.defaultAdvisors(newChatMemoryAdvisor(memory)).build();}}ChatMemory 三种实现实现存储位置适用场景InMemoryChatMemory内存Map开发测试JdbcChatMemoryMySQL生产持久化CassandraChatMemoryCassandra高并发-- JdbcChatMemory 需要的表MySqlCREATETABLEchat_memory(idVARCHAR(36)PRIMARYKEY,conversation_idVARCHAR(36)NOTNULL,contentTEXTNOTNULL,message_typeVARCHAR(20)NOTNULL,-- USER / ASSISTANTmetadata JSON,created_atTIMESTAMPDEFAULTNOW(),INDEXidx_conversation(conversation_id,created_at));控制记忆注入量newChatMemoryAdvisor(memory).setChatMemoryRetrieveSize(10)// 只取最近 10 条.setAdvisorPrefix(以下是之前的对话\n)// 注入时的前缀.setAdvisorSuffix(\n---\n);// 注入时的后缀4.2 QuestionAnswerAdvisor — RAG 知识检索作用从向量数据库检索相关知识拼到 prompt 里。ConfigurationpublicclassRagConfig{BeanpublicVectorStorevectorStore(EmbeddingModelembeddingModel){// 基于 PostgreSQL pgvectorreturnnewPgVectorStore(jdbcTemplate,embeddingModel,PgVectorStoreConfig.builder().tableName(knowledge_base).dimensions(1536).indexType(PgVectorStoreConfig.PgIndexType.HNSW).build());}BeanpublicQuestionAnswerAdvisorqaAdvisor(VectorStorevectorStore){returnQuestionAnswerAdvisor.builder(vectorStore).searchThreshold(0.7)// 相似度阈值低于 0.7 的不采用.topK(5)// 检索 Top 5.promptContextPrepend(\n参考以下知识回答\n)// 注入的知识前缀.build();}BeanpublicChatClientchatClient(ChatClient.Builderbuilder,ChatMemorymemory,QuestionAnswerAdvisorqaAdvisor){returnbuilder.defaultAdvisors(newChatMemoryAdvisor(memory),qaAdvisor).build();}}// 也可按需动态绑定不等同于全局 defaultAdvisorsPostMapping(/chat/rag)publicStringchatWithRag(RequestBodyStringmessage){returnchatClient.prompt().user(message).advisors(qaAdvisor)// 仅本次对话使用 RAG不持久.call().content();}4.3 SimpleLoggerAdvisor — 日志记录BeanpublicSimpleLoggerAdvisorloggerAdvisor(){returnnewSimpleLoggerAdvisor(// 请求日志r-log.info([AI请求] 用户: {},r.userText()),// 响应日志r-log.info([AI回复] {} (耗时{}ms, tokens:{}),r.getResult().getOutput().getContent(),r.getMetadata().get(time-to-first-token),r.getMetadata().get(usage)),0// Advisor 优先级数字越小先执行);}4.4 SafeGuardAdvisor — 安全护栏作用过滤敏感内容阻止非法提问。publicclassSafeGuardAdvisorimplementsCallAroundAdvisor{privatefinalListStringblockedKeywordsList.of(攻击,破解,翻墙,贩卖//等等等等);OverridepublicAdvisedResponsearoundCall(AdvisedRequestrequest,CallAroundAdvisorChainchain){StringuserMessagerequest.userText();// ① 前置检查for(Stringkeyword:blockedKeywords){if(userMessage.contains(keyword)){returnAdvisedResponse.builder().response(newChatResponse(List.of(newGeneration(抱歉我不能回答这个问题)))).build();// 阻止请求不调 LLM}}// ② 放行 → 下一个 Advisor → LLMAdvisedResponseresponsechain.nextAroundCall(request);// ③ 后置检查检查 LLM 回复是否包含敏感内容Stringreplyresponse.response().getResult().getOutput().getContent();for(Stringkeyword:blockedKeywords){if(reply.contains(keyword)){returnAdvisedResponse.builder().response(newChatResponse(List.of(newGeneration(抱歉回答被拦截)))).build();}}returnresponse;}OverridepublicStringgetName(){returnsafeGuard;}OverridepublicintgetOrder(){return0;}// 最先执行}4.5 ReReadingAdvisor — 提示词增强作用把用户的简短问题自动改写成更精确的 prompt。BeanpublicReReadingAdvisorreReadingAdvisor(){returnnewReReadingAdvisor();}// 用户输入6个月以内汽车多久保养一次// LLM 看到的请详细回答以下问题6个月以内汽车多久保养一次\n\n请确保回答准确、全面。4.6 ClearContextAdvisor — 清理上下文// 场景客户切换话题之前的对话历史没用了PostMapping(/chat/new-topic)publicStringnewTopic(RequestBodyStringmessage){returnchatClient.prompt().user(message).advisors(newClearContextAdvisor())// 清空记忆.call().content();}五、自定义 Advisor — 三步实现完整示例Token 计数 超长提示词自动截断ComponentpublicclassTokenLimitAdvisorimplementsCallAroundAdvisor{privatestaticfinalintMAX_TOKENS6000;OverridepublicAdvisedResponsearoundCall(AdvisedRequestrequest,CallAroundAdvisorChainchain){// ① 前置估算 token 数intestimatedTokensestimateTokens(request.userText()request.systemText());if(estimatedTokensMAX_TOKENS){log.warn(提示词过长 ({} tokens)自动截断,estimatedTokens);// 截断 userTextStringtruncatedtruncateByTokens(request.userText(),MAX_TOKENS);// 构建新请求AdvisedRequestnewRequestAdvisedRequest.from(request).userText(truncated).build();returnchain.nextAroundCall(newRequest);}// ② 放行AdvisedResponseresponsechain.nextAroundCall(request);// ③ 后置记录实际 token 用量intactualTokensestimateTokens(response.response().getResult().getOutput().getContent());log.info(实际消耗 token: {},actualTokens);returnresponse;}OverridepublicStringgetName(){returntokenLimit;}OverridepublicintgetOrder(){return1;}// 简单估算中文约 1.5 字 1 token英文约 0.75 词 1 tokenprivateintestimateTokens(Stringtext){return(int)(text.length()*0.5);}privateStringtruncateByTokens(Stringtext,intmaxTokens){intmaxCharsmaxTokens*3;returntext.length()maxChars?text.substring(0,maxChars)...:text;}}六、Advisor 链与执行顺序BeanpublicChatClientchatClient(ChatClient.Builderbuilder){returnbuilder.defaultAdvisors(newSafeGuardAdvisor(),// order0 最先拦截恶意请求newTokenLimitAdvisor(),// order1 控制长度newReReadingAdvisor(),// order10 优化 promptnewChatMemoryAdvisor(...),// order20 注入历史newQuestionAnswerAdvisor(...),// order30 RAG 检索newSimpleLoggerAdvisor(...)// order999 最后记录一切).build();}执行顺序用户 → LLM 方向: 用户 → SafeGuard → TokenLimit → ReReading → Memory → RAG → Logger → LLM 返回顺序LLM → 用户方向 LLM → Logger → RAG → Memory → ReReading → TokenLimit → SafeGuard → 用户 就像一个过滤器链正向执行前置逻辑反向执行后置逻辑。七、动态 vs 默认 Advisor// 默认 Advisor对所有请求生效chatClientbuilder.defaultAdvisors(memoryAdvisor,loggerAdvisor).build();// 动态 Advisor按需添加覆盖默认PostMapping(/chat/deep-search)publicStringdeepSearch(RequestBodyStringmessage){returnchatClient.prompt().user(message).advisors(qaAdvisor,deepSearchAdvisor)// 本次对话额外加两个.call().content();}// 动态 Advisor按需移除默认PostMapping(/chat/anonymous)publicStringanonymous(RequestBodyStringmessage){returnchatClient.prompt().user(message).advisors(newClearContextAdvisor())// 清除记忆.call().content();}八、Spring AI MCP 互通Advisor 和 MCP Server 可以互补使用用户请求 │ ▼ Advisor 链会话管理 │ ├→ ChatMemoryAdvisor加载历史记忆 ├→ QuestionAnswerAdvisorRAG 检索 ├→ SafeGuardAdvisor安全护栏 │ ▼ MCPTOOLS/SKILL Server外部工具调用 │ ├→ search_flights查航班 ├→ get_weather查天气 ├→ query_database查数据库 │ ▼ LLM 回复九、完整项目结构src/main/java/com/liangzai/chat/ ├── config/ │ ├── ChatClientConfig.java → ChatClient Advisor 链配置 │ ├── MemoryConfig.java → ChatMemory 持久化配置 │ └── RagConfig.java → 向量库 RAG Advisor ├── advisor/ │ ├── SafeGuardAdvisor.java → 安全护栏 │ ├── TokenLimitAdvisor.java → Token 限制 │ └── AuditAdvisor.java → 审计日志落库 ├── controller/ │ └── ChatController.java → REST API └── Application.java十、总结Advisor 的本质 面向 AI 对话的 AOP 没有 Advisor每次对话都要手动拼记忆、查知识库、过滤敏感词 有了 Advisor全部自动化链式编排插拔式增减 核心能力 ChatMemoryAdvisor → 记住历史 QuestionAnswerAdvisor → RAG 检索 SafeGuardAdvisor → 安全护栏 自定义 Advisor → 任何你需要的增强逻辑 选择优先级 内置 自定义 — 永远先看 Spring AI 有没有现成的 默认 动态 — 全局相同的放 defaultAdvisors按需加载的放 .advisors()