请介绍一下 AI Agent 的记忆机制?实际开发中怎么设计记忆模块?


一则或许对你有用的小广告

欢迎加入小哈的星球,你将获得:专属的实战项目(4个项目都能学) / 1v1 提问 / 简历修改 / Java 学习路线 / 社群讨论 / 学习打卡 / 每月赠书

  • 《Spring AI 项目实战(问答机器人、RAG 智能客服、联网搜索)》已完结,基于 Spring AI + Spring Boot 3.x + JDK 21...查看介绍

  • 《从零手撸:仿小红书(微服务架构)》 已完结,基于 Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...查看介绍;演示链接:http://116.62.199.48:7070/

  • 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接:http://116.62.199.48/

  • 新开坑项目:《从零手撸:秒杀系统高并发优化实战》 正在更新中...,查看介绍

截止目前,星球内专栏累计输出 150w+ 字,讲解图 5110+ 张,还在持续爆肝中.. 后续还会上新更多项目,已有 4700+ 小伙伴加入学习,欢迎点击围观

面试考察点

  1. 概念分层:你是不是只会说 "长短期记忆"?业界早就演进出 短期 / 长期 + 语义 / 情景 / 程序 的多维分类了,每种记忆存什么、怎么存、什么时候用,能不能讲清楚?
  2. 工程落地:你能不能独立设计一个可扩展的记忆模块?存储选型(关系库 / Redis / 向量库)、上下文窗口管理(滑动窗口、Token 截断、摘要压缩)、多会话隔离这几个关键点,心里有没有数?
  3. 框架熟练度Spring AIChatMemory + ChatMemoryRepositoryLangChain4jMessageWindowChatMemory / TokenWindowChatMemory 这些主流 Java 方案,你是真的写过 demo,还是只看过几篇博客?
  4. 前沿意识:mem0、Letta(前身就是那个大名鼎鼎的 MemGPT)、Zep 这些专门做 Agent 记忆的框架,你听说过吗?它们解决的核心问题是什么?

核心答案

AI Agent 的记忆机制,说白了就是 "模仿人类大脑的记忆系统",让 Agent 在多轮交互、跨会话、跨用户场景下,还能保持上下文连贯、还能记住你是谁。

业界主流的分类方式是从两个维度切:

维度 分类 作用 典型存储
按时效 短期记忆(Short-Term / Working Memory) 当前对话的上下文窗口 LLM Context、Redis
长期记忆(Long-Term Memory) 跨会话的持久化信息 向量库、关系库、图数据库
按认知类型 语义记忆(Semantic) 事实、概念、用户画像 结构化存储 / 向量库
情景记忆(Episodic) 过去的交互事件 向量库
程序记忆(Procedural) 技能、SOP、工具用法 系统提示词、规则库

实际开发设计记忆模块,思路就九个字:分层存储、动态召回、容量治理。短期记忆管好 "当前这轮对话",长期记忆靠检索把 "相关的历史信息" 拉回到上下文里,再配合摘要压缩和遗忘机制,把 Token 成本压住。

一句话总结:Agent 的记忆就是把人的 "工作记忆 + 长期记忆" 搬进工程里,在 LLM 有限的上下文窗口和无限的对话历史之间,架一套 "分层缓存 + 按需换入换出" 的调度系统

深度解析

一、Agent 记忆的整体架构

先上一张全景图,脑子里先有个整体印象:

AI Agent 记忆架构图
AI Agent 记忆架构图

这张图把 Agent 记忆的完整流转画清楚了。关键是一个双向流动

  • 短期 → 长期:对话过程中,把重要的信息(比如用户提到的偏好、关键决策、还没做完的任务)沉淀 到长期记忆里,这一步叫 Memory Consolidation(记忆巩固)
  • 长期 → 短期:新一轮对话开始时,根据当前问题从长期记忆里 召回 相关信息,塞进 LLM 的上下文,这一步说白了就是一次 RAG 检索

把这个双向流动想明白,Agent 记忆这事就通了一大半。剩下的都是存储选型和工程细节。

二、短期记忆:管好当前这轮对话

短期记忆就是 LLM 的 "工作台",说白了就是当前上下文窗口里的那些东西。但 LLM 的上下文窗口是有上限的(哪怕是 Claude 这种 200K Token 的模型,也不能一直往里塞),所以短期记忆最头疼的问题是:怎么在有限的窗口里,留住最有价值的信息?

业界主流有 3 种策略:

策略 做法 优点 缺点
滑动窗口 只保留最近 N 条消息 实现简单、稳定 早期对话信息丢失
Token 窗口 保留不超过 Token 上限的消息 精确控制成本 同上,且需要 Tokenizer
摘要压缩 定期把旧消息压缩成摘要 保留长程语义 实现复杂、有信息损耗

生产环境一般两种混着用:滑动窗口 + 摘要压缩。比如保留最近 20 条原始消息,再带上前 N 条的摘要,短期细节和长程语义就都顾上了。

这块 Java 生态里有现成的轮子可以直接用:

1. LangChain4j 的 ChatMemory

LangChain4j 提供了两种主流实现:

  • MessageWindowChatMemory:基于消息条数的滑动窗口
  • TokenWindowChatMemory:基于 Token 数的滑动窗口
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.memory.chat.TokenWindowChatMemory;
import dev.langchain4j.memory.ChatMemory;
import dev.langchain4j.model.openai.OpenAiChatModel;

// 方式一:基于消息条数的滑动窗口(保留最近 20 条消息)
ChatMemory messageWindow = MessageWindowChatMemory.builder()
        .maxMessages(20)
        .id("user-001-session-1")  // 通过 @MemoryId 实现多用户/多会话隔离
        .build();

// 方式二:基于 Token 数的滑动窗口(更精确控制成本)
ChatMemory tokenWindow = TokenWindowChatMemory.builder()
        .maxTokens(4000, new OpenAiTokenizer("gpt-4o"))
        .id("user-001-session-1")
        .build();

// 配合 @AiService 声明式使用,框架自动管理记忆
interface Assistant {
    String chat(@MemoryId String sessionId, @UserMessage String userMessage);
}

Assistant assistant = AiServices.builder(Assistant.class)
        .chatLanguageModel(OpenAiChatModel.withApiKey("${API_KEY}"))
        .chatMemoryProvider(sessionId -> MessageWindowChatMemory.builder()
                .maxMessages(20)
                .id(sessionId)
                .build())
        .build();

这段代码有两个地方值得多说一句:

  • @MemoryId 这个注解是 多会话隔离的关键。每个用户、每个会话都有自己独立的记忆空间,互不打架
  • chatMemoryProvider 支持按 sessionId 动态创建记忆,这玩意儿生产环境必须上(总不能所有用户共用一份记忆吧)

2. Spring AI 的 ChatMemory + ChatMemoryRepository

Spring AI 把记忆抽象成两层:

  • ChatMemory:负责记忆的 读写逻辑(增删改查消息、窗口管理)
  • ChatMemoryRepository:负责 持久化存储(内存、JDBC、Redis 等多种实现)
import org.springframework.ai.chat.memory.ChatMemory;
import org.springframework.ai.chat.memory.ChatMemoryRepository;
import org.springframework.ai.chat.memory.MessageWindowChatMemory;
import org.springframework.ai.chat.messages.Message;

// 通过 Repository 实现持久化(JDBC / Redis 等开箱即用)
ChatMemoryRepository repository = ChatMemoryRepository.builder()
        .build();  // 默认 InMemory,生产环境换 JdbcChatMemoryRepository / RedisChatMemoryRepository

// 构建 ChatMemory,指定对话窗口
ChatMemory chatMemory = MessageWindowChatMemory.builder()
        .maxMessages(20)
        .chatMemoryRepository(repository)
        .build();

// Spring AI 1.0.x 提供的官方 Starter:
// - spring-ai-starter-model-chat-memory-repository-jdbc
// - spring-ai-starter-model-chat-memory-repository-redis

Spring AI 的设计更有 "Spring 味儿",靠 Starter + 自动装配,把持久化这件事做得相当干净。生产环境我比较推荐 JDBC + Redis 双层缓存 的搭配:Redis 扛高并发读,JDBC 兜底数据可靠性。

三、长期记忆:跨会话的 "知识沉淀"

短期记忆搞定的是 "当前对话",但用户上周告诉你的偏好、三个月前跑完的一次任务,这种就得靠 长期记忆 来存了。

长期记忆怎么设计,主要得先想清楚三件事:

  1. 存什么 —— 用户画像、历史对话片段、关键事实、决策结果
  2. 怎么存 —— 向量库为主,关系库 / 图数据库为辅
  3. 怎么召回 —— 向量检索 + 关键词检索(Hybrid Search)+ 重排序

一个典型架构如下:

Agent 长期记忆架构图
Agent 长期记忆架构图

