Spring AI Session API:大多数人用 ChatMemory 用错了场景
你好我是《Redis 高手心法》作者码哥前段时间做了个内部 AI 助手接的是 Spring AI给ChatClient挂上了MessageWindowChatMemoryRedis 存对话历史跑起来感觉很顺。直到有用户反馈「我上周告诉过它我不喜欢代码里有 magic number这周它又给我生成了一堆。」去查了一遍才发现问题根源不在 Redis也不在配置——是我对 Spring AI 的记忆体系理解就错了。我以为加一个ChatMemory就解决了「Agent 记性」的问题但实际上 Spring AI 的记忆分两层我只接了一层。这篇文章是那次排查之后的系统梳理。如果你正在做 Spring AI 的 Agent 项目或者准备做这两层记忆的区别是必须搞清楚的事。先说清楚Spring AI 记忆的两层架构绝大多数人最先碰到的是ChatMemory用来保存对话历史让 LLM 知道上下文。这是短期记忆解决的是「这一次会话里说过什么」。Spring AI 1.1 之后引入了 Session API进一步强化了这一层加上了事件溯源模型和上下文压缩能力。但另一层——长期记忆——靠的是完全不同的机制AutoMemoryTools。它解决的是「跨会话、跨重启Agent 要记住的那些事」。两层各司其职缺一不可维度短期记忆Session API / ChatMemory长期记忆AutoMemoryTools生命周期当前会话永久持久化存储位置内存 / JDBC / Redis文件系统Markdown 文件内容完整对话历史精选事实、偏好、决策谁来管理Spring AI 框架自动管理LLM 自主决定写入/读取适合场景让 LLM 记住上下文让 Agent 记住用户偏好和项目决策我当时只接了短期记忆这层所以用户的代码风格偏好在下次会话里当然没了——压根没人把这个事实写进长期记忆。图Spring AI Agent 两层记忆体系架构蓝色为 AutoMemoryTools 长期记忆层紫色为 Session API 短期记忆层AutoMemoryTools让 LLM 自己决定记什么AutoMemoryTools 是spring-ai-agent-utils社区库提供的工具集设计灵感直接来自 Anthropic Claude Code 的记忆系统。核心思路很简单给 Agent 6 个文件操作工具让它自己决定哪些事情值得记下来。6 个工具覆盖完整的记忆生命周期工具作用MemoryView读文件内容带行号或列目录MemoryCreate创建带 frontmatter 的记忆文件MemoryStrReplace精确替换文件中的某段内容MemoryInsert在指定行之后插入内容MemoryDelete删除文件或目录MemoryRename移动/重命名文件自动更新索引这 6 个工具和文件系统工具很像但有一个关键限制全部操作被沙箱化在memoriesDir目录内。绝对路径会被拒绝路径穿越会被检测你不用担心 Agent 跑去写/etc/hosts。记忆文件的格式每一条长期记忆是一个 Markdown 文件带 YAML frontmatter--- name:code-style-preference description:用户代码风格偏好 type:feedback --- 用户不希望代码里出现magicnumber变量命名倾向于语义化英文而非拼音缩写。 **Why:**用户在2026-04-28的对话中明确纠正了生成的代码。 **Howtoapply:**生成任何代码时常量抽取为staticfinal变量名用完整英文单词。记忆类型有 4 种user角色、目标、专业背景、沟通偏好feedback用户验证过的纠正和确认过的方法project正在推进的工作、决策、截止日期reference外部系统的指针看板、仪表盘地址所有记忆文件的入口是MEMORY.md这是一个索引文件Agent 在每次会话开始时会先读它决定要加载哪些相关记忆# Memory Index - [code-style-preference.md](code-style-preference.md) — 用户代码风格偏好不要 magic number - [project-deadline.md](project-deadline.md) — 当前项目 Q2 上线计划这个「索引优先」的设计很关键即使长期记忆库积累了几十个文件每次 Agent 也不需要把所有文件全塞进上下文而是看索引摘要按需精确加载。最简接入方式AutoMemoryToolsAdvisor// 依赖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 会写记忆AutoMemoryTools 配套的系统提示词会指引 LLM 在以下场景触发写入用户明确纠正了它的某个行为feedback类型用户给出了关于自己的新信息user类型项目状态有变化project类型值得注意的是LLM 自己决定写什么不是框架强制写。这意味着记忆质量和你用的模型能力直接相关——Claude Sonnet 能写出很精准的记忆条目弱模型可能会漏写或写得太宽泛。还有一个机制叫memoryConsolidationTrigger可以配置每隔 N 轮对话或每 5% 概率触发一次记忆整理防止积累太多冗余记忆。踩坑为什么不要把代码模式存进长期记忆文档里有一段容易被忽略的警告代码模式、git 历史、调试方法不要存进 AutoMemoryTools。这类内容应该存在代码库本身注释、CLAUDE.md、README里而不是 Agent 的个人记忆。原因很直接这些内容会随代码变化长期记忆文件不会自动同步代码库的变化很快就会过时变成噪音。Session API短期记忆的「工业级」升级在 Spring AI 1.1 之前短期记忆的主要实现是MessageWindowChatMemory——维护一个固定大小的消息窗口简单但有明显局限按消息数量截断不按 token 数量——实际上下文利用率低截断时可能切断一半的工具调用 工具结果LLM 看到孤立的 tool result 会困惑没有持久化重启即失Spring AI 的 Session APIAgentic Patterns 系列第 7 篇是对这层的彻底重设计引入了事件溯源模型。核心概念Turn轮次是原子单位Session API 的最大创新是把「一轮对话」定义为原子单位一条 UserMessage 之后所有 AssistantMessage、ToolCall、ToolResult直到下一条 UserMessage 出现。压缩时保证完整轮次要么全保留要么全丢弃不会出现「保留了工具调用但丢掉了对应的工具结果」的情况。这解决了MessageWindowChatMemory最头疼的问题。每个SessionEvent包装了一条Message附带了UUID 和 sessionId时间戳branch label用于多 Agent 并行的分支隔离METADATA_SYNTHETIC标志标记这是 LLM 生成的摘要不是真实消息4 种压缩策略怎么选策略是否需要 LLM适用场景SlidingWindow否成本敏感短期上下文TurnWindow否对话结构清晰N 轮之前的内容不重要TokenCount否严格的 token 上限控制RecursiveSummarization是长对话历史信息需要保留但要压缩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 可以用关键词搜索历史对话。比如 Agent 在第 80 轮突然需要查找「第 5 轮说过的某个 API 地址」可以直接调用工具检索而不需要在上下文里一直保留第 5 轮的内容。这个设计参考了 MemGPT 的 Recall Storage 模式。图Session API 四种压缩策略的选择决策流程以及 Recall Storage 的保障机制JDBC 持久化生产环境必须做Session API 默认的spring-ai-session-jdbc用两张表存储-- AI_SESSION会话元数据 -- AI_SESSION_EVENTappend-only 事件日志支持 PostgreSQL、MySQL、MariaDB、H2。对话历史跨重启保留这才是生产级的短期记忆。向量库 vs Redis vs 文件系统长期记忆存储选型除了 AutoMemoryTools 的文件系统方案社区里有另一套思路用 Redis 向量库做语义记忆检索。Spring AI Redis 向量存储的架构这套方案来自 Redis 官方博客核心是两种记忆类型EPISODIC情节记忆个人经历、用户偏好按时间顺序精确检索SEMANTIC语义记忆通用知识、事实按语义相似度检索存储用RedisVectorStore// Redis 向量索引配置 RedisVectorStore.RedisVectorStoreConfig config RedisVectorStore.RedisVectorStoreConfig.builder() .withIndexName(longTermMemoryIdx) .withVectorAlgorithm(Algorithm.HSNW) // 近似最近邻 .withContentFieldName(content) .withEmbeddingFieldName(embedding) .addMetadataField(MetadataField.tag(memoryType)) .addMetadataField(MetadataField.tag(userId)) .build();向量维度是 384 维 FLOAT32使用 COSINE 距离。Redis 8 号称可以支持 10 亿向量不降低延迟在规模上不是瓶颈。两个关键 Advisor// Retrieval Advisor调用 LLM 之前向量搜索最相关记忆并注入 system prompt // Recorder AdvisorLLM 响应之后提取原子事实并去重存入向量库两种方案的对比维度AutoMemoryTools文件系统Redis 向量库搜索方式精确匹配LLM 自行判断相关性语义向量相似度记忆管理LLM 自主决策Advisor 自动提取运维复杂度低文件读写中Redis 向量索引维护记忆可读性高Markdown 文件人可直接读低向量 JSON不直观适合规模小到中百条级别中到大万条级别冷启动LLM 读 MEMORY.md 索引每次请求自动语义检索怎么选如果 Agent 面向个人用户每个用户几十到几百条记忆AutoMemoryTools 的文件方案完全够用而且可读性好便于调试。如果 Agent 需要在海量历史记忆中语义检索比如企业知识库 AgentRedis 向量方案更合适但带来了额外的 embedding 调用开销和 Redis 运维负担。两种方案并不互斥——你可以用 AutoMemoryTools 存「用户偏好」这类精准事实用 Redis 向量库存「历史交互摘要」这类语义内容。图两种长期记忆存储方案的全面对比以及适用场景分析Spring AI vs LangChain4j记忆能力差距在哪里国内文章比较两个框架时经常只比「支不支持某个模型」记忆能力这块鲜有人细讲。LangChain4j 的记忆体系LangChain4j 的聊天记忆基于ChatMemory接口有MessageWindowChatMemory和TokenWindowChatMemory两种实现。TokenWindowChatMemory按 token 数量而非消息数量控制窗口这一点比 Spring AI 默认的实现更实用。LangChain4j 没有内置类似 AutoMemoryTools 的跨会话长期记忆方案需要自己组合EmbeddingStore支持向量存储来实现语义记忆检索灵活但需要更多 boilerplate。Spring AI 的优势Spring AI 1.1 的 Session API 和 AutoMemoryTools 提供了更高层的抽象两层记忆体系开箱即用与 Spring Boot Advisor 链深度集成。对于已经在 Spring 生态里的团队接入成本极低。AutoMemoryTools 最大的创新点是把记忆管理的决策权交给 LLM 本身而不是用代码规则决定「什么该存」这在长期任务场景下效果明显更好——因为 LLM 能理解语义重要性代码规则做不到。LangChain4j 的优势运行时内存占用更小Quarkus 下 50-100MB vs Spring Boot 的 150-300MB如果你的 Agent 跑在资源受限的环境这是明显优势。模型提供商支持更广有些小众或私有部署模型只有 LangChain4j 有适配。坦白说2026 年两个框架都到了 1.x 阶段功能差距已经在收窄。选型的核心还是生态适配性用 Spring Boot 就用 Spring AI用 Quarkus 就用 LangChain4j不用强行跨栈。把两层记忆接在一起完整 Agent 示例下面是一个把 Session API 和 AutoMemoryTools 都接上的完整示例读者可以直接跑// 前置条件Spring AI 1.1spring-ai-agent-utils // 依赖 // spring-ai-starter-model-openai (或其他模型) // spring-ai-session-jdbc // spring-ai-agent-utils (社区库) Configuration publicclass 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(); } } RestController RequestMapping(/api/agent) publicclass AgentController { privatefinal ChatClient agentChatClient; PostMapping(/chat) public String chat(RequestParam String userId, RequestParam String message) { return agentChatClient.prompt() .user(message) // Session API 通过 AdvisorContext 传递用户 ID .advisorParam(SessionMemoryAdvisor.USER_ID_PARAM, userId) .call() .content(); } }关键点说明AutoMemoryToolsAdvisor必须在SessionMemoryAdvisor之前放因为长期记忆的系统提示词需要在短期记忆之前注入disableInternalConversationHistory()避免ToolCallAdvisor自己维护一份多余的对话历史memoriesDir对不同用户应该隔离实际项目中用{userId}做子目录常见问题QAutoMemoryTools 用的是文件系统部署在多个 Pod 上怎么办A这是目前社区版 AutoMemoryTools 的一个限制。多 Pod 场景下你需要把memoriesDir指向共享存储NFS、S3 挂载、云文件服务。或者考虑用 Redis 向量方案替代Redis 天然支持多节点共享。后续版本可能会提供 JDBC/Redis 作为后端的 AutoMemoryTools 实现。QSession API 的 RecursiveSummarization 会不会把重要内容总结丢A有这个风险但 Session API 有两道保障(1) 原始事件日志永远保留即使被压缩可以通过conversation_search工具按关键词检索(2) Synthetic 摘要会标记METADATA_SYNTHETIC工程师可以监控摘要质量。如果业务对信息完整性要求极高建议只用TurnWindow策略不用 LLM 摘要。QAutoMemoryTools 的 6 个工具会占用多少 tokenA工具定义本身大约 500-800 token取决于模型如何编码 JSON schema。每次对话开始时还需要加载MEMORY.md索引大约几十到几百 token。整体 overhead 相对于完整对话历史来说比较小但对极度成本敏感的场景可以考虑 Option B手动接入按需只加载特定工具。QSpring AI 的 ChatMemory 和 Session API 可以同时用吗A不建议同时用。Session API 是对 ChatMemory 的替代不是补充两个都接会导致对话历史被记两份。选一个简单项目用MessageWindowChatMemory加MessageChatMemoryAdvisor生产项目用 Session API 的SessionMemoryAdvisor。QLangChain4j 有没有类似 AutoMemoryTools 的东西A没有开箱即用的等价物。LangChain4j 的AiServices可以通过注解注入记忆但长期跨会话记忆需要自己组合EmbeddingStore和提取逻辑来实现代码量显著更多。我的判断AutoMemoryTools Session API 这个组合是目前 Java 生态里 Agent 记忆最完整的开箱方案。短期记忆解决了「这次对话不乱」长期记忆解决了「下次还记得你」两层各司其职不互相替代。说实话在这个方案出来之前我们在生产上跑了好几个月的 Spring AI Agent靠着各种临时方案拼凑记忆层——把偏好塞进 system prompt 硬编码、每次请求先 RAG 一遍历史记录。现在有了这套 API很多事可以丢给框架了。还没解决的问题是 AutoMemoryTools 的文件系统后端对云原生多副本部署不够友好。这个等社区推出 JDBC 版本或者自己先用 Redis 向量方案顶着。如果你身边有同事正在做 Spring AI 的 Agent 落地这篇可以直接甩给他少走点弯路。