Skip to content

Latest commit

 

History

History
130 lines (89 loc) · 8.62 KB

File metadata and controls

130 lines (89 loc) · 8.62 KB

Mem0 核心原理:把 LLM 当记忆秘书的"写时增改删"

2026-08-26 · 上下文专题 · 业界项目剖析(第一篇) 剖析对象:mem0ai/mem0: Universal memory layer for AI Agents(Apache-2.0) 本地源码:/root/projects/mem0/(浅克隆,本剖析所有行号均实测自该副本) 与专题主线的连接:业界"LLM 主判"记忆路线的代表,对照见文末第七节。


一、一句话定位

Mem0 是给 AI agent 加"记忆层"的开源项目:对话进来不存原文,由 LLM 提取成一条条事实;写入时先对已有记忆做"增/改/删"决策;读取时多信号混合检索。

它不是向量数据库,也不是 RAG 框架,而是架在 LLM + 向量库之上的一层"记忆语义层"——核心贡献是写时思考:每次 add 都是一次记忆对账,而不是无脑追加。

  • 出身:YC S24 孵化,前身是 MemGPT(记忆分页思想的商业化)
  • 形态:polyglot monorepo —— Python SDK(mem0ai)+ TypeScript SDK + 自托管 server(FastAPI + pgvector + Neo4j)+ CLI + 各类 agent 集成
  • 可插拔生态:LLM×24、向量库×30、embedding×15、图库×4、重排器×5(全部 provider 化,一个 base 抽象类 + 每 provider 一个文件)

二、大语境:记忆层的三种存储,各管什么

Mem0 的"记忆"不是单一存储,是三层分工:

存什么 作用
向量库 提取后的事实条目(embedding) 检索主战场(30 种向量库可换)
图库(可选) 实体-关系(4 种图库可换) 关系感知检索,补充向量而非替代
SQLite 原始对话消息 + 记忆历史 可追溯、去重、审计

关键点:原始对话也落库db.save_messages)——即使 LLM 没提取出任何事实,对话也留痕。提取层出问题,原始层兜底,这是防信息丢失的最后防线。

三、写路径 add:5 个 Phase,灵魂在 LLM 提取

add()_add_to_vector_store()mem0/memory/main.py:879),V3 流水线(:916 起):

Phase 0 上下文收集   取该 scope 最近 10 条消息(记忆不是只看这一句)      :919-921
Phase 1 现有记忆     新对话 embedding → 向量检索已有记忆 top10          :923-931
                    记忆 id 从 UUID 映射成整数(防 LLM 幻觉编 id)       :933-938
Phase 2 LLM 提取     把 [新消息 + 已有记忆 + 最近消息] 一次喂给 LLM,    :940-989
                    输出 JSON:ADD / UPDATE / DELETE 指令 + hash
Phase 3 批量 embedding 提取出的记忆文本                                :991-1003
Phase 4-5 hash 去重 + 落库                                            :1005+

灵魂在 Phase 2——LLM 输出的是三类指令,不是一条追加:

  • ADD:全新事实,直接写入
  • UPDATE:与已有记忆冲突/过时,覆盖旧条目(用户先说"住北京"后说"搬去上海",结果是改,不是并存两条矛盾记忆)
  • DELETE:用户明确否定/改主意,删除旧记忆

这个"提取 + 对账"是 Mem0 与普通向量记忆的本质区别:存原文就没有"更新"概念,只有提取成结构化事实,才能做增改删

设计细节(源码实锤):

  • 单次调用:已有记忆(top10)+ 新消息 + 最近 10 条历史,一次 LLM 调用全部决策,不逐条问(省成本)
  • 上下文注入infer=True(默认)才走提取;infer=False 则消息原样入库(:880-914),是个可用的逃生门
  • 错误不静默:LLM 提取失败抛 LLMError:963-969),让调用方区分"没提取出事实"和"LLM 挂了",不吞错
  • 失败兜底:提取为空时仍保存原始消息(:986-989),对话不丢

四、读路径 search:多信号混合打分

search()main.py:1379)参数:top_k(默认 20)/ threshold(默认 0.1)/ rerank / explain / filters

流程:向量检索 → 阈值门槛 → 可选 rerank。

底层的混合打分在 mem0/utils/scoring.py:60 score_and_rank

combined = (semantic + bm25 + entity_boost) / max_possible
  • 语义分(向量):主信号,且先过 threshold 门槛才进入融合——语义上不相关的候选,BM25/实体加成拉不回来
  • BM25(关键词):专有名词、代码、人名这类向量不敏感的 token 靠它兜底
  • 实体加成(entity boost):与记忆中的实体链接命中的加权