这张图重点看两边:

  • 写入侧:对话产生的新记忆 千万别无脑全存,得先让 LLM 跑一遍 "记忆提取"(Memory Extraction),把 "用户说他是 Java 程序员"、"用户偏好早上 9 点推送" 这类高价值信息抽出来,再结构化存下来。mem0 就是这么干的,它从每轮对话里识别显著事实(salient facts),蒸馏成简洁的自然语言记忆条目
  • 读取侧:召回时走 Hybrid Search(向量 + 关键词),再过一道 Rerank,保证塞进上下文的是最相关的那几条记忆

一个简单的长期记忆写入 / 检索代码示例(基于 Spring AI):

import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.document.Document;
import java.util.List;

@Service
public class AgentLongTermMemoryService {

    private final VectorStore vectorStore;  // Milvus / PgVector / Redis Vector
    private final ChatClient chatClient;

    public AgentLongTermMemoryService(VectorStore vectorStore, ChatClient chatClient) {
        this.vectorStore = vectorStore;
        this.chatClient = chatClient;
    }

    /**
     * 记忆巩固:从一轮对话里提取关键事实,写入长期记忆
     * 这一步是长期记忆质量的关键,建议用成本较低的小模型(如 GPT-4o-mini / Qwen-Turbo)
     */
    public void consolidate(String userId, String userMessage, String assistantReply) {
        String prompt = """
            从以下对话中抽取关于用户的关键事实(偏好、身份、目标、重要决策),
            每条事实一行,没有就输出 EMPTY。

            用户: %s
            助手: %s
            """.formatted(userMessage, assistantReply);

        String facts = chatClient.prompt(prompt).call().content();
        if ("EMPTY".equalsIgnoreCase(facts.trim())) return;

        // 结构化写入向量库,metadata 带上 userId 用于过滤
        Document doc = new Document(facts,
                Map.of("userId", userId, "type", "user_profile", "ts", System.currentTimeMillis()));
        vectorStore.add(List.of(doc));
    }

    /**
     * 记忆召回:根据当前问题检索相关历史记忆
     */
    public String recall(String userId, String question, int topK) {
        List<Document> docs = vectorStore.similaritySearch(
                SearchRequest.builder()
                        .query(question)
                        .topK(topK)
                        .filterExpression("userId == '" + userId + "'")  // 按用户隔离
                        .build());
        return docs.stream().map(Document::getText).collect(Collectors.joining("\n"));
    }
}

这段代码搭出来的是一个 能跑的最小长期记忆闭环:抽取 → 写入 → 召回。生产环境还得补上 Rerank、TTL 过期、去重、冲突检测(用户改主意了怎么办)这些治理逻辑。

四、业界前沿:Letta / mem0 / Zep 这些专业框架在做什么

前面聊的都是 "自己造轮子" 的玩法,但到 2026 年,业界已经有相当成熟的记忆框架可以直接拿来用,面试时能把这几个名字说清楚就很加分

框架 核心思路 适用场景
Letta(前身 MemGPT) 借鉴操作系统 虚拟内存 + 分页 的思想,把记忆分层为 core / recall / archival,由 LLM 自己决定什么时候换入换出 长期伴侣型 Agent、个性化助手
mem0 从对话里 抽取事实 蒸馏成简洁记忆条目,支持向量 + 图混合存储,提供 add / search / update / delete API 通用记忆层,可嵌入任意 Agent
Zep 时序记忆图(Temporal Knowledge Graph),擅长处理 时序查询和多跳推理 需要理解 "上个月"、"之前提到的那个项目" 这类时间相关记忆
Cognee 基于 知识图谱 + 向量 的混合方案,强调数据的可解释性 企业知识库型 Agent

这几个框架其实都指向同一个趋势:Agent 记忆正在从 "直接塞进 Prompt" 演进成 "独立的记忆服务",就跟当年数据库从应用内嵌走向独立中间件一个道理。其中 Letta 的分页思想特别值得拎出来讲,面试里说出来相当亮眼:

Letta 把 Agent 记忆类比成操作系统的内存管理:

  • Core Memory(核心记忆):相当于 CPU 寄存器,永远在上下文里,存最关键的 user persona 和任务状态
  • Recall Memory(回忆记忆):相当于内存,存完整的对话历史,按需检索
  • Archival Memory(归档记忆):相当于硬盘,存大量背景知识,通过向量检索按需召回

Agent 通过 function call 自己决定什么时候把信息从 archival 换进 core,"有限窗口下的无限记忆" 就这么实现了。

五、生产环境设计记忆模块的 7 条经验

