1. 为什么 Agent 的“长期记忆”是个真问题
做 Agent 开发的人,迟早会撞上同一堵墙:模型上下文窗口再大,也扛不住跨会话、跨天、跨月的记忆需求。你今天跟 Agent 说“我下周三要去杭州出差,帮我盯着那几天的天气”,它记住了;明天再问它“我下周出门要带伞吗”,它一脸茫然。这不是模型笨,是它压根没有“长期记忆”这个能力。
短期记忆靠上下文窗口就能撑住,一轮对话、一个任务链路,塞进 prompt 里就行。但长期记忆要解决的是另一回事:信息怎么存、存成什么结构、什么时候取、取出来怎么用、旧信息怎么更新和遗忘。这四个问题任何一个没处理好,Agent 就会表现得像个失忆症患者,或者更糟——记了一堆错误信息还自信满满地引用。
我过去一年在几个 Agent 项目里反复折腾记忆模块,从最早的“把历史对话全塞进向量库”这种土办法,到后来认真研究 Mem0、Zep、Graphiti 这几个专门做记忆层的项目,踩的坑足够写一篇长文。这篇就围绕六个主流项目的设计思路做一次横向对比,把每个方案背后的取舍讲清楚,让你在选型时知道自己在选什么。
先说清楚这六个项目大致分几类。Mem0和Zep是专门为 Agent 记忆设计的独立服务,开箱即用程度高;Graphiti走的是时序知识图谱路线,强调时间维度和关系演化;Letta(前身 MemGPT)把记忆管理做进了 Agent 运行时本身;LangMem是 LangChain 生态里的记忆工具集;Memobase则是偏用户画像方向的记忆方案。它们解决的问题有重叠,但切入角度差别很大。
提示:选记忆方案之前,先想清楚你的 Agent 是“单用户长期陪伴型”还是“多用户任务型”。前者对记忆的连续性和个性化要求极高,后者更看重隔离性和检索效率。这个判断会直接决定你该看哪几个项目。
适合读这篇的人:正在做 Agent 应用、被记忆问题卡住的开发者;准备给现有 Agent 加记忆模块、但不确定选哪个方案的工程师;以及想理解“记忆”这件事在架构层面到底该怎么设计的技术负责人。下面我会把每个项目的核心思路、关键机制、实操要点和踩坑经验都摊开讲。
2. 六个项目的核心设计思路拆解
2.1 Mem0:把记忆做成一层可插拔的服务
Mem0 的设计哲学很直接:记忆不该跟 Agent 逻辑耦合,应该抽出来做成一独立层。它的架构是“提取-存储-检索”三段式。每轮对话结束后,Mem0 会用 LLM 从对话里抽取值得记住的事实(fact),比如“用户喜欢喝美式”“用户下周三去杭州”,然后把这些事实存进向量库加图数据库的混合存储里。
它最聪明的地方在于记忆的增删改查是自动的。新信息和旧信息冲突时,Mem0 会判断是更新、删除还是新增。比如用户先说“我住在北京”,后来说“我搬到上海了”,Mem0 不会傻乎乎地存两条矛盾记录,而是把北京那条标记为过期或直接更新。这个机制靠的是一套 LLM 驱动的记忆管理 prompt,让模型来判断该怎么处理新事实。
存储层它默认用向量库做语义检索,同时用图存储来维护实体间关系。检索时两路结果融合,既能按语义相似度找,也能按关系跳转找。这个混合检索在实际使用中确实比纯向量检索准,尤其是涉及“某人的某属性”这类查询时。
2.2 Zep:时序知识图谱 + 会话摘要双轨并行
Zep 的思路比 Mem0 更重,它把记忆分成两条线:一条是知识图谱,存实体、关系和事实,而且带时间戳;另一条是会话摘要,把长对话压缩成摘要存起来。检索时两条线都能用。
Zep 最核心的卖点是时序感知。它不只记“用户住在上海”,还记“用户从 2024 年 3 月开始住在上海”。这个时间维度在回答“用户去年住哪”这类问题时是决定性的。Graphiti 其实也是这个路子,后面会细说,Zep 在早期版本里就集成了类似 Graphiti 的图能力。
它的另一个特点是自动摘要。长对话不会原样存,而是定期压缩成摘要,这样既省存储又提升检索质量。摘要的粒度可以配置,比如每 N 轮对话或每 M 个 token 触发一次。这个设计对长周期陪伴型 Agent 特别友好,因为原始对话里噪音太多,摘要能提炼出真正重要的信息。
2.3 Graphiti:时序知识图谱的纯粹派
Graphiti 是 Zep 团队开源出来的图记忆框架,可以单独用。它的核心是一张双时态知识图谱:每个事实(边)有两个时间维度,一个是事实发生的时间(valid time),一个是事实被记录的时间(transaction time)。这个设计让它可以回答“截至某个时间点,系统知道什么”这类问题。
Graphiti 的图谱构建是增量的。新对话进来,它抽取实体和关系,跟已有图谱做对齐和合并,冲突时按时间戳决定谁覆盖谁。它不依赖批量重跑,而是流式更新,这对实时性要求高的场景很重要。
它跟纯向量方案最大的区别是关系推理能力。向量检索只能找“相似”,图谱能找“关联”。比如问“跟我一起做项目 A 的同事里谁养猫”,向量库基本没戏,图谱可以沿着“项目 A - 参与人 - 养猫”这条路径查出来。当然代价是构建和维护成本更高。
2.4 Letta(MemGPT):把记忆管理做进 Agent 运行时
Letta 的前身是 MemGPT,那篇论文的思路是“让 LLM 像操作系统管理内存一样管理自己的记忆”。它把上下文窗口当成“内存”,把外部存储当成“硬盘”,Agent 自己决定什么时候把信息从内存换出到硬盘、什么时候换回来。
这个方案的核心是Agent 自主管理记忆。它给 Agent 一套工具(比如core_memory_append、archival_memory_search),让 Agent 在对话过程中自己判断该记什么、该查什么。这跟 Mem0、Zep 那种“外部服务自动处理”是两条完全不同的路。
好处是灵活,Agent 可以根据任务需要动态调整记忆策略。坏处是依赖模型的判断力,模型不够聪明时会出现该记的没记、该查的没查的情况。而且这套机制跟 Letta 的运行时强绑定,想单独抽出来用比较麻烦。
2.5 LangMem:LangChain 生态里的记忆工具箱
LangMem 是 LangChain 官方出的记忆工具集,定位是“给 LangGraph 应用加记忆能力”。它提供几种记忆类型:语义记忆(事实)、情节记忆(经历)、程序记忆(技能)。每种记忆有对应的存储和检索接口。
它的优势是跟 LangChain 生态无缝集成。如果你已经在用 LangGraph 搭 Agent,加 LangMem 基本是改几行配置的事。它支持把记忆存到多种后端,也支持自定义提取和检索逻辑。
但它的“自动记忆管理”能力比 Mem0、Zep 弱一些,更多是提供工具和抽象,具体怎么提取、怎么更新,需要你自己写逻辑或者用它的默认实现。适合想自己掌控记忆策略、又不想从零造轮子的团队。
2.6 Memobase:用户画像导向的记忆方案
Memobase 的切入点是用户画像。它不太关心任务相关的记忆,而是专注把用户的各种属性、偏好、经历结构化地存下来,形成一份持续更新的用户档案。它的数据结构偏“用户中心”,每个用户一份 profile,里面分门别类存各种事实。
这个方案适合做个性化推荐、陪伴型应用、客服 Agent 这类“需要懂用户”的场景。它的检索也偏“取用户档案”而不是“语义搜索历史对话”。如果你要的是任务记忆,它可能不是最优选;如果你要的是“越用越懂这个用户”,它的结构更对路。
3. 核心机制对比:存储、检索、更新到底怎么选
3.1 存储结构:向量、图、还是混合
存储结构直接决定了你能回答什么问题。纯向量存储适合“找相似”,比如“用户之前提过的类似需求”。图存储适合“找关联”,比如“跟这个实体相关的所有事实”。混合存储想两头都占,但实现复杂度也上去了。
| 项目 | 主存储 | 是否带图 | 时间维度 |
|---|---|---|---|
| Mem0 | 向量 + 图 | 是 | 弱 |
| Zep | 图 + 摘要 | 是 | 强 |
| Graphiti | 图 | 是 | 强(双时态) |
| Letta | 分层存储 | 否 | 弱 |
| LangMem | 可配置 | 可选 | 弱 |
| Memobase | 结构化 profile | 否 | 中 |
从表里能看出来,Zep 和 Graphiti 在时间维度上做得最重,这跟它们面向长周期记忆的定位一致。Mem0 的图能力相对轻,更偏向量检索。Letta 和 Memobase 走的是另一条路,一个管运行时内存,一个管用户档案。
我的经验是:如果你的查询里经常出现“什么时候”“之前”“现在还是不是”这类词,时间维度就必须重视,选 Zep 或 Graphiti。如果查询主要是“用户喜欢什么”“用户是谁”,Memobase 或 Mem0 更合适。
3.2 检索策略:语义、关系、还是混合
检索策略决定了记忆能不能被“想起来”。语义检索靠 embedding 相似度,快但容易漏掉关系信息。关系检索靠图遍历,准但需要图谱构建得好。混合检索想兼顾,但融合排序是个技术活。
Mem0 的混合检索是向量结果和图结果做加权融合,权重可以调。Zep 的检索会同时查图谱和摘要,然后按相关性排序。Graphiti 的检索更偏图遍历加语义过滤,适合复杂关系查询。
实操中我发现一个坑:混合检索的融合权重很难调。调高了图权重,简单查询会变慢;调高了向量权重,关系查询会漏。建议先用默认权重跑一批真实查询,看召回情况再微调,别一上来就自己拍权重。
3.3 更新机制:覆盖、追加、还是遗忘
记忆更新是最容易被忽视、又最容易出问题的一环。新信息来了,是覆盖旧的、追加新的、还是让旧的过期?处理不好就会出现“Agent 记着用户三个月前说的过时信息”这种尴尬。
Mem0 的更新是 LLM 驱动的,它会判断新事实跟旧事实的关系,决定 ADD、UPDATE、DELETE 还是 NOOP。Zep 和 Graphiti 靠时间戳,新事实带更新的时间,旧事实自动失效。Letta 靠 Agent 自己判断,灵活但不可控。Memobase 偏追加,profile 字段可以覆盖。
注意:不管选哪个方案,都要给记忆加“过期”或“置信度”机制。我见过太多项目把用户随口一说的话当成永久事实存下来,结果 Agent 一直按错误信息行事。至少给每条记忆加个时间戳和来源,方便后续清理。
4. 实操落地:从零给 Agent 接上长期记忆
4.1 选型决策:先问自己四个问题
别急着看文档,先回答这四个问题,答案会帮你砍掉一半选项。
第一个问题:记忆是单用户还是多用户?多用户的话,隔离性必须优先,Mem0 和 Zep 都支持按 user_id 隔离,Memobase 天然按用户组织。单用户陪伴型可以更激进地用图记忆。
第二个问题:查询以语义为主还是关系为主?语义为主选 Mem0 或 LangMem,关系为主选 Graphiti 或 Zep。
第三个问题:要不要时间维度?要的话 Zep、Graphiti 是首选,其他方案得自己加时间字段。
第四个问题:团队愿意维护多重的基础设施?Graphiti 要自己搭图数据库,Zep 有托管版但自建也不轻,Mem0 相对轻量,LangMem 最轻但功能也最基础。
4.2 以 Mem0 为例的接入实操
假设你选 Mem0,接入流程大致是这样。先装依赖:
pip install mem0ai然后初始化客户端,配置 LLM 和向量库:
from mem0 import Memory config = { "llm": { "provider": "openai", "config": {"model": "gpt-4o-mini"} }, "vector_store": { "provider": "qdrant", "config": {"host": "localhost", "port": 6333} } } m = Memory.from_config(config)加记忆和查记忆:
# 加记忆,user_id 用来隔离不同用户 m.add("我下周三要去杭州出差", user_id="user_001") # 查记忆 results = m.search("我下周有什么安排", user_id="user_001")这里有个关键点:add的时候一定要传 user_id,否则记忆会混在一起。Mem0 的隔离靠的就是这个字段。另外add是异步抽取事实的,如果你需要立刻能查到,得等抽取完成或者用同步模式。
4.3 以 Graphiti 为例的图谱记忆搭建
Graphiti 的接入重一些,因为要起图数据库。它默认用 Neo4j 或 FalkorDB。以 Neo4j 为例:
from graphiti_core import Graphiti graphiti = Graphiti("bolt://localhost:7687", "neo4j", "password") await graphiti.build_indices_and_constraints()加记忆:
await graphiti.add_episode( name="出差安排", episode_body="用户下周三去杭州出差", source_description="对话", reference_time=datetime.now() )查记忆:
results = await graphiti.search("用户下周的安排")Graphiti 的reference_time很关键,它决定了这条事实在时间轴上的位置。如果你不传,它会用当前时间,可能导致时序错乱。每次加记忆都显式传 reference_time,这是我踩过坑之后的习惯。
4.4 记忆提取的 prompt 设计要点
不管用哪个方案,记忆提取的质量都取决于 prompt。我总结了几条经验。第一,明确告诉模型什么值得记:事实、偏好、计划、关系,这些要记;寒暄、重复、临时情绪,这些不记。第二,要求模型输出结构化格式,比如 JSON,方便后续处理。第三,让模型判断新旧关系,是新增、更新还是删除。
一个我常用的提取 prompt 骨架是这样的:先给模型一段对话,然后要求它输出一个 JSON 数组,每个元素包含fact、action(add/update/delete)、target(如果是 update/delete,指向哪条旧记忆)。这个结构比让模型自由发挥稳定得多。
5. 常见问题与排查技巧实录
5.1 记忆检索不准怎么办
检索不准通常有三个原因。一是 embedding 模型不适合你的领域,通用 embedding 在专业术语上表现差,换个领域微调过的模型会好很多。二是记忆粒度太粗,一条记忆塞了太多信息,检索时匹配不上。解决办法是提取时拆细,一条记忆只讲一件事。三是检索策略单一,纯向量漏了关系信息,加图检索或关键词检索做补充。
我遇到过一个典型案例:用户问“我上次说的那个项目进展如何”,Agent 死活查不到。排查发现记忆里存的是“用户在开发一个电商推荐系统”,没有“项目”这个词,语义相似度不够。后来在提取时让模型补上“项目”这个上位词,问题就解决了。
5.2 记忆冲突和过时怎么处理
冲突处理的核心是时间戳加置信度。每条记忆带一个记录时间,新记忆覆盖旧记忆时保留历史版本。查询时默认取最新,需要历史时能回溯。Mem0 和 Graphiti 都支持这个,其他方案得自己加字段。
过时记忆的清理建议定期做,比如每周跑一次,把超过一定时间没被检索到的记忆标记为冷记忆,或者直接归档。别让记忆库无限膨胀,检索质量会随噪音增加而下降。
5.3 多用户隔离怎么做才安全
隔离的关键是每个查询都带 user_id 过滤,而且这个过滤要在存储层做,不能只在应用层做。我见过有项目在应用层过滤,结果向量检索时把别人的记忆也召回了,只是最后被过滤掉,白白浪费算力还增加泄露风险。
Mem0 和 Zep 都支持在存储层按 user_id 隔离,用的时候确认一下你的配置有没有开。Memobase 天然按用户组织,隔离性最好。自建方案的话,建议每个用户一个 collection 或者一个图分区,物理隔离最稳。
5.4 记忆模块拖慢响应怎么办
记忆检索是额外的一次或多次 IO,肯定会增加延迟。优化方向有几个。一是缓存热点记忆,把最近常用的记忆放内存里。二是异步检索,记忆查询跟主流程并行,不阻塞首 token 输出。三是限制检索条数,别一次召回几十条,取 top 3 到 top 5 通常够用。四是预取,根据当前对话预测可能要用的记忆,提前查好。
实测下来,Mem0 加 Qdrant 的检索延迟在几十毫秒量级,Graphiti 加 Neo4j 会高一些,复杂图查询可能上百毫秒。如果对延迟敏感,向量方案更友好。
5.5 常见问题速查表
| 问题现象 | 可能原因 | 排查方向 |
|---|---|---|
| 记忆查不到 | embedding 不匹配 / 粒度太粗 | 换模型 / 拆细记忆 |
| 记忆冲突 | 没做更新判断 | 加时间戳和冲突处理 |
| 跨用户串记忆 | 隔离没在存储层做 | 检查 user_id 过滤位置 |
| 响应变慢 | 检索阻塞主流程 | 异步化 / 加缓存 |
| 记忆库膨胀 | 没做清理 | 定期归档冷记忆 |
| 时序错乱 | reference_time 没传 | 显式传时间戳 |
6. 几个我踩过的坑和选型建议
先说一个最容易被忽视的坑:记忆的写入时机。很多人习惯每轮对话都写一次记忆,结果噪音极大。更好的做法是按事件写,一个任务完成、一个偏好表达、一个计划确定,这些才是值得记的节点。Mem0 支持批量 add,可以把一段对话攒起来一起提取,质量比逐轮提取高。
第二个坑是过度依赖自动提取。LLM 提取事实不是百分百准,偶尔会漏或者错。关键信息建议加一道人工确认或者规则校验。比如涉及金额、时间、人名的事实,提取后做一次格式校验,不对就重试。
第三个坑是忽略记忆的读取成本。记忆不是越多越好,检索时召回太多反而稀释了相关性。我一般把召回条数控制在 5 条以内,超过的做二次排序。
选型上,我的建议是这样:快速验证阶段用 Mem0,接入快、功能够用;需要强时序和关系推理用 Graphiti 或 Zep,但要做好维护图数据库的准备;已经在 LangChain 生态里就用 LangMem,省集成成本;做用户画像和个性化用 Memobase;想深度定制记忆策略、又愿意跟运行时绑定,可以看 Letta。
最后分享一个我自己的做法:不管用哪个方案,我都会在记忆层外面包一层自己的抽象接口,把 add、search、update、delete 这几个操作统一掉。这样将来换方案时,上层 Agent 逻辑不用动,只换底层实现。这个抽象层花不了多少时间,但能省掉未来迁移的大麻烦。记忆这件事,架构上留好退路比选对当下方案更重要。