Spring AI Session API 深度解析:避开 ChatMemory 常见使用误区与最佳实践指南
在Ja va Agent开发领域,构建一个“有记性”的智能体一直是个挑战。很多开发者可能和我一样,最初以为给ChatClient挂上一个MessageWindowChatMemory就万事大吉了,直到遇到用户反馈:“我上周说过不喜欢magic number,这周怎么又生成了?”
问题的根源往往不在于存储配置,而在于对记忆体系的理解。Spring AI的记忆机制是分层的,只接一层,自然无法实现跨会话的连贯体验。这篇文章就来系统梳理一下这两层记忆架构,帮你彻底搞明白。
先说清楚:Spring AI记忆的两层架构
大多数人最先接触的是ChatMemory,它负责保存对话历史,确保大语言模型(LLM)了解当前会话的上下文。这属于短期记忆,核心是解决“这次对话里说过什么”。
从Spring AI 1.1开始引入的Session API,更是对短期记忆的一次“工业级”升级,加入了事件溯源和上下文压缩能力。
然而,真正决定智能体能否“下次还记得你”的,是另一套完全不同的机制:AutoMemoryTools。它专攻长期记忆,负责跨会话、甚至跨应用重启的信息留存。
这两层职责分明,缺一不可。当初我只接了短期记忆,用户的代码风格偏好自然无法留存到下次会话——因为根本没人告诉系统要把这个事实写进长期记忆库。
图:Spring AI Agent两层记忆体系架构,蓝色为AutoMemoryTools长期记忆层,紫色为Session API短期记忆层
AutoMemoryTools:让LLM自己决定记什么
AutoMemoryTools来自spring-ai-agent-utils社区库,其设计灵感直接源于Anthropic Claude Code的记忆系统。核心理念很巧妙:赋予智能体六个文件操作工具,让它自主判断哪些信息值得被长期记录。
6个工具覆盖完整的记忆生命周期
这六个工具模拟了文件系统操作,但被严格沙箱化在memoriesDir目录内。绝对路径和路径穿越都会被拒绝,无需担心智能体误操作系统文件。
记忆文件的格式
每一条长期记忆都是一个独立的Markdown文件,并带有YAML frontmatter元数据,例如:
---
name: code-style-preference
description: 用户代码风格偏好
type: feedback
---
用户不希望代码里出现magic number,变量命名倾向于语义化英文而非拼音缩写。
**Why:** 用户在2026-04-28的对话中明确纠正了生成的代码。
**How to apply:** 生成任何代码时,常量抽取为static final,变量名用完整英文单词。
记忆主要分为四种类型:
user:记录用户角色、目标、专业背景、沟通偏好等。feedback:保存用户验证过的纠正和确认过的方法。project:跟踪正在推进的工作、关键决策和截止日期。reference:存储指向外部系统(如看板、仪表盘)的指针。
所有记忆文件的入口是MEMORY.md索引文件。每次会话开始时,智能体会先读取这个索引,根据摘要决定需要精确加载哪些相关记忆文件,而不是一股脑塞进上下文,这大大提升了效率。
最简接入方式:AutoMemoryToolsAdvisor
接入方式非常简洁,通过组合Advisor即可:
// 依赖:spring-ai-agent-utils
ChatClient chatClient = ChatClient.builder(chatModel)
.defaultAdvisors(
// 1. AutoMemory Advisor:注入系统提示词 + 注册6个工具
AutoMemoryToolsAdvisor.builder()
.memoriesDir("/home/user/.agent/memories")
.build(),
// 2. 短期记忆:保留最近100条消息
MessageChatMemoryAdvisor.builder(
MessageWindowChatMemory.builder().maxMessages(100).build())
.build(),
// 3. 工具调用执行器
ToolCallAdvisor.builder().disableInternalConversationHistory().build()
)
.build();
三个Advisor叠加,就完整接入了两层记忆。AutoMemoryToolsAdvisor会自动注入系统提示词,引导LLM在适当时机读写记忆。
什么情况下LLM会写记忆?
配套的系统提示词会指引LLM在特定场景触发写入操作,例如:用户明确纠正了某个行为(feedback类型)、用户提供了关于自身的新信息(user类型),或项目状态发生变更(project类型)。
值得注意的是,写入决策权在LLM手中,而非框架强制。这意味着记忆质量与所用模型的能力直接相关——Claude Sonnet能写出精准条目,而较弱模型可能遗漏或表述宽泛。此外,还可以配置memoryConsolidationTrigger
踩坑:为什么不要把代码模式存进长期记忆
文档中有一个容易被忽略的警告:代码模式、Git历史、调试方法等内容不应存入AutoMemoryTools。原因很直接:这些内容会随代码库频繁变动,而长期记忆文件不会自动同步更新,很快便会过时,成为干扰噪音。这类信息更适合放在代码库本身的注释或文档中。
Session API:短期记忆的“工业级”升级
在Spring AI 1.1之前,短期记忆的主力是MessageWindowChatMemory,它维护一个固定大小的消息窗口。这种方式简单,但存在明显局限:按消息数而非token数截断,可能导致上下文利用率低;截断时可能破坏工具调用与结果的完整性;且缺乏持久化能力,重启即失。
Session API(属于Agentic Patterns系列)是对此的彻底重新设计,引入了事件溯源模型。
核心概念:Turn(轮次)是原子单位
Session API的最大创新在于将“一轮对话”定义为原子单位:即一条用户消息及其后续所有的助手消息、工具调用和工具结果,直到下一条用户消息出现。
在进行上下文压缩时,保证完整轮次要么全部保留,要么全部丢弃,彻底解决了工具调用与结果被割裂的难题。每个SessionEvent包装了一条消息,并附带了UUID、会话ID、时间戳、分支标签以及用于标记LLM生成摘要的METADATA_SYNTHETIC标志。
4种压缩策略怎么选
Session API提供了多种压缩策略:
SlidingWindow:滑动窗口,保留最近N个事件。TurnWindow:轮次窗口,保留最近N轮完整对话。RecursiveSummarization:递归摘要,智能压缩历史内容。NoCompaction:不压缩,保留全部。
RecursiveSummarization是最智能但也最昂贵的选择。它并非每次都从头摘要,而是维护一个滚动的压缩历史,每次只对新淘汰的内容进行重新摘要,效率更高。摘要结果会被标记为METADATA_SYNTHETIC,以便后续进一步压缩。
选型建议:
- 聊天机器人、单轮问答:
SlidingWindow或TurnWindow足矣。 - 复杂任务Agent(工具调用链长):优先
TurnWindow,保证原子性。 - 长期工作Agent(需保留全局上下文):考虑
RecursiveSummarization,但需权衡LLM调用成本。
// Session API 接入示例
SessionMemoryAdvisor advisor = SessionMemoryAdvisor.builder(sessionService)
.defaultUserId("user-alice")
// 20 轮之后触发压缩
.compactionTrigger(new TurnCountTrigger(20))
// 压缩策略:保留最近 10 个 event,其余滑走
.compactionStrategy(
SlidingWindowCompactionStrategy.builder().maxEvents(10).build()
)
.build();
还有一个隐藏能力:Recall Storage
即使上下文被压缩,Session API仍会保留完整的事件日志,并提供了conversation_search工具。Agent可以通过关键词搜索历史对话,例如在第80轮时查找“第5轮提到的API地址”,而无需在上下文中一直保留早期内容。这个设计参考了MemGPT的Recall Storage模式。
图:Session API四种压缩策略的选择决策流程,以及Recall Storage的保障机制
JDBC持久化:生产环境必须做
Session API默认通过spring-ai-session-jdbc模块,使用两张表(AI_SESSION会话元数据和AI_SESSION_EVENT只追加事件日志)进行持久化,支持PostgreSQL、MySQL等主流数据库。这确保了对话历史可以跨应用重启保留,是生产级短期记忆的基石。
向量库 vs Redis vs 文件系统:长期记忆存储选型
除了AutoMemoryTools的文件系统方案,社区还有另一套思路:利用Redis向量库实现基于语义的记忆检索。
Spring AI + Redis向量存储的架构
这套方案源自Redis社区的实践,核心是将记忆分为两种类型:
- EPISODIC(情节记忆):如个人经历、用户偏好,按时间顺序精确检索。
- SEMANTIC(语义记忆):如通用知识、事实,按语义相似度检索。
存储使用RedisVectorStore,通常配置HNSW近似最近邻算法、COSINE距离度量,能够支持海量向量的高效检索。
关键环节通过两个Advisor实现:Retrieval Advisor在调用LLM前进行向量搜索并注入相关记忆;Recorder Advisor在LLM响应后提取原子事实去重并存入向量库。
两种方案的对比
如何选择?
- AutoMemoryTools文件方案:适合面向个人用户的Agent(每个用户几十到几百条记忆)。优势是结构清晰、可读性好、便于调试,无需额外服务依赖。
- Redis向量方案:适合需要在海量历史记忆中进行语义检索的场景(如企业知识库Agent)。优势是检索能力强、支持多节点共享,但会引入Embedding调用开销和Redis运维成本。
两者并非互斥。完全可以混合使用:用AutoMemoryTools存储“用户偏好”这类精准事实,用Redis向量库存放“历史交互摘要”这类适合语义检索的内容。
Spring AI vs LangChain4j:记忆能力差距在哪里
国内很多文章对比这两个框架时,往往只关注模型支持列表,却很少深入记忆能力这一核心差异。
LangChain4j的记忆体系基于ChatMemory接口,提供了MessageWindowChatMemory和更实用的TokenWindowChatMemory(按Token数控制窗口)。但它没有内置类似AutoMemoryTools的跨会话长期记忆方案,需要开发者自行组合EmbeddingStore等组件来实现,灵活性高但样板代码也多。
Spring AI的优势在于其高层的、开箱即用的抽象。Spring AI 1.1+的Session API和AutoMemoryTools构成了清晰的两层记忆体系,并与Spring Boot的Advisor链深度集成,对Spring生态内的团队来说接入成本极低。AutoMemoryTools将记忆管理的决策权交给LLM本身,这在长期任务中往往比硬编码的规则更有效。
LangChain4j的优势在于更轻量的运行时(Quarkus下约50-100MB)以及对更广泛模型提供商的支持。如果你的应用运行在资源受限环境或依赖特定私有模型,这可能是一个决定因素。
坦白说,到了2026年,两个框架在1.x阶段的功能差距已在缩小。选型的核心考量应该是技术栈适配性:用Spring Boot就选Spring AI,用Quarkus则考虑LangChain4j,不必强行跨栈。
把两层记忆接在一起:完整Agent示例
下面是一个将Session API和AutoMemoryTools整合的完整配置示例,可供参考:
// 前置条件:Spring AI 1.1+,spring-ai-agent-utils
@Configuration
public class AgentConfig {
@Bean
public ChatClient agentChatClient(
ChatModel chatModel,
SessionService sessionService) {
return ChatClient.builder(chatModel)
.defaultSystem("""
你是一个有记忆的 AI 助手。你能记住用户的偏好、项目背景和历史交互。
在回答问题之前,主动检查是否有相关记忆需要加载。
当用户纠正你或透露新的偏好时,主动保存到长期记忆。
""")
.defaultAdvisors(
// 长期记忆层:AutoMemoryTools(跨会话持久化)
AutoMemoryToolsAdvisor.builder()
.memoriesDir(System.getProperty("user.home") + "/.agent/memories")
.build(),
// 短期记忆层:Session API(当前会话 + 上下文压缩)
SessionMemoryAdvisor.builder(sessionService)
.defaultUserId("demo-user")
.compactionTrigger(new TurnCountTrigger(15))
.compactionStrategy(
RecursiveSummarizationStrategy.builder()
.summarizationModel(chatModel)
.build()
)
.build(),
// 工具调用执行器
ToolCallAdvisor.builder()
.disableInternalConversationHistory()
.build()
)
.build();
}
}
关键点说明:
AutoMemoryToolsAdvisor必须放在SessionMemoryAdvisor之前,以确保长期记忆的系统提示词优先注入。- 调用
ToolCallAdvisor.builder().disableInternalConversationHistory()可以避免该Advisor维护多余的对话历史副本。 - 生产环境中,
memoriesDir应对不同用户进行隔离,例如使用{userId}作为子目录。
常见问题
Q:AutoMemoryTools用的是文件系统,部署在多个Pod上怎么办?
A:这是社区版当前的一个限制。多Pod场景下,需要将memoriesDir指向共享存储(如NFS、S3)。或者,可以考虑采用前述的Redis向量方案替代,Redis天生支持多节点共享。社区未来也可能提供JDBC或Redis后端的AutoMemoryTools实现。
Q:Session API的RecursiveSummarization会不会把重要内容总结丢?
A:存在一定风险。但Session API提供了双重保障:一是原始事件日志始终保留,可通过conversation_search工具检索;二是合成摘要会被标记为METADATA_SYNTHETIC,便于监控质量。若业务对信息完整性要求极高,建议仅使用TurnWindow策略,避免LLM摘要。
Q:AutoMemoryTools的6个工具会占用多少token?
A:工具定义本身约占500-800 token(取决于模型编码)。每次对话还需加载MEMORY.md索引(几十到几百token)。总体开销相对于完整对话历史较小,但在极度成本敏感的场景,可考虑手动接入,按需加载特定工具。
Q:Spring AI的ChatMemory和Session API可以同时用吗?
A:不建议。Session API旨在替代而非补充ChatMemory。两者同时使用会导致对话历史被重复记录。根据项目复杂度二选一即可:简单项目用MessageWindowChatMemory;生产项目用Session API的SessionMemoryAdvisor。
Q:LangChain4j有没有类似AutoMemoryTools的东西?
A:没有完全对等的开箱即用方案。LangChain4j的AiServices可通过注解注入记忆,但实现跨会话的长期记忆需要开发者自行组合EmbeddingStore和提取逻辑,代码量会显著增加。
核心判断
综合来看,AutoMemoryTools + Session API的组合,是目前Ja va生态中为Agent提供记忆能力最完整、最开箱即用的方案。短期记忆确保“这次对话不乱”,长期记忆实现“下次还记得你”,两层各司其职,相辅相成。
在这个方案成熟之前,生产环境往往需要拼凑各种临时方案来实现记忆功能。现在,大部分工作可以交给框架了。当然,挑战依然存在,比如AutoMemoryTools文件后端对云原生多副本部署不够友好的问题,可以等待社区推出JDBC版本,或暂时用Redis向量方案作为替代。
如果你或你的团队正在基于Spring AI进行Agent落地,希望这篇梳理能帮助你们避开一些弯路。