打分顺序有讲究:门槛在融合之前。这不是加分排序,是"先筛后加"——语义底线守不住,其他信号没有资格参与排序。

五、三个关键设计决策(为什么这么设计)

① 为什么提取事实,不存原文? 记忆密度。原文是噪音,事实是信号。"我今天吃了碗面"不该占一条记忆。只有提取成结构化事实,才能做 UPDATE/DELETE 这种语义级操作。

② 为什么写时就要检索已有记忆? 解决记忆矛盾。没有这一步,新旧事实只会堆积冲突;有这一步,每次写入都是一次记忆对账。代价是写成本高:每次 add 多一次向量检索 + 一次 LLM 调用(且要喂已有记忆)。

③ 为什么 user/agent/run 三层作用域? 记忆不是全局一锅粥。用户 A 的偏好不能污染用户 B;agent 的程序性记忆与用户事实分开。search() 强制要求至少一个作用域(:1457),防止跨主体串记忆。

六、防幻觉工程细节(值得抄的四个点)

  1. UUID→整数映射:933-938):把记忆 id 换成 0,1,2... 再喂 LLM,防止模型幻觉编造不存在的 id 去 UPDATE——LLM 对短整数比对 UUID 老实得多
  2. hash 去重:1007):同一事实被重复提取时,hash 相同直接跳过,防止记忆通胀
  3. 原始消息落 SQLite:988):提取层与原始层分离,可追溯、可审计
  4. 时间能力在 OSS 版是关闭的timestamp / reference_date 直接抛 ValueError:817-818:1432-1433),只有 expiration_date(过期隐藏)可用——时间感知检索是托管平台专有能力,README 明说"OSS 版方向一致、数字不同"

七、与专题主线的对照:LLM 主判 vs 规则主判

Mem0 是业界**"LLM 主判"记忆路线**的代表作,与总纲第一篇《记忆分层规则引擎:决策思路复盘》正好是两种路线:

维度 Mem0(LLM 主判) 我们的设计(规则主判)
判定主体 LLM 每次 add 现判 规则引擎确定性分流(宪法)+ LLM 兜底(司法解释)
可审计 ❌ 说不清"为什么记这条" ✅ rule_chain 可回放
生命周期分层 ❌ 无 L1/L2/L3,只有可选 expiration_date ✅ 三层存储 + TTL + 后验修正
冲突处理 UPDATE/DELETE(LLM 判,黑盒) 用户纠正 P3 覆盖一切(确定性)
写成本 每次 add 一次 LLM 调用 + 一次检索 规则零成本,LLM 只碰灰色地带

结论:Mem0 的"写时增改删"和"提取事实"思路值得抄,它的 UPDATE/DELETE 机制本质上就是我们设计的 P3(用户纠正覆盖)的自动化弱化版——但"判定不可审计、无生命周期先验、写成本高"是它的结构性短板。我们的规则引擎路线,正是针对这些短板的批判性升级。

反过来,Mem0 也验证了我们的判断:提取层(LLM 切句)与判断层(分层)必须分离——Mem0 把两件事都压在 LLM 一次调用里,导致无法解释、无法修正;我们让 LLM 只做提取(切事实),分层交给可测试的规则。

八、源码导航(后续自己读的入口)

mem0/memory/main.py          核心:add(:760) / search(:1379) / _add_to_vector_store(:879)
mem0/memory/storage.py       图库 + 向量库的读写封装(graph + vector 双写)
mem0/memory/base.py          记忆工具对外的工具接口(给 agent 调用的 schema)
mem0/utils/scoring.py        混合打分(semantic + bm25 + entity boost)
mem0/llms/                   24 个 LLM provider
mem0/vector_stores/          30 个向量库 provider
mem0/graphs/                 4 个图库 provider(可选图记忆层)
evaluation/                  memory-benchmarks 子模块(LoCoMo / LongMemEval / BEAM)

建议阅读顺序:先 main.py 的 add(写路径 5 Phase)→ scoring.py(读路径打分)→ 再回 main.py 的 search 串全链路。图记忆层(graphs/)是进阶,理解向量层之后再碰。


本文行号基线:/root/projects/mem0/ 浅克隆副本(2026-08-26)。仓库演进后行号可能漂移,以 grep -n "def add" mem0/memory/main.py 重新定位为准。