三条消息,一个决定
假想一个帮电商卖家做推广视频的 Agent。一月第一周,用户说:"我们品牌调性偏冷,主色用蓝灰,别用红色。"这句话该记下来,没有争议。第三周用户又说:"新品是春节礼盒,这条视频主色要用红。"到这里问题来了:记忆库里那条"别用红色"怎么办?
把它删掉,下个月做常规款视频时 Agent 会满屏喜庆红。原样保留再加一条"用红",两条记忆互相打架,检索时哪条都可能被捞出来。把两条合成一条"品牌偏冷色,春节礼盒例外用红",最合理,但这要求系统能分辨什么是长期偏好、什么是单次任务的例外。什么都不做,等于第三周这句话白说。
四个选项,对应的正是 Mem0 论文里更新阶段的四个操作:DELETE、ADD、UPDATE、NOOP。这个决定由谁来做、依据什么做、做错了能不能回退,是记忆系统里最不像数据库的那一部分。把东西存下来这件事本身从来不难。
Mem0 是目前最流行的开源记忆框架之一,配套论文(Chhikara et al., 2025)正文不到十五页,算法上没有多少新意:从对话里抽事实,和旧记忆比对,决定增删改,用的时候按语义相似度取回。它的价值在于把这条最小流水线画得足够干净,并且量化了它相对于"把全部历史塞进上下文"能省多少延迟和 token。本文顺着这条流水线走一遍,重点放在写入那一步;再看图结构版本 Mem0g 多买到了什么;然后带着怀疑读实验数字;最后翻一下开源仓库一年后的代码,看论文里的那次判定去了哪里。
先把流水线走一遍
Mem0 一次写入到底做了什么?处理单元是一对消息 ,通常是一句用户消息加一句助手回复。每来一对消息,跑两个阶段:提取(extraction)和更新(update)。

