跳到主要内容

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:443 / :2090
EmbedderFactory 等四个工厂按 config 里的 provider 名,动态实例化后端mem0/utils/factory.py:33
VectorStore存记忆向量、做语义检索 + 可选 BM25mem0/vector_stores/base.py:4 VectorStoreBase
LLM抽事实(写入)、可选 rerank/proceduralmem0/llms/base.py
Embedder文本 → 向量mem0/embeddings/base.py
SQLiteManager记忆变更历史 + 会话消息缓冲mem0/memory/storage.py:11
实体库(entity_store)第二个向量集合,存实体 → 链接的记忆 idmem0/memory/main.py:515 entity_store
ADDITIVE_EXTRACTION_PROMPT写入线的核心提示词,只产 ADDmem0/configs/prompts.py:468
score_and_rank检索线的三路分数融合mem0/utils/scoring.py:60

主线走一遍(高层)

以一次 add([...], user_id="alice") 为例(mem0/memory/main.py:830 _add_to_vector_store):

  1. 用 scope id 拼出 session_scope 字符串,从 SQLite 取本会话最近 10 条消息当上下文。
  2. 把新消息 embed 一下,去向量库捞 top-10 已有相关记忆——给 LLM 当「我已经记过啥」的参照,顺便做去重。
  3. 一次 LLM 调用,system prompt 是 ADDITIVE_EXTRACTION_PROMPT,返回 JSON {"memory": [{"id":"0","text":"..."}, ...]}
  4. 每条事实算 md5 哈希,撞上已有哈希就丢;批量 embed 剩下的,批量 insert 进向量库。
  5. 从这些事实里抽实体(spaCy),连进实体库。
  6. 把原始消息存回 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. 阅读地图(建议顺序)

按「由浅入深」读,每章都先讲直觉再上源码:

  1. 01-add-pipeline.md —— 写入线。一条记忆从对话到落库的 8 个阶段:上下文收集、捞已有记忆、单次 LLM 抽取、哈希去重、批量 embed/insert。先读这章,它是项目名所指的核心价值。
  2. 02-search-fusion.md —— 检索线。over-fetch、三路打分(语义/BM25/实体)、score_and_rank 的自适应分母融合、阈值门控。
  3. 03-entity-linking.md —— 实体机制。spaCy 抽实体、实体库的 upsert 与去重、检索时的 entity boost——取代旧图谱的轻量方案。
  4. 04-providers-config.md —— 可插拔后端。四个工厂如何用 provider 名动态加载 20+ 向量库 / 15+ LLM,以及 Pydantic config 体系。

5. 代码地图(导航索引)

主题文件符号
同步总入口mem0/memory/main.pyMemory (:443)
异步总入口mem0/memory/main.pyAsyncMemory (:2090)
写入管线核心mem0/memory/main.py_add_to_vector_store (:830)
检索核心mem0/memory/main.py_search_vector_store (:1575)
三路分数融合mem0/utils/scoring.pyscore_and_rank (:60)
写入提示词mem0/configs/prompts.pyADDITIVE_EXTRACTION_PROMPT (:468)
提示词构造mem0/configs/prompts.pygenerate_additive_extraction_prompt (:1016)
实体抽取mem0/utils/entity_extraction.pyextract_entities / extract_entities_batch
实体库mem0/memory/main.pyentity_store (:515), _upsert_entity (:561)
后端工厂mem0/utils/factory.pyLlmFactory / VectorStoreFactory / EmbedderFactory
历史与消息缓冲mem0/memory/storage.pySQLiteManager (:11)
配置根mem0/configs/base.pyMemoryConfig (:29)