下面这些是实际项目踩坑后攒下来的,面试时随便甩出两三条就很能打:

  1. 短期记忆用 Redis,长期记忆用向量库 —— 别把所有东西都塞进 MySQL,也别什么都存向量库
  2. 一定要做记忆巩固(Extraction) —— 无脑把原始对话塞进向量库是新人最容易犯的错,效果又差又贵
  3. 多会话隔离用 userId + sessionId —— @MemoryId 必须带,别让张三的对话串到李四头上
  4. 加 Rerank —— 和 RAG 一样,记忆召回也强烈建议过一道 Cross-Encoder 重排序
  5. TTL + 遗忘机制 —— 不是所有记忆都要永久保留,设计遗忘策略(按时间衰减、按访问频次衰减)反而能让重要记忆更突出
  6. 冲突处理 —— 用户上周说喜欢 Java,这周改口喜欢 Go,你要能识别并更新,而不是把两条矛盾的记忆都召回
  7. 记忆也要评估 —— 别只看回答好不好,要单独评估记忆召回的 Recall@K、记忆是否被正确更新等指标

六、常见踩坑:上下文爆炸

这个坑必须单独拎出来说一下。不少人设计记忆模块的时候没做好容量治理,结果碰到一个话痨用户连聊了 200 轮,直接把 100K Token 的上下文撑爆,LLM 冷不丁甩你一句 "context length exceeded" 报错。

解决思路其实就三招:

  • 硬性兜底:用 TokenWindowChatMemory 把 Token 上限焊死
  • 软性优化:旧消息定期做摘要压缩
  • 分级注入:长期记忆召回的结果 不直接进短期记忆,而是按优先级动态拼到 Prompt 里,超出上限就降级

面试高频追问

  1. 追问一:Agent 的记忆和 RAG 的知识库有什么区别?

    这个追问命中率特别高。区别主要在这:

    • RAG 知识库静态的、公共的、面向 "事实",比如公司产品手册、法律法规
    • Agent 记忆动态的、个性化的、面向 "交互历史",比如这个用户上周问过什么、他的偏好是什么

    技术实现两边高度相似(都是向量检索),但 写入策略完全不一样:RAG 是批量导入,Agent 记忆是在线持续更新的。

  2. 追问二:怎么处理用户偏好的变更?

    • 写入时做 冲突检测:新记忆和已有记忆矛盾时,用 LLM 判断是覆盖还是追加
    • 给每条记忆打 时间戳和置信度,召回时优先新鲜且高置信度的
    • mem0 在这块做得比较到位,内置了 updatedelete 语义
  3. 追问三:多 Agent 协作时记忆怎么共享?

    • Blackboard 模式:所有 Agent 读写同一份共享记忆
    • Message Passing:Agent 之间通过消息传递上下文
    • 生产环境比较稳的搭配是 共享长期记忆 + 各自独立的短期记忆
  4. 追问四:记忆模块怎么评估效果?

    • 端到端:用多轮对话 Benchmark(如 LoCoMo、LongMemEval)评估
    • 记忆层:单独评估记忆召回的 Recall@K、记忆更新的准确率
    • mem0 2026 年那份报告里有组数据挺有参考价值:新算法在时序查询上提了 29.6 分,多跳推理提了 23.1 分

常见面试变体

  • 变体一:"你会怎么设计一个能记住用户偏好的智能客服?"(场景化考察)
  • 变体二:"LangChain4j / Spring AI 里 ChatMemory 是怎么实现的?"(框架细节)
  • 变体三:"为什么需要长期记忆?直接把所有历史对话塞进 Prompt 不行吗?"(考察对上下文窗口和成本的理解)
  • 变体四:"了解 MemGPT / Letta 吗?它的核心创新是什么?"(前沿意识)

记忆口诀

Agent 记忆设计六字诀:"分层、召回、遗忘"

  • 分层:短期管当前,长期管历史;语义存事实,情景存事件
  • 召回:向量 + 关键词 + Rerank,三件套不能少
  • 遗忘:TTL 衰减 + 冲突覆盖,记忆不是越多越好

再记一个 Letta 的三层:"Core / Recall / Archival",对应 "寄存器 / 内存 / 硬盘",往操作系统虚拟内存上一套就通了。

总结

Agent 的记忆机制说白了,就是 把人脑的记忆模型搬进工程里,在 LLM 有限的上下文窗口和无限的交互历史之间,架一套 "分层缓存 + 按需调度" 的系统。实际开发抓住三件事:短期记忆用滑动窗口 + 摘要把住容量关,长期记忆用向量库 + Extraction 做个性化沉淀,召回层必须加 Rerank。能把 Spring AI / LangChain4j 的代码讲清楚,再顺嘴提一下 mem0 / Letta 的设计思路,这道题基本就稳了。