完整实战)
Spring AI 多轮对话记忆怎么做ChatMemory 持久化内存/JDBC/Mongo完整实战系列Spring AI 深入浅出第 7 篇版本Spring AI 2.0.1 / Spring Boot 4.1.x / JDK 21标签#SpringAI #ChatMemory #多轮对话 #会话管理前置阅读第 6 篇《Advisor 责任链原理》预计字数6500 字可复制源码 持久化对照表 踩坑 FAQ本文将解决多轮对话的本质是把历史消息塞回上下文但怎么塞、塞多少、怎么按用户隔离、怎么持久化才是工程问题。本文用ChatMemoryMessageChatMemoryAdvisor一次讲透。你将学到记忆的本质与 token 成本增长MessageWindowChatMemory滑窗记忆builder 用法ChatMemory.CONVERSATION_ID会话隔离2.0 必传不传会串话持久化ChatMemoryRepository内存/JDBC/Mongo/Cassandra 等上下文裁剪与 token 成本控制2.1 Session API 前瞻ChatMemory 即将废弃一、版本标注本文基于 Spring AI 2.0.1 / Boot 4.1 / JDK 21。两个 2.0 必知的 breaking①InMemoryChatMemory类已移除改用MessageWindowChatMemory.builder()②ChatMemory.DEFAULT_CONVERSATION_ID已删除会话 id 必须显式传。⚠️ 前瞻Spring AI2.1预计 2026-11将引入 Session API 并废弃 ChatMemory。本文以 2.0 写法为主文末给出迁移预告若你读到本文时 2.1 已 GA请优先用 Session API。二、记忆的本质大模型本身无状态——每次请求它都不记得上一句。多轮对话的做法是把历史消息拼接进本次请求的上下文。代价是token 成本随轮次线性增长每轮都把前面所有内容重新发一遍。所以记忆不是存得越多越好而是存得刚刚好——这也是下面滑窗与裁剪的意义。三、ChatMemory 与滑窗记忆3.1 创建 ChatMemory Beanimportorg.springframework.ai.chat.memory.ChatMemory;importorg.springframework.ai.chat.memory.MessageWindowChatMemory;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassMemoryConfig{BeanpublicChatMemorychatMemory(){returnMessageWindowChatMemory.builder().maxMessages(20)// 只保留最近 20 条消息默认也是 20.build();}}注意MessageWindowChatMemory的构造器是私有的必须用builder()直接new会编译报错。滑窗的意思是超过maxMessages时自动丢弃最旧的消息保留 system 消息。3.2 挂到 ChatClient 上importorg.springframework.ai.chat.client.ChatClient;importorg.springframework.ai.chat.client.advisor.MessageChatMemoryAdvisor;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;ConfigurationpublicclassChatClientConfig{BeanpublicChatClientchatClient(ChatClient.Builderbuilder,ChatMemorymemory){returnbuilder.defaultAdvisors(MessageChatMemoryAdvisor.builder(memory).build()).build();}}MessageChatMemoryAdvisor是一个内置 Advisor第 6 篇的机制它会在请求进入时把历史消息回填在响应返回后把本轮对话写入记忆。四、会话隔离ChatMemory.CONVERSATION_ID2.0 必传这是最容易踩的坑。2.0 删掉了默认会话 id不传conversationId就没有隔离所有用户会共用一段记忆互相串话importorg.springframework.ai.chat.memory.ChatMemory;// 每个会话一个稳定的 id如登录用户的 session、或前端生成的 UUIDStringconversationIduser-10086;StringreplychatClient.prompt().user(帮我查一下我的订单).advisors(a-a.param(ChatMemory.CONVERSATION_ID,conversationId)).call().content();要点ChatMemory.CONVERSATION_ID的值是chat_memory_conversation_id作为 advisor 参数传入。conversationId必须稳定同一段对话的每一轮都用同一个 id换新对话才换新 id。典型的取法登录态用userId游客态用前端生成并持久化的UUID。五、持久化ChatMemoryRepository5.1 默认内存重启即失不配持久化时默认用内存实现应用重启记忆就没了——只适合本地开发。5.2 持久化实现ChatMemory的持久化靠ChatMemoryRepository接口2.0 内置实现有JDBC、Cassandra、Neo4j、MongoDB、CosmosDB各实现的具体类名与配置以 2.0.1 官方文档为准。说明Redis 不是 Spring AI 2.0 内置的 ChatMemoryRepository 实现不少教程写了 Redis实际要自己实现ChatMemoryRepository接口对接。若团队有 Redis 技术栈通常会用 JDBC/Mongo 先落地或自定义实现。以 JDBC 为例的接入思路!-- 需要 JDBC 一个数据库驱动 --dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-jdbc/artifactId/dependency// 思路用 JdbcChatMemoryRepository 替换默认内存实现// 具体构造参数与建表语句以 2.0.1 官方文档为准5.3 持久化选型对照表实现适用特点内存默认本地开发/测试重启丢失JDBC已有关系库无需新组件运维简单MongoDB已有 Mongo文档型扩展方便Cassandra高可用大规模分布式运维重CosmosDBAzure 生态托管免运维六、上下文裁剪与 token 成本控制记忆不是越大越好几个控制手段滑窗条数maxMessages(20)控制保留条数超出丢最旧。主动裁剪长对话里只保留关键结论而非全部历史结合第 5 篇的模板把摘要而非全文回填。token 预算结合第 6 篇的 token 预算 Advisor超预算短路或降级。经验问答类会话maxMessages设 10~20 够用需要强上下文连贯的如代码 review 长会话可加大但要盯着 token 成本。七、原理图解一次多轮对话的记忆流转模型ChatMemoryMessageChatMemoryAdvisor用户模型ChatMemoryMessageChatMemoryAdvisor用户第1轮user(查订单) conversationId读历史空只发本轮回复写入 [user, assistant]第2轮user(那我的优惠券呢) 同 conversationId读历史第1轮 2条历史2条 本轮回复理解那指上一轮订单追加写入conversationId是这段流转的键——同一个 id 读同一段记忆。八、2.1 前瞻Session API 与 ChatMemory 废弃Spring AI 2.1预计 2026-11将引入Session API并废弃 ChatMemory。对本文内容的影响MessageWindowChatMemory/MessageChatMemoryAdvisor这套 API 会进入废弃期迁移方向用新的 Session API 管理多轮会话状态写作策略2.0 项目继续用本文写法没问题新项目若在 2.1 之后直接学 Session API我会在本合集置顶评论维护一张「2.1 变更速览」把这套记忆 API 的迁移对照补上。九、踩坑 FAQ坑 1多用户串话现象用户 A 的对话上下文跑到了用户 B 那里。原因没传conversationId2.0 没有默认值兜底了。解决每轮请求显式.advisors(a - a.param(ChatMemory.CONVERSATION_ID, conversationId))且 id 要稳定。坑 2滑窗截断丢了关键约束现象对话到后面模型忘了前面设定的关键约束。原因maxMessages太小最早的 system 约束/关键信息被滑出窗口。解决system 消息会保留关键约束放defaultSystem每次都会带别只放最早一条 user 里。坑 3直接 new MessageWindowChatMemory 编译报错现象new MessageWindowChatMemory(20)报构造器私有。原因2.0 改为 builder 模式。解决用MessageWindowChatMemory.builder().maxMessages(20).build()。坑 4重启后记忆丢失现象重启应用对话上下文全没了。原因用了默认内存实现。解决接ChatMemoryRepository持久化实现JDBC/Mongo 等。坑 5工具调用的中间消息污染记忆现象记忆里混入了工具调用的中间消息导致上下文杂乱。原因历史上某些版本默认把工具中间消息也写入了记忆。解决2.0 默认会过滤掉工具调用的中间消息只保留 user/assistant 对话若你自定义了写入逻辑需注意。十、小结与合集导航本篇把多轮对话记忆从原理到落地讲完MessageWindowChatMemory.builder()滑窗记忆MessageChatMemoryAdvisor挂载 ChatMemory.CONVERSATION_ID会话隔离ChatMemoryRepository持久化内存/JDBC/Mongo/Cassandratoken 成本控制与 2.1 Session API 前瞻下一篇《结构化输出》——让 AI 直接返回 Java 对象并自动自纠正校验失败。十一、互动钩子投票你的多轮对话记忆用什么持久化内存开发阶段JDBCMongoDB其他评论区聊聊你的 conversationId 是怎么设计的源码见置顶评论。