提取:从一对消息里拿出候选事实
提取阶段的输入不只是这对消息。论文把送给 LLM 的完整 prompt 写成
是整段对话的摘要,由一个独立的异步模块周期性刷新,提供全局背景; 是最近 条消息,提供摘要里没来得及合并的局部细节;最后两项是当前消息对。LLM 从中抽出一组候选事实 ,只针对新消息对提取,前两项只当上下文。实验里 。
这个设计的要点是**“全局摘要 + 局部窗口 + 当前增量"三层上下文**,目的是让抽取时能消解指代。用户第三周说"这条视频主色要用红”,没有前十条消息,LLM 不知道"这条视频"是春节礼盒;没有摘要,不知道这个用户的品牌原本偏冷。抽出来的事实质量,取决于抽取时看到了多少。
顺带一句时间上的谨慎:论文明确说摘要生成是异步的,但没有说整条提取加更新的链路是否异步于用户响应。不能凭这张图断言 Mem0 的写链路不阻塞主请求。
更新:每个候选事实过一次判定
提取完成后,每个候选事实 单独走一遍更新:先用向量相似度从记忆库里取 Top- 条最相似的旧记忆(实验 ),把候选事实和这些旧记忆一起交给 LLM,通过函数调用接口让 LLM 在四个操作里选一个:
- ADD:没有语义等价的旧记忆,新建;
- UPDATE:旧记忆相关,候选事实信息更全,用新的替换旧的;
- DELETE:候选事实和旧记忆矛盾,删旧的;
- NOOP:已经有了,或者不相干,什么都不做。
论文特意说明没有用单独的分类器,就让 LLM 凭候选事实和旧记忆的语义关系直接选。UPDATE 只在新事实的信息量大于旧记忆时才执行,否则视同 NOOP。判定之后由系统执行数据库操作,LLM 不直接碰库。
这一段是全文的核心,先把它的位置放准。论文自己把相对 RAG 的优势归给抽取:只存最显著的事实,不存原文切块(4.3 节)。我的理解是抽取和更新各管一半。抽取决定什么值得进库,让库比原始对话小一个量级;更新决定新事实进库时怎么对待旧记忆,让库在对话继续时不自相矛盾。RAG 索引的是一份给定的、不会自己变的语料,这两件事都不需要做。更新也不是记忆系统的必选项,后面会看到 Mem0 自己后来就不做了;它是论文版 Mem0 比一个"抽取后直接追加"的最小系统多出来的全部东西。抽取属于记忆的形成(Formation),更新属于演化(Evolution)里的更新(Updating)。
一个最容易混淆的地方:图里的召回不是回答时的召回
Figure 2 里有一个"fetch similar memories",很容易被读成检索链路,但它不是。这次召回发生在更新阶段,Query是候选新事实,目的是找出可能需要被改的旧记忆,服务的是记忆库的维护。用户提问时的另一次召回,Query是用户问题,目的是找回答需要的证据。两次都叫检索,输入、目的、失败方式完全不同:
| 更新时召回 | 回答时召回 | |
|---|---|---|
| 查询 | 候选新事实 | 用户当前问题 |
| 目的 | 找出可能被改的旧记忆 | 找出回答所需的证据 |
| 漏召回的后果 | 冲突记忆并存 | 答不上 |
| 误召回的后果 | 无关记忆被错改、错删 | 噪声进 prompt |
检索回来的记忆带时间戳,回答模型被要求"注意时间戳"、“冲突时优先最新的记忆”、“把’去年’这类相对时间按记忆时间戳换算成具体年份”。把两条链路拼在一起,完整的 Mem0 长这样:
召回 Top-s 相似旧记忆"] R1 --> J["LLM 判定
ADD / UPDATE / DELETE / NOOP"] J --> DB[("记忆库")] end subgraph Q["读链路(论文只给了 prompt)"] U["用户问题"] --> R2["向量检索 Top-k 记忆
(带时间戳)"] DB --> R2 R2 --> A["回答 LLM
相对时间换算、冲突取最新"] end
读链路那句"冲突时优先最新"值得记住:写入时已经做过一次冲突判定(DELETE),读的时候又做了一次。两道防线,我读成作者自己也没把宝全押在写入判定上。这个伏笔后面会用到。
论文没给的 prompt,代码里有
附录 A 给了 LLM 评审(LLM-as-a-Judge)的 prompt 和回答 prompt,没给抽取 prompt,也没给四操作判定的 prompt。开源仓库补上了这个缺口。我翻了论文发表当月(2025 年 4 月底)的代码,写链路和论文大体对得上,也有几处出入:
- 抽取 prompt 把要记的信息分成七类:个人偏好、重要个人信息、计划与意图、活动与服务偏好、健康与饮食、职业信息、其他杂项,附六个 few-shot 例子,要求输出
{"facts": [...]}。它的选择标准是"关于用户的",不是"对未来任务有用的"。 - 更新 prompt 就是四操作的自然语言版,每个操作配一个例子。UPDATE 的规则写得很具体:记忆里有"喜欢打板球",新事实是"喜欢和朋友打板球",更新;记忆里有"喜欢芝士披萨",新事实是"很喜欢芝士披萨",不更新,因为信息相同。全靠 LLM 的语感。
- 一个防御性细节:召回的旧记忆先把 UUID 映射成 0、1、2 这样的整数再给 LLM,代码注释写明是为了应对 LLM 编造不存在的 UUID。
- 当时的开源库里每个候选事实召回 Top-5 而不是论文的 Top-10,而且没有摘要 和最近 条消息这两层上下文,抽取只看当前传入的消息。摘要和近因窗口是托管平台的功能。
第四点意味着论文描述的是平台版本,实验也是在平台上跑的(后面细说)。拿开源库复现论文数字,从架构上就不完全等价。
Mem0g:图结构多买到了什么
把句子拆成三元组,到底换来了什么?Mem0 的记忆是一条条自然语言句子,"用户住在旧金山"整句存、整句 embedding、整句召回。Mem0g(g 是 graph)把句子拆开,存成有向标记图 :节点 是实体(Alice、San Francisco),边 是关系(lives_in),标签 给节点打类型(Person、City)。每个节点带实体类型、embedding 和创建时间戳,关系存为三元组 。
同一个用户的三条记忆,Mem0 存三句话:
1 | Memory 1: Alice lives in San Francisco. |
Mem0g 存一个节点和三条边:
Person"] SF["San Francisco
City"] Veg["Vegetarian food
Preference"] Bike["Bicycle
Object"] Alice -->|lives_in| SF Alice -->|prefers| Veg Alice -->|owns| Bike
三句话里的 Alice 在 Mem0 里是三个互不相识的字符串,只靠 embedding 相似度间接靠近;在 Mem0g 里是同一个节点,三条关系从它出发。

