news 2026/7/10 5:32:16

ChatMemory + RAG 双剑合璧:让 AI 既记得你,又懂业务

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ChatMemory + RAG 双剑合璧:让 AI 既记得你,又懂业务

前 5 篇我们把 ChatMemory 从内存玩到了 MySQL、Redis,又做了性能对比和分布式部署。

但有个评论一直在我心里:“会多轮对话,它就能当客服了吗?”

不能。因为多轮对话只是"记得你刚才说了啥",它并不知道你公司的业务规则——退货几天、运费多少、会员几折。这些信息在知识库里,不在模型脑子里。

所以这一篇,我们把ChatMemory(记忆)RAG(检索增强)组合起来。一个管"对话上下文",一个管"业务知识",合体之后,才是真正能用的 AI 助手。


一、ChatMemory 和 RAG,到底是什么关系?

很多人以为这是二选一,其实它们是互补的

维度ChatMemory(对话记忆)RAG(检索增强)
解决的问题“你上一轮说了什么”“公司手册里写了什么”
数据来源本次会话的历史消息外部知识库(文档/FAQ/数据库)
生命周期会话级(可持久化)长期静态知识
类比短期记忆长期记忆 / 案头手册
Spring AI 组件MessageChatMemoryAdvisorQuestionAnswerAdvisor+VectorStore

一句话总结:

ChatMemory 让 AI"记得你",RAG 让 AI"懂业务"。
只上 ChatMemory:AI 记得聊天,但一问公司规定就胡说。
只上 RAG:AI 答得准,但每轮都像失忆,追问"那运费呢?"它就懵了。
两个一起上:既准又有连续性。


二、Spring AI 怎么做 RAG?

RAG 的核心链路:文档 → 切片 → 向量化(Embedding) → 存入向量库(VectorStore) → 用户提问时检索相似片段 → 拼进 Prompt

Spring AI 1.0 把"检索 + 拼 Prompt"封装成了一个开箱即用的 Advisor:QuestionAnswerAdvisor。我们只要准备两样东西:

  1. 一个EmbeddingModel(把文本变成向量)
  2. 一个VectorStore(存向量、做相似检索)

本项目一直用智谱 GLM通过 OpenAI 兼容接口接入,所以 Embedding 直接复用同一个 starter,换一个embedding-3模型即可。

Step 1:加依赖

pom.xml:

<!-- RAG 向量库:SimpleVectorStore(内存版,仅演示/测试用) --><!-- 注意:Spring AI 1.0 没有 spring-ai-starter-vector-store-simple 这个 starter, SimpleVectorStore 在核心模块 spring-ai-vector-store 里,版本由 spring-ai-bom 管理 --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-vector-store</artifactId></dependency><!-- RAG Advisor:QuestionAnswerAdvisor 所在模块(独立模块,openai starter 不传递引入) --><dependency><groupId>org.springframework.ai</groupId><artifactId>spring-ai-advisors-vector-store</artifactId></dependency>

⚠️重要:Spring AI 1.0 的SimpleVectorStore纯内存实现,官方明确说明"仅用于测试/演示,不能上生产"。它启动就把知识库全部向量化加载到内存,重启即丢。生产请看第六节换成 pgvector / Redis / Chroma。

Step 2:配 Embedding

application.yml(在已有的spring.ai.openai下加一段):

spring:ai:openai:api-key:${ZHIPU_API_KEY}base-url:https://open.bigmodel.cn/api/paas/v4# 嵌入模型:复用智谱 OpenAI 兼容接口(embedding-3),供 SimpleVectorStore 向量化知识库embedding:base-url:https://open.bigmodel.cn/api/paas/v4# 关键:Spring AI 默认 embedding 路径是 /v1/embeddings,智谱 endpoint 是 /embeddings(无 /v1 前缀),# 不覆盖会被拼成 .../v4/v1/embeddings → 404。chat 靠 completions-path 覆盖,这里同理。embeddings-path:/embeddingsoptions:model:embedding-3chat:completions-path:/chat/completionsoptions:model:glm-4.7-flashtemperature:0.7

加了这段,Spring AI 会自动配好一个EmbeddingModelBean(智谱embedding-3),后面的VectorStore直接注入就能用。


三、先把知识库建起来

新建RagConfig.java,用SimpleVectorStore承载知识库,启动时把几条客服 FAQ 向量化写进内存:

@ConfigurationpublicclassRagConfig{@BeanpublicVectorStorevectorStore(EmbeddingModelembeddingModel){SimpleVectorStorestore=SimpleVectorStore.builder(embeddingModel).build();// 启动时把知识库向量化并写入内存(演示用 5 条客服 FAQ)store.accept(knowledgeBase());returnstore;}/** * 演示知识库:公司客服 FAQ。 * 真实项目可改为从数据库 / 文件 / 网页批量加载,并用 metadata 打标签便于过滤。 */privateList<Document>knowledgeBase(){returnList.of(newDocument("退货政策:商品签收后 7 天内,未拆封、不影响二次销售的可无理由退货。",Map.of("category","policy","title","退货政策")),newDocument("运费规则:单笔订单满 99 元包邮,不满则收取 10 元运费。",Map.of("category","policy","title","运费规则")),newDocument("会员等级:分为普通会员、银卡、金卡;金卡会员享全场 9 折优惠。",Map.of("category","member","title","会员等级")),newDocument("发票说明:支持开具电子普通发票,下单时填写抬头,1 个工作日内开具。",Map.of("category","policy","title","发票说明")),newDocument("客服时间:人工客服每日 9:00-21:00,智能助手 7x24 小时在线。",Map.of("category","service","title","客服时间")));}}

关键点:

  • Document第二个参数是metadata(键值对),后面可以用过滤表达式按category精准检索。
  • store.accept(docs)会调用EmbeddingModel把每条文档变成向量存起来。
  • 真实项目里,这一步通常是"定时从数据库/文档站同步知识库",而不是写死 5 条。

四、纯 RAG:先单独跑通

新建RagController.java,先来一个只有 RAG、没有记忆的版本,方便对比:

@RestController@RequestMapping("/rag")publicclassRagController{privatefinalChatClientragOnlyClient;// 纯 RAG:只检索知识库publicRagController(ChatClient.Builderbuilder,VectorStorevectorStore){// 纯 RAG:只有 QuestionAnswerAdvisor,不带记忆this.ragOnlyClient=builder.defaultSystem("你是公司客服助手,只依据【知识库】内容回答,不要编造;知识库没有就如实说不知道。").defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().topK(4).similarityThreshold(0.5).build()).build()).build();}/** 纯 RAG 问答:每轮独立,不知道上一轮聊了什么 */@GetMapping("/ask")publicStringask(@RequestParamStringquestion){returnragOnlyClient.prompt().user(question).call().content();}}

QuestionAnswerAdvisor.builder(vectorStore)就是 RAG 的全部:

  • 用户提问 → Advisor 用同样的EmbeddingModel把问题向量化 → 去VectorStore里找最相似的topK条文档(相似度 ≥similarityThreshold)
  • 把检索到的文档拼进 Prompt 的上下文位置 → 模型基于"知识库 + 问题"作答

测一下:

curl"http://localhost:8080/rag/ask?question=你们运费怎么算?"# => 单笔订单满 99 元包邮,不满则收取 10 元运费。

效果不错!但如果你紧接着问**“那金卡会员呢?”**——它不知道你上一句在聊运费,容易答偏。

五、重点:ChatMemory + RAG 结合 ⭐

把上一篇的MessageChatMemoryAdvisor加进来,两个 Advisor 共存于同一个ChatClient,记忆和检索一起生效:

@RestController@RequestMapping("/rag")publicclassRagController{privatefinalChatClientragOnlyClient;// 纯 RAGprivatefinalChatClientragChatClient;// RAG + ChatMemory 结合privatefinalChatMemorychatMemory;publicRagController(ChatClient.Builderbuilder,VectorStorevectorStore,ChatMemorychatMemory){this.chatMemory=chatMemory;// 纯 RAG(同上,省略)this.ragOnlyClient=builder.defaultSystem("你是公司客服助手,只依据【知识库】内容回答,不要编造;知识库没有就如实说不知道。").defaultAdvisors(QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().topK(4).similarityThreshold(0.5).build()).build()).build();// 结合:RAG 提供外部知识 + ChatMemory 提供对话历史// 顺序:MessageChatMemoryAdvisor 在前(先把历史拼进 prompt),QuestionAnswerAdvisor 在后(再注入检索到的知识)this.ragChatClient=builder.defaultSystem("你是公司客服助手,结合【知识库】与【对话历史】回答用户问题。").defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build(),QuestionAnswerAdvisor.builder(vectorStore).searchRequest(SearchRequest.builder().topK(4).similarityThreshold(0.5).build()).build()).build();}@GetMapping("/ask")publicStringask(@RequestParamStringquestion){returnragOnlyClient.prompt().user(question).call().content();}/** RAG + ChatMemory 结合:既有知识库,又有多轮记忆 */@GetMapping("/chat")publicStringchat(@RequestParamStringsessionId,@RequestParamStringquestion){returnragChatClient.prompt().user(question).advisors(a->a.param(ChatMemory.CONVERSATION_ID,sessionId)).call().content();}}

就这一行差别:.defaultAdvisors(MessageChatMemoryAdvisor..., QuestionAnswerAdvisor...)

Spring AI 的 Advisor 是责任链:

  1. 请求进来,MessageChatMemoryAdvisor先把该会话的历史消息读出来,拼进 Prompt(让模型"记得前面聊了啥")
  2. 接着QuestionAnswerAdvisor检索知识库,把相关文档拼进上下文(让模型"知道业务规定")
  3. 模型拿到历史 + 知识 + 当前问题,给出连贯又准确的回答

测一下多轮:

# 第一轮curl"http://localhost:8080/rag/chat?sessionId=u1&question=运费怎么算?"# => 单笔订单满 99 元包邮,不满则收取 10 元运费。# 第二轮(指代消解:"那金卡呢"依赖上一轮的"运费")curl"http://localhost:8080/rag/chat?sessionId=u1&question=那金卡会员运费有优惠吗?"# => 金卡会员享全场 9 折;运费规则本身不变(满 99 包邮,不满收 10 元),# 但金卡折扣同样适用于运费部分。

看到了吗?第二轮它能正确理解"那金卡呢"指的是运费优惠,这就是 ChatMemory 的功劳;而"9 折""满 99 包邮"这些硬知识来自 RAG。两者缺一不可。


六、生产落地,这 5 个坑别踩

1. SimpleVectorStore 不能上生产

内存版重启即丢、无法多实例共享。换持久化向量库,代价极小——只要换依赖和 Bean:

// 生产推荐:pgvector(PostgreSQL 插件,SQL 友好、事务一致)@BeanpublicVectorStorevectorStore(EmbeddingModelembeddingModel,JdbcTemplatejdbcTemplate){returnPgVectorStore.builder(jdbcTemplate,embeddingModel).dimensions(1024)// 与 embedding-3 维度一致.build();}

对应依赖:spring-ai-starter-vector-store-pgvector(Redis/Chroma 同理,换成对应 starter 即可)。Advisor 那层代码一行不用改,这就是面向接口编程的好处。

2. Advisor 顺序有讲究

记忆 Advisor 放前面、RAG Advisor 放后面是常规写法。顺序影响最终 Prompt 的拼装结构,建议记忆在前、知识在后,并在 system 里写清"先参考对话历史,再结合知识库"。

3. 注意 Token 预算

ChatMemory 的maxMessages=20(前几篇配的)+ RAG 的topK文档,都会塞进上下文。两者叠加可能撑爆窗口、推高费用。经验值:

  • 对话类场景:maxMessages=10~20,topK=3~5
  • 知识密集场景:maxMessages调小,topK调大
4. 相似度阈值要调

similarityThreshold太高(如 0.85)会漏召回,太低(如 0.3)会召回无关文档。中文 FAQ 场景一般 0.4~0.6 起步,用真实问题跑一遍校准。

5. 用 metadata 过滤,而不是什么都塞进去

QuestionAnswerAdvisor支持运行时过滤表达式,按业务隔离知识:

// 只检索"政策类"文档chatClient.prompt().user(question).advisors(a->a.param(QuestionAnswerAdvisor.FILTER_EXPRESSION,"category == 'policy'")).call().content();

多租户、多业务线时,这个过滤能避免 A 客户的问题召回 B 客户的内部文档。


七、总结

核心结论:

  1. ChatMemory 和 RAG不是二选一,而是标配组合——一个管"记得你",一个管"懂业务"。
  2. Spring AI 用Advisor把两者解耦:MessageChatMemoryAdvisor+QuestionAnswerAdvisor,一个ChatClient全搞定。
  3. 组合成本极低:加一个 Advisor 而已,无需改模型、无需改 Prompt 结构。
  4. SimpleVectorStore仅演示,生产换 pgvector / Redis / Chroma,Advisor 层零改动

整体架构:

用户问题 │ ├─[MessageChatMemoryAdvisor]──> 读历史消息 ──┐ │ ├─> 拼成完整 Prompt ─> GLM ─> 回答 └─[QuestionAnswerAdvisor]──> 检索知识库 ─────┘

本篇代码清单:

  • pom.xml:新增spring-ai-vector-store(核心模块,提供内存版 SimpleVectorStore)
  • application.yml:新增spring.ai.openai.embedding(智谱 embedding-3)
  • RagConfig.java:VectorStore+ 知识库加载
  • RagController.java:纯 RAG 接口 + RAG/ChatMemory 结合接口

下一篇预告:ChatMemory 监控与排查(怎么知道记忆丢了、RAG 没召回?)

写在最后

我是一名8 年 Java 后端,正在转型 AI 应用开发。Spring AI 系列会持续更新,从 hello world 到 RAG 到 Agent,一路踩坑一路写。

如果你也在转型 AI,关注我,一起走。有问题评论区聊,我会逐条回复。

如果这篇文章帮到了你,点个赞就是对我最大的鼓励❤️


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

Karpathy 65行提示词解析:构建结构化AI对话系统的工程实践

&#x1f680; 30款热门AI模型一站整合&#xff0c;DeepSeek/GLM/Qwen 随心用&#xff0c;限时 5 折。 &#x1f449; 点击领海量免费额度 最近在AI圈子里&#xff0c;Andrej Karpathy发布的一个项目引起了不小的震动。他仅用65行精心设计的提示词&#xff08;Prompt&#x…

作者头像 李华
网站建设 2026/7/10 5:26:57

RC 无源高通滤波器设计实战:从 1kHz 到 100kHz 的 3 种参数计算与仿真验证

RC无源高通滤波器工程指南&#xff1a;1kHz-100kHz参数计算与仿真验证在音频处理、传感器信号调理和射频通信等领域&#xff0c;高频噪声抑制和低频干扰消除是硬件工程师面临的常见挑战。无源RC高通滤波器以其简洁的拓扑结构、低廉的成本和可靠的性能&#xff0c;成为解决这类问…

作者头像 李华
网站建设 2026/7/10 5:25:11

基于MA12070与PIC18F56K42的高保真音频系统设计

1. 项目概述&#xff1a;构建基于MA12070与PIC18F56K42的高保真音频系统在数字音频处理领域&#xff0c;如何平衡功率效率与音质表现一直是工程师面临的挑战。本项目通过英飞凌MA12070 D类音频放大器与Microchip PIC18F56K42微控制器的组合&#xff0c;打造了一套支持80W2声道输…

作者头像 李华
网站建设 2026/7/10 5:22:57

OpenClaw中文AI Agent开发实战:从零部署到飞书集成

1. 这不是“龙虾”&#xff0c;是OpenClaw——一个被中文社区误传却真实可用的AI Agent开发框架 你搜“ai 龙虾 助手 中文”&#xff0c;首页跳出来的全是带“龙虾”字样的教程、安装包、QQ群和网盘链接。点进去一看&#xff0c;界面确实有个卡通龙虾图标&#xff0c;命令行里…

作者头像 李华
网站建设 2026/7/10 5:20:06

Beyond Compare 5授权机制解析与Python密钥生成工具实践

1. 项目概述&#xff1a;当“评估期已结束”的弹窗成为日常 如果你是一名开发者、运维工程师&#xff0c;或者经常需要处理大量文本、代码、配置文件的从业者&#xff0c;那么对Beyond Compare这款“神器”一定不陌生。它那清晰的三窗格对比、强大的文件夹同步和合并功能&#…

作者头像 李华
网站建设 2026/7/10 5:19:57

居家办公效率提升秘籍:5 款实用小工具及更换原因大揭秘!

1. 居家办公的新挑战与调整有一位全职在家工作的人士&#xff0c;十分享受这种工作模式。然而&#xff0c;最近体检结果让他不得不重新审视在办公桌前的时长&#xff0c;进而对日常习惯做出改变。若要继续在家高效工作&#xff0c;办公室配置就得更新&#xff0c;以保障身心健康…

作者头像 李华