ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringAI2用Advisors串Memory与RAG

SpringAI2用Advisors串Memory与RAG Spring AI 2ChatClient Advisors 把 Memory 与 RAG 串成链多轮对话要记上下文、回答要检索私有知识时用 defaultAdvisors 按序挂 Memory 与 QuestionAnswer别漏 CONVERSATION_ID。一、痛点ChatClient 会调模型但不会自动「记得」和「查库」把业务接到 Spring AI 的ChatClient之后第一周通常只做单轮问答。需求一加码就出现两件独立的事多轮要记忆模型 API 本身无状态上一轮说过的约束下一轮不会自动带上回答要接地要查向量库里的制度、工单、产品说明不能只靠参数里的世界知识。若把「拼接历史消息」和「先查向量再拼 Prompt」都写进 Service很快就是满屏复制粘贴每个入口都要取历史、裁剪窗口、拼系统提示再决定要不要检索。工具调用一开更乱中间轮次的消息进不进记忆、检索要不要看到工具结果这些规则会散落在多个if里。这两件事都适合做成Advisor在请求进模型前改 Prompt在响应回来后写回状态。官方 Advisors 与 ChatClient 文档给出的组合是MessageChatMemoryAdvisor管会话历史QuestionAnswerAdvisor或模块化 RAG 的RetrievalAugmentationAdvisor管检索增强。下面依据 Spring AI2.x当前参考文档只谈Advisors 链、会话 ID、Memory/RAG 顺序与 ToolCalling 自动注册。MCP 工具如何显式接到 ChatClient见本账号 2026-09-25 那篇。这里不重复绑定细节也不宣称「装了 MCP starter 就会自动带上远程工具」。二、注册defaultAdvisors顺序就是契约推荐在 Builder 上用defaultAdvisors(...)注册避免每个请求重复拼链ConfigurationpublicclassChatClientConfig{privatefinalChatModelchatModel;privatefinalVectorStorevectorStore;publicChatClientConfig(ChatModelchatModel,VectorStorevectorStore){this.chatModelchatModel;this.vectorStorevectorStore;}BeanChatClientchatClient(){ChatMemorychatMemoryMessageWindowChatMemory.builder().build();returnChatClient.builder(chatModel).defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(),QuestionAnswerAdvisor.builder(vectorStore).build()).build();}}MessageWindowChatMemory默认窗口大约20条消息超出后淘汰更早的消息并尽量保留 system若又写入新的 system旧的 system 会被清掉保证指令面不互相打架。需要持久化时再换带ChatMemoryRepository的实现内存 / JDBC / Redis / Mongo 等见 Chat Memory 文档Advisor 侧仍是同一套MessageChatMemoryAdvisor。另一种VectorStoreChatMemoryAdvisor把记忆检索进 system 文本适合超长历史的相关性召回。但它的语义和「整段消息列表回放」不同选哪个要看模型是否擅长吃结构化历史。顺序规则Advisors APIgetOrder()越小请求侧越先执行链是栈请求正向穿过响应反向穿过——先处理请求的 Advisor最后处理响应同 order 值不保证相对次序关键路径不要靠「碰巧的注册顺序」。官方 ChatClient 文档对 Memory RAG 的推荐很明确先挂 Memory再挂 QuestionAnswerAdvisor。这样检索查询能看到已经注入的对话上下文不会只拿「当前这一句用户输入」做孤立检索。举个例子用户第一句说「只看华东仓」第二句说「库存多少」。如果 RAG 看不到第一句检索词会缺少地域约束答案质量会明显漂移。更复杂的 RAG 可改用RetrievalAugmentationAdvisor并依赖org.springframework.ai.rag模块化 RAG拼检索、增强与后处理Naive RAG 场景QuestionAnswerAdvisor.builder(vectorStore)通常就够作为起点。Advisor 还接入可观测性链路与指标里能看到各顾问耗时排障时先分清是记忆写入慢还是检索慢别笼统怪模型。三、硬约束每次调用都要带 CONVERSATION_IDMemory Advisor 按会话键读写历史。文档反复强调参数名是ChatMemory.CONVERSATION_ID必须在每一次使用 Memory Advisor 的调用里通过.advisors(a - a.param(...))传入没有默认会话 ID省略会在运行时抛出IllegalArgumentException。RestControllerpublicclassSupportController{privatefinalChatClientchatClient;publicSupportController(ChatClientchatClient){this.chatClientchatClient;}PostMapping(/support/chat)publicStringchat(RequestParamStringconversationId,RequestParamStringuserText){returnchatClient.prompt().advisors(a-a.param(ChatMemory.CONVERSATION_ID,conversationId)).user(userText).call().content();}}把会话 ID 当成和「用户文本」同级的请求必填项来设计登录用户可用稳定业务键匿名前端可在首轮生成 UUID 并回传网关也可透传追踪号。但框架不会替你猜。共用一个全局 ID 会导致串话每次新建却从不复用又会让窗口记忆形同虚设。升级注意对照当前参考文档与迁移习惯旧版若存在「conversationId 流式辅助方法」在 2.x 文档路径下应改为param显式传入。若记忆里只剩「最终一问一答」、工具中间轮次丢失先检查 Advisor 顺序再对照文档看是否需要调整 tool-calling 相关顺序或关闭内部会话历史选项例如文档中的disableInternalConversationHistory一类能力具体以你锁定的 Spring AI 2.x 参考页为准勿抄过期方法名。四、ToolCallingAdvisor自动在链上但别和 Memory 打架ChatClient默认自动注册ToolCallingAdvisor可用属性或单次参数关闭默认顺序约为Ordered.HIGHEST_PRECEDENCE 300。它负责模型发起的工具调用循环直到不再需要 tool call。即便你没有在 Builder 上静态配置工具运行时由别的 Advisor 注入的工具定义也能被它接住这就是「循环上提」的好处。和 Memory 同时开时注意两件事链上已有ToolAdvisor标记时不会再自动塞第二个 ToolCallingAdvisorMemoryAdvisor标记会参与 DefaultChatClient 对「下游是否有记忆顾问」的检测从而影响工具往返期间的历史如何被存储。关闭自动注册的常见方式文档全局spring.ai.chat.client.tool-calling.enabledfalse单次AdvisorParams.toolCallingAdvisorAutoRegister(false)或自行提供实现了ToolAdvisor的顾问抑制默认那一个。自动注册的ToolCallingAdvisor默认位置是Ordered.HIGHEST_PRECEDENCE 300可用spring.ai.chat.client.tool-calling.advisor-order调整这个值要小于所有需要在工具循环内运行的顾问的 order。经验上希望「每轮工具迭代都再跑一遍」的顾问order 要落在工具循环内侧只想在用户请求进出时各跑一次的顾问则放在外侧。MCP 这里不展开需要远程工具时仍按 09-25 的方式显式.defaultTools(...)/.tools(...)由同一条 ToolCalling 循环执行。五、联调时怎么验证链真的生效配置写对只是第一步联调时还得能「看见」顾问链光看最终字符串不够缺 ID 必失败刻意去掉CONVERSATION_ID确认立刻IllegalArgumentException证明 Memory Advisor 确实在链上没被悄悄跳过同 ID 多轮第一轮设定约束第二轮用代词提问答案应引用约束换一个 ID 后约束应消失顺序对照临时把 QuestionAnswer 调到 Memory 前做一次对比仅实验环境观察检索词是否丢掉上文评审问「为什么文档要求 Memory 在前」时可以拿来解释日志与观测打开 Advisor 包 DEBUG 或依赖 Micrometer 链路确认 Memory → RAG →ToolCalling→ Model 的先后生产记得脱敏 Prompt窗口边界连续发送超过窗口上限的轮次确认旧消息被淘汰且 system 仍在避免「指令面丢了」的假回归。若使用流式stream()记得选同时支持流式的顾问实现或确认内置顾问对 stream 路径的行为阻塞调用与流式调用不要混用两套记忆写入假设。把上面几步写进接口验收比只贴一段 Builder 代码靠谱能避开「演示环境碰巧有上下文、生产却没有会话键」这种坑。和 09-25 那篇 MCP 文的分工很简单那篇解决「工具回调从哪来」这篇解决「记忆与检索在链上怎么排」。完整的助手往往两者都要。但塞进同一篇文章容易把「显式 tools」和「defaultAdvisors」两套生命周期搅在一起所以日更拆开更清晰。六、落地清单与常见坑上线多轮问答或内部知识助手时按下面勾一遍Builder 注册Memory RAG 用defaultAdvisors运行时只补CONVERSATION_ID及个别覆盖。顺序Memory 在 QuestionAnswer 前需要观测时再把SimpleLoggerAdvisor挂到偏后位置并给org.springframework.ai.chat.client.advisor开 DEBUG注意脱敏。会话键每个终端用户或每个浏览器会话一个稳定 ID禁止共用一个全局 ID 导致串话。窗口大小默认 20 只是起点超长制度问答可加大窗口或改 VectorStore 记忆顾问但要盯上下文长度与费用这里只做定性提醒不给延迟或单价数字。RAG 选型先QuestionAnswerAdvisor检索管道要拆步骤再用RetrievalAugmentationAdvisor。工具循环默认 ToolCallingAdvisor 已在关自动注册前想清楚是否改为用户自驱循环。与 MCP 文分工工具从哪来本地Tool/ MCP provider是绑定问题这里讲的是绑定之后的 Advisors 链问题。两篇互补不要混成「装了 starter 就既有记忆又有远程工具」。回归用例至少覆盖「缺 CONVERSATION_ID 抛错」「同 ID 多轮能引用上文」「Memory 在前时检索词含上文约束」「工具调用后最终答复仍可写入记忆」四类。收个尾Advisors 把「记忆」和「检索」从业务代码里抽成可排序的中间件。先 Memory 后 RAG每次带上ChatMemory.CONVERSATION_ID再让自动注册的 ToolCallingAdvisor 跑工具循环。这条链比在 Service 里手拼 Prompt 更稳也更好观测。
返回列表