写入分两步。实体抽取器先从消息里找实体和类型,关系生成器再在实体对之间判断有没有关系、贴什么标签(lives_in、prefers、owns、happened_on)。新三元组进图时,先对源和目标实体算 embedding,在图里搜相似度超过阈值 的已有节点:找到就复用,找不到就新建。这一步就是实体消解(entity resolution),做错了要么同一个人分裂成两个节点,要么两个人被合成一个;论文没给 的取值,当时的开源代码里节点合并阈值是 0.9。
然后冲突检测器找可能矛盾的旧关系,由 LLM 做的更新解析器决定哪条关系已过时。论文强调过时关系是"标记失效而不是物理删除",目的是支持时间推理:Alice 先住旧金山后搬纽约,两条 lives_in 边都在,一条标记 invalid,既能答"现在住哪"也能答"以前住哪"。
检索有两条路。以实体为中心:先识别问题里的实体,定位到节点,沿出入边扩一个子图,适合"Alice 工作的公司在哪个城市"这种要走两跳的问题。以三元组为中心:把整个问题 embedding,和每条三元组的文本编码比相似度,返回超过阈值的三元组,适合没有明确实体的问题。
"Alice 工作的公司在哪个城市"走实体那条路:定位到 Alice,沿 works_at 到公司,再沿 located_in 到城市,两跳都在子图里。"Alice 最近在学什么"走三元组那条路:没有可锚定的关系名,整句去和三元组比。
图结构真正买到的是三样东西:关系有方向(A 住在 B 和 B 住在 A 不一样),关系之间能连成路径,关系有生命周期。代价论文也给了:记忆库 token 体积翻倍(7k 到 14k),搜索延迟三倍(p50 从 0.148 秒到 0.476 秒),外加实体消解的错误传播。
再补一条代码层面的观察。"标记失效而不是删除"在论文里是概念描述,没有字段、没有规则、没有 prompt。论文发表当时的开源图记忆模块里,冲突关系的处理是一条 Cypher 语句
MATCH ... DELETE r,物理删除。时间推理靠的不是图上的失效标记,而是记忆本身带的时间戳和回答 prompt 里的换算规则。至少在开源版本,Mem0g 在时间问题上相对 Mem0 的优势,来源要打个问号。
实验结果
论文用 LoCoMo 评测。LoCoMo(Maharana et al., 2024)是一个专门测长对话记忆的数据集:10 段对话,每段约 600 轮、26k token,跨多个 session,两个真人聊日常;每段配约 200 个问题,分单跳、多跳、时间、开放域四类。原数据集还有一类"不可回答"的对抗问题,论文排除了。指标是 F1、BLEU-1 和 LLM 评审分(跑 10 次取均值),加上检索上下文的 token 数和延迟。
对手六类:已发表的记忆方法(LoCoMo 自带的方法、ReadAgent、MemoryBank、MemGPT、A-Mem)、开源库 LangMem、不同 chunk 大小和 k 的 RAG、全上下文、OpenAI ChatGPT 的记忆功能、以及 Zep。几个后面会讨论的名字:Zep 是一个商业记忆平台,核心是带时间的知识图谱;LangMem 是 LangChain 出的记忆库;A-Mem 用互相链接的笔记组织记忆,写入时动态更新旧笔记;MemGPT 用操作系统分页的思路让模型自己在上下文和外部存储之间搬数据。
Table 1 只留评审分(越高越好):
| 方法 | 单跳 | 多跳 | 开放域 | 时间 |
|---|---|---|---|---|
| A-Mem | 39.79 | 18.85 | 54.05 | 49.91 |
| LangMem | 62.23 | 47.92 | 71.12 | 23.43 |
| Zep | 61.70 | 41.35 | 76.60 | 49.31 |
| OpenAI | 63.79 | 42.92 | 62.29 | 21.71 |
| Mem0 | 67.13 | 51.15 | 72.93 | 55.51 |
| Mem0g | 65.71 | 47.19 | 75.71 | 58.13 |
Table 2 留 token、延迟和总分:
| 方法 | 检索上下文 token | 搜索 p50 / p95(秒) | 总延迟 p50 / p95(秒) | 总评审分 |
|---|---|---|---|---|
| 全上下文 | 26031 | – | 9.87 / 17.12 | 72.90 |
| 最好的 RAG(k=2,chunk 8192) | – | 0.29 / 1.12 | 2.31 / 9.94 | 60.53 |
| A-Mem | 2520 | 0.67 / 1.49 | 1.41 / 4.37 | 48.38 |
| LangMem | 127 | 17.99 / 59.82 | 18.53 / 60.40 | 58.10 |
| Zep | 3911 | 0.51 / 0.78 | 1.29 / 2.93 | 65.99 |
| OpenAI | 4437 | – | 0.47 / 0.89 | 52.90 |
| Mem0 | 1764 | 0.15 / 0.20 | 0.71 / 1.44 | 66.88 |
| Mem0g | 3616 | 0.48 / 0.66 | 1.09 / 2.59 | 68.44 |
结果:
- 全上下文仍然是质量上限。72.90 对 Mem0g 的 68.44、Mem0 的 66.88,差四到六分。Mem0 卖的不是"更准",是这笔交易:用四到六分评审分,换 p95 延迟从 17 秒降到 1.4 秒、上下文 token 从 26k 降到 1.8k。摘要里的"91% lower p95 latency"和"90% token 节省"说的就是这个。这是一个产品判断,不是算法胜利,而且它成立的前提是对话真的长到塞不下、或者塞得起但付不起。
- 图结构帮的是时间和开放域,不是多跳。直觉上图擅长多跳,结果多跳上 Mem0g(47.19)反而比 Mem0(51.15)低四分,单跳也略低;它赢在时间问题(58.13 对 55.51)和开放域。论文自己的解释是图在多步推理里引入了冗余和开销。我的理解是 LoCoMo 的多跳是"把散在几个 session 里的信息拼起来",带时间戳的自然语言事实已经足够做这件事,三元组把句子拆碎反而丢了上下文。
- 开放域 Zep 更高。Zep 的记忆库有 600k token,论文说是因为每个节点都缓存了一份摘要、事实又重复存在边上,二十倍于原始对话,Mem0 是 7k。论文还记了一笔:往 Zep 写完记忆立刻查经常答不上,隔几小时再查就好了,因为它的图构建有大量异步 LLM 调用。这段读起来有点像抱怨,但它点出了一个真问题:记忆的可用延迟和检索延迟是两个指标。
- RAG 最好也只到 60.5。固定切块检索原始对话,最好的配置是取两个 8192 token 的块,等于把半段对话塞回去,延迟也接近全上下文。这组对照说明的是切块检索原始对话不如抽取后的事实,不是说明 Mem0 的判定机制有效。
数字没说的事
评测脚本 2026 年 6 月从主仓库退役,但 git 历史还在。翻出来看,有几件事论文正文没提,读数字时得放在心里:
- 实验跑在托管平台上,不是开源库。脚本用的是平台客户端,每次 add 送两条消息(对应论文的消息对),开图的时候加一个 enable_graph 参数。
- 脚本给平台设了一段面向 LoCoMo 的自定义抽取指令:记忆里要写人名不要写"user"、要带具体日期、要覆盖"身份认同与自我接纳、家庭计划与育儿、创意爱好、心理健康"这些主题、每条记忆写成一段有叙事结构的话。这些主题就是 LoCoMo 对话的主题。抽取 prompt 对着评测集调过,论文没有说明。
- LoCoMo 是两个真人的对话,没有"助手"。脚本给每段对话建两个记忆库,一个把 A 当 user、B 当 assistant,另一个反过来;回答时两个库都检索,各取 Top-k(脚本默认 10 条)拼进 prompt。
- 时间问题的分数有一部分是读链路挣的。检索回来的记忆按"时间戳: 内容"的格式排列,回答 prompt 明确要求做相对时间换算。记忆结构只需要把时间戳保住,换算由回答模型完成。
- 评审很宽松。它的 prompt 原话是"只要和标准答案涉及同一个话题就算正确",时间问题"只要指向同一个时段就算正确"。
- 没有消融,没有失败案例分析。全文没有测抽取的准确率,没有测四操作判定的准确率,没有测 DELETE 误删了多少。所有指标都是端到端问答。
第六条最要紧。论文的核心机制是写入时的判定,而整篇论文没有一个数字直接衡量这个判定做得对不对。端到端分数高,可能是因为判定准,可能是因为读链路的"冲突取最新"兜住了判定的错误,也可能是因为 LoCoMo 里真正互相矛盾的事实本来就不多。三种情况在数据上无法区分。
这些数字支持的结论是"比全上下文便宜得多、质量损失可控",不支持"四操作判定是有效的记忆更新机制"。
新版本写链路完全去除了冲突判定
写这篇的时候我顺手看了开源仓库的主分支(2026 年 8 月)。写链路和论文已经对不上了。2026 年 4 月的一个 PR 把写入流水线换成了 v3,prompt 文件里的注释写着"从平台后端移植",说明托管平台更早就换了。几个变化:
- 抽取 prompt 开头就写着"你唯一的操作是 ADD"。UPDATE 和 DELETE 不再由 LLM 判定,四操作的 prompt 还留在文件里,但主流程不再调用它。
- 冲突和变化改在抽取时处理。prompt 专门有一节要求把"转变"写进同一条记忆:不要写"用户喝燕麦奶拿铁",要写"用户因为对杏仁过敏,从杏仁奶换成了燕麦奶拿铁"。新旧关系被压进了记忆文本本身。prompt 还要求模型给出新记忆关联的旧记忆 ID(同一实体、偏好变化、事件延续、矛盾都算),不过在我读到的开源路径里,这个字段没有被写进记忆记录,真正落库的是实体到记忆的链接。
- 抽取范围从"关于用户的事实"扩到了助手消息里的推荐、计划、结论,prompt 明说"拿不准就抽,漏掉比冗余代价大",重复交给下游的哈希去重。
- 读链路从纯向量变成混合检索(hybrid retrieval):语义召回多取一批,叠 BM25 分和实体加权再排序;记忆可以带过期时间,过期的默认不返回。
- 图记忆模块在同一个 PR 里从开源库删掉了,只在托管平台保留。
PR 没有写动机,下面是我的推断:论文版的判定有三个结构性问题:
- 贵:每次写入都要一次带着十条旧记忆的 LLM 调用。
- 险:DELETE 是物理删除,判错了没有回退;UPDATE 的"留信息多的那条"会悄悄改写历史。
- 盲:判定只看 Top-s 相似的旧记忆,窗口外的冲突根本看不见,所以本来就清不干净。既然清不干净又不可逆,不如不清:写入只追加,把新旧关系写进文本,冲突留到读的时候,让回答模型带着时间戳自己判断。(前面那个伏笔在这里兑现了:读链路的"冲突取最新"从第二道防线变成了主要的一道)
这和 Zep 的"标记失效而不删除"殊途同归,也和上一篇综述里梳理的演进方向一致:早期系统让 LLM 检测冲突后直接替换删除,后来的系统转向软失效和读时消解。Mem0 自己走完了这条路,只是论文停在了起点。
回到春节礼盒
现在可以把开头的问题放回两个版本的 Mem0 里走一遍。
- 论文版:第三周的消息抽出候选事实"春节礼盒视频主色用红",召回旧记忆"品牌偏冷,别用红色",LLM 判定。四个选项里 NOOP 和 ADD 都留下矛盾,DELETE 毁掉一条仍然有效的长期偏好,只有 UPDATE 成"品牌偏冷色,春节礼盒例外用红"是对的。但这要求判定模型分辨出"这条视频"是任务范围、"品牌调性"是用户范围,而它手里只有十条相似记忆和一段几百字的规则。判对了记忆库最干净,判错了不可逆。
- v3 版:两条都存,抽取时尽量把"这次是春节礼盒的例外"写进文本。下个月做常规款视频,检索会同时捞回"一月:品牌偏冷"和"三月:春节礼盒用红",由回答模型看着日期和措辞决定用哪条。安全,但判断的成本转嫁到了每一次读取,而且依赖抽取时有没有把"例外"两个字写进去。
两个版本的差别不是谁更聪明,是把判定放在哪里:写入时判一次,之后每次读都轻;或者写入时不判,每次读都判。全上下文是后者的极端,什么都不判,让模型每次读完整历史。(Zep 走中间路线,写入时标记、读取时过滤)
没有免费的位置,只有适合自己负载的位置:写多读少的系统适合把判定推到读侧,写少读多的系统才值得在写侧判干净。
最后把论文版的整条写链路放回来看一眼。虚线灰色的部分是 v3 里不再存在的:判定本身和它后面的 UPDATE、DELETE、NOOP 三个分支。相似记忆召回还在,只是挪到了抽取之前,给抽取模型当去重和关联的参考;剩下的提取阶段和数据库,就是今天开源库写链路的骨架。
当前消息与上一条消息"] subgraph EXT["Extraction Phase(提取)"] CTX["输入上下文
全局摘要 S
最近 m 条消息
新消息对"] EXTRACT["LLM 抽取
候选事实"] CAND["候选事实集合 Ω"] CTX --> EXTRACT --> CAND end subgraph UPD["Update Phase(更新)"] SIM["召回 Top-s 条
相似旧记忆"] DECIDE["LLM 判定
新事实与旧记忆的关系"] OP{"选择操作"} ADD["ADD
新增记忆"] UPDATE["UPDATE
替换已有记忆"] DELETE["DELETE
删除冲突记忆"] NOOP["NOOP
无需修改"] SIM --> DECIDE --> OP OP --> ADD OP --> UPDATE OP --> DELETE OP --> NOOP end SUMGEN["异步摘要生成器"] MSG --> CTX DB -->|"读取摘要和最近历史"| CTX DB -->|"读取相似旧记忆"| SIM CAND --> SIM ADD --> WRITE["执行数据库操作"] UPDATE --> WRITE DELETE --> WRITE NOOP --> WRITE WRITE -->|"写回"| DB DB --> SUMGEN SUMGEN -.->|"周期性刷新摘要"| DB classDef gone fill:#f4f4f4,stroke:#999,stroke-dasharray:5 5,color:#888 class DECIDE,OP,UPDATE,DELETE,NOOP gone
最后强调三点:
- 更新时召回和回答时召回是两个组件。用一套配置、一套指标去管,两个都管不好。
- 不要让 LLM 的一次判定触发物理删除。标记、链接、过期时间,都比删除便宜,而且能回退。
- 端到端问答分数衡量不了写入判定。要知道自己的记忆系统有没有在"改"这件事上做对,得单独测抽取准确率、操作判定准确率和误删率。论文没测,不代表可以不测。
Mem0 论文最有用的地方,是把"记忆系统在写入时要做一个决定"这件事讲得足够清楚;它最诚实的地方,是一年后的代码承认了这个决定有多难做。
参考
- Chhikara, P., Khant, D., Aryan, S., Singh, T., & Yadav, D. (2025). Mem0: Building Production-Ready AI Agents with Scalable Long-Term Memory. arXiv:2504.19413
- Maharana, A. et al. (2024). Evaluating Very Long-Term Conversational Memory of LLM Agents(LoCoMo). ACL 2024.
- Rasmussen, P. et al. (2025). Zep: A Temporal Knowledge Graph Architecture for Agent Memory. arXiv:2501.13956
- Mem0 开源仓库:github.com/mem0ai/mem0。文中的代码观察基于主分支两个时间点:2025-04-29(论文发表当月)与 2026-08-12。