Java Agent 上下文总是丢?我排查了 Spring AI 会话记忆的 5 个误区
很多人第一次接 Spring AI 的记忆能力,心里默认有个预期:
“我既然已经把 ChatMemory 接进来了,模型就应该记得刚才聊过什么。”
结果真跑起来以后,经常是这种场面:
- 第一轮还能答对
- 第二轮开始就忘
- 一旦换会话或者换接口,前文像没发生过
- 你以为是模型上下文太短,最后发现问题根本不在模型
这个坑比 Tool Calling 更容易误判。因为它不像报错那样明显,很多时候只是“记忆效果很差”,但你又说不清到底是哪一层丢了。
这篇就专门拆 Spring AI 里最常见的 5 个误区。
先看一个最小可用例子
Spring AI 的 ChatMemory 不是自己生效的。你得先把它接到 ChatClient 的 advisor 链里。
一个最小示例长这样:
ChatMemory chatMemory = MessageWindowChatMemory.builder().build();
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(MessageChatMemoryAdvisor.builder(chatMemory).build())
.build();
String conversationId = "007";
String answer = chatClient.prompt()
.user("我的名字叫 James Bond")
.advisors(a -> a.param(ChatMemory.CONVERSATION_ID, conversationId))
.call()
.content();
第二轮继续用同一个 conversationId,模型才有机会拿到同一条会话的记忆。
如果你现在的代码里根本没有 MessageChatMemoryAdvisor,或者每次请求都没传稳定的 conversationId,那后面很多“为什么记不住”的讨论其实都不用展开。
误区 1:只要有 ChatMemory bean,模型就会自动记住上下文
这个误区很常见。
Spring AI 官方文档说得很清楚:框架会自动配置一个 ChatMemory bean。默认情况下,它背后用的是 InMemoryChatMemoryRepository,上层是 MessageWindowChatMemory。
但“自动配置了 ChatMemory bean”不等于“你的 ChatClient 自动启用了记忆”。
真正把记忆挂进请求链路的是 advisor,比如:
MessageChatMemoryAdvisorPromptChatMemoryAdvisorVectorStoreChatMemoryAdvisor
如果你只是项目里存在一个 ChatMemory bean,但调用链上根本没挂 advisor,那模型每次看到的仍然是裸 prompt。
这也是为什么很多人会说:“我都注入 ChatMemory 了,怎么还是不记得?”
因为你注入了,不代表你用了。
误区 2:ChatMemory 就是聊天历史,应该完整保留所有消息
这个想法很自然,但 Spring AI 官方文档专门提醒过:memory 和 history 不是一个东西。
文档里的定义很直接:
Chat Memory是为了维持当前上下文而保留的相关信息Chat History是完整对话记录
Spring AI 还明确说了,ChatMemory 并不是存完整聊天历史的最佳地方。如果你要保存所有消息,应该用别的存储方式,比如基于 Spring Data 的完整持久化方案。
这意味着什么?
意味着你不能拿 ChatMemory 去要求它同时完成两件事:
- 给模型提供精简且可控的上下文
- 给业务系统保留一份一字不差的聊天档案
这两个目标经常冲突。
如果你把这两个概念混在一起,就会出现一种非常典型的误判:
“怎么少了一部分旧消息?是不是 memory 坏了?”
很多时候不是坏了,而是它本来就不是历史归档系统。
误区 3:Spring AI 默认记忆是持久化的
如果你没额外配置 repository,这个想法通常是错的。
官方文档写得很明确:默认会用 InMemoryChatMemoryRepository。名字已经说明问题了,它就是内存版。
所以你会遇到这些现象:
- 应用一重启,记忆没了
- 多实例部署后,每个实例的记忆各管各的
- 本地开发看起来能用,一上测试环境就开始“串台”或“失忆”
这类问题经常不是模型的问题,也不是 advisor 的问题,而是你把“能跑”误当成了“可持久化”。
如果你的场景需要跨重启保留会话,就不要停在默认内存实现上。Spring AI 官方文档已经给了多种 repository 选项,比如 JDBC、Cassandra、Neo4j、MongoDB。
一句话概括就是:
默认记忆适合验证思路,不适合你把它直接当线上会话存储。
误区 4:上下文一丢,就是模型太笨或者 token 不够
不一定。先看看窗口是不是自己把旧消息挤掉了。
官方文档里提到,MessageWindowChatMemory 的默认窗口大小是 20 条消息。超过以后,老消息会被移除,但系统消息会保留。
也就是说,哪怕你根本没到模型的上下文上限,Memory 层也可能已经先把旧消息裁掉了。
这件事非常容易被忽略,因为很多人以为“我就聊了十几轮,模型怎么会忘”。但如果一轮里消息多、工具多、系统消息也参与进来,窗口比你想象得更快被顶满。
再补一个细节。Spring AI 文档还提到:
- 系统消息会被特殊处理
- 新系统消息进来时,旧系统消息可能被替换
所以如果你一边用记忆,一边频繁动态改 system prompt,别惊讶最终上下文会变形。
很多“记忆漂了”的问题,实际上不是模型理解错了,而是 memory 的窗口策略已经把你以为还在的上下文处理掉了。
误区 5:Tool Calling 过程中的中间消息也会自动进记忆
这个坑很隐蔽,官方文档也专门点出来了。
Spring AI 在 Chat Memory 文档里明确写道:当前实现下,执行 Tool Calling 时和大模型交换的中间消息,并不会被自动存进 memory。
这意味着什么?
如果你的 Agent 很依赖工具调用链,比如:
- 先调搜索工具
- 再调数据库工具
- 最后根据两轮工具结果回答
那你最后看到的记忆,不一定完整反映了中间推理链。
这类场景下,如果你还指望 memory 自动帮你保留所有中间状态,后面排障会很痛苦。因为你看到的只是“最终结果”和部分消息,不是完整过程。
Spring AI 给出的方向也很明确:如果你需要更强的可控性,要考虑 user-controlled tool execution。
所以这不是你记忆配错了,而是当前实现边界本来就在这里。
我会怎么排这个问题
如果我接手一个“上下文总是丢”的 Spring AI 项目,我会按这个顺序查:
- 先看 ChatClient 上有没有真正挂 memory advisor
- 再看每次请求有没有稳定的
conversationId - 再确认你要的是 memory 还是 full history
- 再看当前 repository 是内存还是持久化
- 最后再看窗口大小和 Tool Calling 中间消息是否被误以为会自动保存
这个顺序的好处是,前 3 步很便宜,而且经常一查就中。
最后给一份速查清单
- 你的
ChatClient是否真的挂了MessageChatMemoryAdvisor或其他 memory advisor? - 每次请求是否传了稳定的
conversationId? - 你现在想解决的是“上下文连续性”,还是“完整历史归档”?
- 你是不是还停在默认的
InMemoryChatMemoryRepository? - 应用重启后记忆消失,是不是其实符合默认实现?
MessageWindowChatMemory的窗口大小够不够?- 你有没有频繁替换 system prompt,导致上下文结构变化?
- 你是不是误以为 Tool Calling 的中间消息也会自动进 memory?
结尾
Spring AI 的记忆能力不算难用,但它特别容易被“想当然”。很多问题不是你没接 memory,而是你把 memory 想成了历史系统、持久化系统、甚至完整推理轨迹系统。
下一篇我继续写另一个同样高频的坑:别再把 Prompt 当配置文件了:Spring AI 提示词失控的 5 个原因。会话记忆和 prompt 设计这两件事,经常是一起把项目带偏的。
参考资料
- Spring AI Chat Memory Reference: https://docs.spring.io/spring-ai/reference/api/chat-memory.html
- Spring AI Advisors API: https://docs.spring.io/spring-ai/reference/api/advisors.html
- Spring AI Tool Calling Reference: https://docs.spring.io/spring-ai/reference/api/tools.html
说明
文中的以下结论直接来自 Spring AI 官方文档:
ChatMemory和ChatHistory是两个不同概念- 默认会自动配置
ChatMemorybean,且默认使用内存 repository 和MessageWindowChatMemory MessageWindowChatMemory默认窗口大小是 20 条消息ChatMemory不是保存完整聊天历史的最佳位置- Tool Calling 过程中的中间消息当前不会自动写入 memory
文中的这些表达属于我的工程归纳,不是官方原句:
- “你注入了,不代表你用了”
- “默认记忆适合验证思路,不适合直接当线上会话存储”
- “很多记忆漂移问题,实际上是 Memory 层先裁掉了上下文”
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)