数据截至 (上游 commit 001c235229be)
Mem0 — 架构与原理
30 秒导读: Mem0 是给 LLM agent 用的「长期记忆层」。你把一段对话丢给
add(),它用一次 LLM 调用把里面值得记的事抽成一条条独立事实存进向量库;下次search()时再用「语义相似 + 关键词 BM25 + 实体匹配」三路打分融合,把相关记忆捞回来塞进 prompt。本文按这个克隆 commit(31cec11)讲它 2026 年 4 月「新记忆算法」的真实实现。
1. 这是什么(零基础也能懂)
一句话定义: Mem0 是一个 Python 库(pip install mem0ai),给 AI 应用加一层跨会话的长期记忆——把用户说过的事记住,下次对话能想起来。
解决什么问题 / 给谁用:
LLM 本身是「失忆」的:每次对话结束,上下文窗口一关,它就什么都不记得了。你想做一个「记得用户口味的助手」「记得项目背景的 coding agent」,就得自己把历史塞回去。但历史越积越长,全塞进上下文又贵又慢还会超窗。
Mem0 干的就是这件事的「中间层」:
- 帮你决定记什么(不是把整段对话存下来,而是抽成「User 对花生过敏」这种一句话事实)。
- 帮你存到哪(向量库 + 一个 SQLite 历史库)。
- 帮你怎么找回来(给一个查询,返回最相关的几条记忆)。
一句话直觉/类比: 把 LLM 的上下文窗口当「内存(RAM)」、把 Mem0 当「磁盘」。RAM 断电就没,磁盘长期存;每次对话开始,Mem0 负责把磁盘上相关的几页「换入」内存。
用起来什么样: 这是 README 里的真实最小用法(README.md:202-222):
from mem0 import Memory
memory = Memory() # 默认 OpenAI + Qdrant
# 检索:给一个查询,捞回最相关的记忆
relevant = memory.search(query=message, filters={"user_id": "alice"}, top_k=3)
# 写入:把一轮对话交给它,自己决定抽出哪些事实存下来
memory.add(messages, user_id="alice")
注意两个 API 形状:写入是 add(messages, user_id=...),检索是 search(query, filters={...})。至少要给一个 scope id(user_id / agent_id / run_id),否则报错——记忆永远是「属于某个人/某个 agent/某次运行」的,不存在全局记忆。
本节不碰底层。下面进顶层全景。
2. 顶层全景(它大概怎么转)
两条主线
整个库就两条端到端路径,其它都是配角:
- 写入线
add()—— 对话进来 → LLM 抽事实 → 去重 → 存向量库。 - 检索线
search()—— 查询进来 → 三路打分 → 融合排序 → 返回 top-k。
┌─────────────────────── Memory (mem0/memory/main.py) ───────────────────────┐
│ │
对话 messages ───▶│ add() │
│ 1. 取本会话最近消息(SQLite) │
│ 2. 向量库捞「已有相关记忆」当上下文 │
│ 3. 一次 LLM 调用 → 抽出一组「新事实」(ADD-only) ◀── ADDITIVE_EXTRACTION │
│ 4. md5 哈希去重 → 批量 embed → 批量 insert 向量库 │
│ 5. 抽实体、连进「实体库」 │
│ │
查询 query ──────▶│ search() │
│ A. 语义检索(向量) ┐ │
│ B. 关键词检索(BM25)├──▶ score_and_rank 融合 ──▶ top-k │
│ C. 实体匹配 boost ┘ │
└────────────────────────────────────────────────────────────────────────────┘
│embed │向量CRUD │抽事实/判断 │历史&消息缓冲
▼ ▼ ▼ ▼
Embedder VectorStore LLM SQLiteManager
(20+ 家) (20+ 家) (15+ 家) (history.db)
部件一句话职责
| 部件 | 干什么 | 在哪个文件 / 符号 |
|---|---|---|
Memory / AsyncMemory | 总入口,编排 add/search 两条线 | mem0/memory/main.py:487 / :2090 |
EmbedderFactory 等四个工厂 | 按 config 里的 provider 名,动态实例化后端 | mem0/utils/factory.py:35 |
VectorStore | 存记忆向量、做语义检索 + 可选 BM25 | mem0/vector_stores/base.py:4 VectorStoreBase |
LLM | 抽事实(写入)、可选 rerank/procedural | mem0/llms/base.py |
Embedder | 文本 → 向量 | mem0/embeddings/base.py |
SQLiteManager | 记忆变更历史 + 会话消息缓冲 | mem0/memory/storage.py:11 |
| 实体库(entity_store) | 第二个向量集合,存实体 → 链接的记忆 id | mem0/memory/main.py:559 entity_store |
ADDITIVE_EXTRACTION_PROMPT | 写入线的核心提示词,只产 ADD | mem0/configs/prompts.py:468 |
score_and_rank | 检索线的三路分数融合 | mem0/utils/scoring.py:60 |
主线走一遍(高层)
以一次 add([...], user_id="alice") 为例(mem0/memory/main.py:879 _add_to_vector_store):
- 用 scope id 拼出
session_scope字符串,从 SQLite 取本会话最近 10 条消息当上下文。 - 把新消息 embed 一下,去向量库捞 top-10 已有相关记忆——给 LLM 当「我已经记过啥」的参照,顺便做去重。
- 一次 LLM 调用,system prompt 是
ADDITIVE_EXTRACTION_PROMPT,返回 JSON{"memory": [{"id":"0","text":"..."}, ...]}。 - 每条事实算 md5 哈希,撞上已有哈希就丢;批量 embed 剩下的,批量
insert进向量库。 - 从这些事实里抽实体(spaCy),连进实体库。
- 把原始消息存回 SQLite 当下一轮的上下文。
3. 这版的关键转折:为什么没有 UPDATE/DELETE 了
这是读 Mem0 最容易踩的认知坑,先点破。
老版 Mem0(很多博客和旧文档写的)是 ADD/UPDATE/DELETE 四操作:LLM 不仅抽新事实,还要判断「这条和已有的冲突吗?要改吗?要删吗?」。代码里那套 DEFAULT_UPDATE_MEMORY_PROMPT(mem0/configs/prompts.py:176)和 get_update_memory_messages(:406)就是为它服务的,现在仍保留——但本地 Memory.add() 的推理路径已经不走它了。
这个 commit 的 README.md:54-58 写明了新算法:
- Single-pass ADD-only extraction —— 一次 LLM 调用,不做 UPDATE/DELETE。记忆只增不改。
- Agent 产出的事实是一等公民 —— assistant 说的话也抽。
- 实体链接 —— 实体被抽出来、embed、跨记忆链接,用于检索增强。
- 多信号检索 —— 语义、BM25、实体三路并行打分融合。
为什么去掉 UPDATE/DELETE?权衡很清楚:UPDATE 要先让 LLM 读一堆旧记忆再判断冲突,慢、贵、还容易判错(把不该合并的合并了)。ADD-only 把这事推给检索端——旧记忆不删,但检索时新的更相关就排前面。代价是记忆会冗余增长,靠 md5 哈希去重 + prompt 里的「语义等价就跳过」兜底。README.md 的 benchmark 表显示新算法在 LoCoMo 上 71.4 → 91.6,token 降到 7.0K,p50 延迟 0.88s——单次检索、无 agentic loop。
代码里看不出的部分(诚实): 上面的「老版」对照是基于仓库里仍存在但
add()不再调用的 UPDATE 代码 + README 的算法说明推断的(inferred);本文档只详述这个 commit 里add()实际走的 ADD-only 路径。
4. 阅读地图(建议顺序)
按「由浅入深」读,每章都先讲直觉再上源码:
- 01-add-pipeline.md —— 写入线。一条记忆从对话到落库的 8 个阶段:上下文收集、捞已有记忆、单次 LLM 抽取、哈希去重、批量 embed/insert。先读这章,它是项目名所指的核心价值。
- 02-search-fusion.md —— 检索线。over-fetch、三路打分(语义/BM25/实体)、
score_and_rank的自适应分母融合、阈值门控。 - 03-entity-linking.md —— 实体机制。spaCy 抽实体、实体库的 upsert 与去重、检索时的 entity boost——取代旧图谱的轻量方案。
- 04-providers-config.md —— 可插拔后端。四个工厂如何用 provider 名动态加载 20+ 向量库 / 15+ LLM,以及 Pydantic config 体系。
5. 代码地图(导航索引)
| 主题 | 文件 | 符号 |
|---|---|---|
| 同步总入口 | mem0/memory/main.py | Memory (:443) |
| 异步总入口 | mem0/memory/main.py | AsyncMemory (:2090) |
| 写入管线核心 | mem0/memory/main.py | _add_to_vector_store (:830) |
| 检索核心 | mem0/memory/main.py | _search_vector_store (:1575) |
| 三路分数融合 | mem0/utils/scoring.py | score_and_rank (:60) |
| 写入提示词 | mem0/configs/prompts.py | ADDITIVE_EXTRACTION_PROMPT (:468) |
| 提示词构造 | mem0/configs/prompts.py | generate_additive_extraction_prompt (:1016) |
| 实体抽取 | mem0/utils/entity_extraction.py | extract_entities / extract_entities_batch |
| 实体库 | mem0/memory/main.py | entity_store (:515), _upsert_entity (:561) |
| 后端工厂 | mem0/utils/factory.py | LlmFactory / VectorStoreFactory / EmbedderFactory |
| 历史与消息缓冲 | mem0/memory/storage.py | SQLiteManager (:11) |
| 配置根 | mem0/configs/base.py | MemoryConfig (:29) |