深入 Mem0 Platform 记忆架构:从写入管线、混合检索到四维多租户作用域
Mem0 是一个托管式 AI 记忆层,它封装了记忆抽取、去重、冲突消解与语义检索的全部复杂性,让你的应用只需调用 search() 与 add() 两个接口。本文基于 Mem0 Platform 架构参考文档,结合仓库中 Python 客户端源码 与 类型定义,完整拆解记忆的处理管线、检索管线、生命周期、记忆对象结构、多租户作用域与性能特征,读完你可以准确理解 v3 版本的 ADD-only 模型、异步处理语义以及 user_id / agent_id / app_id / run_id 四维作用域的存储与查询规则。
核心概念:应用与记忆层之间的三步循环
Mem0 Platform 的定位是"受管记忆层"(managed memory layer),位于你的 AI 应用与用户之间。所有集成本质上都遵循同一个三步循环:
User Input → Retrieve relevant memories → Enrich LLM prompt → Generate response → Store new memories
即:检索相关记忆 → 用记忆增强 LLM 提示词 → 生成回复 → 把新事实存回记忆库。正如 SKILL.md 中总结的 "retrieve → generate → store" 模式,抽取、去重、冲突消解和语义检索的复杂性全部由 Mem0 承担。
底层存储由两部分构成:
- 向量存储(Vector store):存放嵌入向量,支撑语义相似度检索;
- 实体存储(Entity store):在
add()时自动抽取实体并建立实体链接(entity linking),为"关系感知"的检索提供图谱侧信号。
从源码结构看,这一存储分工对应 v3 的两处演进:实体链接取代了 v2 的图记忆(graph memory),且无需任何配置、在 add() 期间自动完成——这也是 v2 配置中 enable_graph 与 graph_store 参数被移除的原因(详见 Platform v2 → v3 迁移指南)。
记忆处理管线:client.add() 内部发生了什么
当调用 client.add() 时,消息会依次经过三个阶段:
Messages In
│
▼
┌─────────────────────┐
│ 1. EXTRACTION │ 单次 LLM 调用抽取所有新的独立事实
│ (infer=True) │ 若 infer=False,则原文照存
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 2. DEDUPLICATION │ 基于哈希的去重(MD5 拦截完全重复)
│ │ 无 UPDATE/DELETE —— v3 是 ADD-only
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 3. STORAGE │ 批量嵌入 → 向量存储
│ │ 实体抽取 → 实体存储
└─────────┬───────────┘
│
▼
Memory Object
阶段一:抽取(Extraction)
抽取有 infer 参数控制两种模式:
推断模式(infer=True,默认):
- LLM 从对话中抽取结构化事实;
- 抽取阶段附带冲突消解:重复内容被去重,矛盾内容被消解;
- 适用于:自然对话 → 记忆的典型场景。
原文模式(infer=False):
- 文本原样存储,不经过任何 LLM 处理;
- 跳过冲突消解——同一事实可能被存两次;
- 只存储
user角色的消息,assistant消息被忽略; - 适用于:批量导入、预结构化数据、数据迁移。
警告: 不要对同一份数据混用
infer=True与infer=False,否则同一事实会被存两次。
这一点在 SDK 中有对应的类型定义:AddMemoryOptions 中 infer 是一个可选布尔字段,此外还提供 custom_categories(自定义分类标签)、custom_instructions(定制事实抽取指令)、timestamp 与 expiration_date(时间属性)、structured_data_schema(结构化数据抽取模式)等可选参数,用于在同一管线上进一步定制抽取行为。
阶段二:去重(Deduplication)
v3 的去重采用基于哈希(MD5)的精确重复拦截,且不产生 UPDATE/DELETE 操作——v3 抽取是单遍的 ADD-only 模型。这意味着记忆随时间累积(accumulate)而非被持续合并(consolidate);同一实体的认知演化通过新增记忆体现,由检索阶段的多信号打分决定哪条记忆更相关。
阶段三:存储(Storage)
通过去重后的记忆会被批量嵌入(batch embed)写入向量存储,同时完成实体抽取写入实体存储,最终落库为一条 Memory Object(结构见下文)。
v3 的异步处理模型
v3 默认异步处理 add():
- API 立即返回
{"status": "PENDING", "event_id": "evt-..."}; - 通过
GET /v1/event/{event_id}/轮询处理状态; - 也可以配置 Webhook 在处理完成时接收实时通知。
从源码看,MemoryClient.add() 对 POST /v3/memories/add/ 发起请求(L217),并把响应原样返回——PENDING + event_id 正是这个响应的内容。这一模型的实际影响是:刚调用完 add() 立即 search() 可能查不到新记忆(SKILL.md 建议等待 2~3 秒),这是异步管线的直接推论。
检索管线:client.search() 的三信号融合
当调用 client.search() 时,查询经过三个阶段:
Query In
│
▼
┌─────────────────────┐
│ 1. PREPROCESSING │ 关键词词形还原(lemmatize)、实体抽取
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 2. PARALLEL SCORING │ 语义检索(向量相似度)
│ │ BM25 关键词检索(词项匹配)
│ │ 实体匹配(实体图加权)
└─────────┬───────────┘
│
▼
┌─────────────────────┐
│ 3. SCORE FUSION │ 多路信号融合为单一分数
│ │ 可选:rerank=True 深度重排
└─────────┬───────────┘
│
▼
Results (combined score per memory)
三路并行打分分别覆盖三种匹配形态:向量相似度捕捉语义等价("不吃肉" ≈ "素食主义者"),BM25 词项匹配捕捉精确关键词(型号、代号等),实体匹配则利用实体存储中自动建立的实体链接做关系加权。三路信号在融合阶段合成单一 score;开启 rerank=True 后还会经过一轮深度重排(代价是额外延迟,见性能章节)。
v3 检索默认值
| 参数 | 默认值 | 备注 |
|---|---|---|
top_k |
20 | v2 中为 100 |
threshold |
0.1 | v2 中为 None |
rerank |
False | v2 中为 True |
这些默认值对应 SDK 中的可选参数定义:SearchMemoryOptions 暴露了 top_k、threshold、rerank、filters、categories、keyword_search(纯关键词检索)、show_expired、latest_only、reference_date(相对时间查询的参考日期)等字段,全部为可选——不传即使用上表默认值。
隐式空值作用域(Implicit null scoping)
这是 v3 检索中最容易踩坑的语义:当 filters 只带 user_id 时,Mem0 只返回 agent_id、app_id、run_id 全部为空(null)的记忆。这一设计从默认行为上防止了跨作用域泄漏(cross-scope leakage)。
若需要包含带非空作用域字段的记忆,必须改用显式 OR 过滤:
# 获取 alice 的所有记忆(无论 agent/app/run 是什么)
filters={"OR": [{"user_id": "alice"}]}
这一点在 SDK 层有硬约束佐证。mem0/client/main.py 中定义了:
ENTITY_PARAMS = frozenset({"user_id", "agent_id", "app_id", "run_id"})
search() 与 get_all() 都会拦截并拒绝顶层传入这四个实体参数(L267-L273、L316-L322 抛出 ValueError),强制要求把它们放进 filters 字典——v3 API 不接受顶层实体参数。这与 types.py 模块文档的说明一致:Identity fields (user_id, agent_id, app_id, run_id) must be passed inside the filters dict。也就是说,作用域语义不仅在服务端生效,SDK 在客户端就先行校验,确保作用域信息以统一的 filters 结构下发。
记忆生命周期(v3)
v3 使用 ADD-only 抽取:记忆随时间累积而不被合并。完整的生命周期操作在 MemoryClient 中都有对应实现:
创建
client.add(messages, user_id="alice")
单遍抽取 → 去重 → 存储,返回 {"event_id": "...", "status": "PENDING"}。
更新
client.update(memory_id, text="...") # 替换文本
client.batch_update([...]) # 批量,每批最多 1000 条
从源码看,batch_update() 走 PUT /v1/batch/(L571-L595),batch_delete() 走 DELETE /v1/batch/(L598-L621),二者共用 batch 端点、以 HTTP 方法区分。
删除
client.delete(memory_id) # 单条
client.batch_delete([...]) # 批量
client.delete_all(filters={"user_id": "alice"}) # 按条件批量清除
delete_all() 对应 DELETE /v1/memories/(L414-L442),而针对整个实体的清理(删除某 user/agent/app/run 下的一切)由 delete_users() 通过 DELETE /v2/entities/{type}/{name}/ 完成,reset() 则调用它实现全量清空。端点级细节可继续查阅 docs/api-reference/memory/ 下的 delete-memory.mdx、batch-update.mdx、batch-delete.mdx 等页面。
记忆对象(Memory Object)结构
每条记忆落库后的完整结构如下:
{
"id": "uuid-string",
"memory": "Extracted memory text",
"user_id": "user-identifier",
"agent_id": null,
"app_id": null,
"run_id": null,
"metadata": { "source": "chat", "priority": "high" },
"categories": ["health", "preferences"],
"created_at": "2025-03-12T12:34:56Z",
"updated_at": "2025-03-12T12:34:56Z",
"structured_attributes": {
"day": 12, "month": 3, "year": 2025,
"hour": 12, "minute": 34,
"day_of_week": "wednesday",
"is_weekend": false,
"quarter": 1, "week_of_year": 11
},
"score": 0.85
}
| 字段 | 类型 | 说明 |
|---|---|---|
id |
UUID | 唯一标识符,用于 update/delete |
memory |
string | 抽取或存储的文本内容 |
user_id |
string | 用户作用域(主实体) |
agent_id |
string | Agent 作用域 |
app_id |
string | 应用作用域 |
run_id |
string | 会话/运行作用域 |
metadata |
object | 自定义键值对,可用于过滤 |
categories |
array | 自动分配或自定义的分类标签 |
created_at |
datetime | 创建时间戳 |
updated_at |
datetime | 最后修改时间戳 |
structured_attributes |
object | 时间维度拆解,支持基于时间的查询 |
score |
float | 语义相似度(仅检索结果中出现,0–1) |
值得注意的两个设计点:
structured_attributes是对add()时间参数的落库体现。 在 AddMemoryOptions 中传入timestamp(Unix 时间戳)或expiration_date(YYYY-MM-DD)后,服务端会把时间戳拆解为天/月/年、时/分、星期、是否周末、季度、年度周次等字段——这是时间感知检索(如reference_date相对时间查询)的底层依据。categories支持自动与自定义两条来源。add()时可通过custom_categories传入标签 schema 覆盖自动分类;检索时又可用SearchMemoryOptions.categories按标签过滤。
作用域与多租户(Scoping & Multi-Tenancy)
Mem0 沿四个维度隔离记忆,防止数据混用:
| 维度 | 字段 | 用途 | 示例 |
|---|---|---|---|
| 用户 | user_id |
持久化的人物画像或账户 | "customer_6412" |
| Agent | agent_id |
独立的智能体或工具 | "meal_planner" |
| 应用 | app_id |
产品表面或部署 | "ios_retail_app" |
| 会话 | run_id |
短生命周期的流程或线程 | "ticket-9241" |
存储模型:每个实体组合独立成记录
每个实体组合(entity combination)都会生成独立的记录:user_id="alice" 的记忆与 user_id="alice" + agent_id="bot" 的记忆是两条不同的存储记录。这与 SDK 中 ENTITY_PARAMS 常量(main.py L40)所约束的四个身份字段一一对应。
关键陷阱:跨实体查询
# 这样查不到任何结果 —— user 记忆与 agent 记忆是分开存的
filters={"AND": [{"user_id": "alice"}, {"agent_id": "bot"}]}
# 用 OR 查询多个作用域
filters={"OR": [{"user_id": "alice"}, {"agent_id": "bot"}]}
# 用通配符 "*" 匹配任意非空值
filters={"AND": [{"user_id": "*"}]} # 所有用户(不含 null)
第一条查询返回空不是 bug 而是设计:AND 语义要求同一条记忆同时满足 user 和 agent 两个条件,而两者各自独立存储、从不落在同一条记录上。SKILL.md 的"常见边界情况"一节把这条列为高频误区。
推荐的作用域模式
# 用户级:持久化偏好
client.add(messages, user_id="alice")
# 会话级:临时上下文
client.add(messages, user_id="alice", run_id="session_123")
# 用完即清:client.delete_all(run_id="session_123")
# Agent 级:agent 专属知识
client.add(messages, agent_id="support_bot", app_id="helpdesk")
# 多租户:完全隔离
client.add(messages, user_id="alice", agent_id="bot",
app_id="acme_corp", run_id="ticket_42")
记忆分层:从单轮到终身
Mem0 支持三层记忆,按生命周期从短到长排列:
对话记忆(Conversation memory)
- 单个回合内的进行中消息;
- 包含工具调用、思维链推理;
- 生命周期: 单次响应——回合结束即消失;
- 管理者: 你的应用,而不是 Mem0。
会话记忆(Session memory)
- 面向当前任务或频道的短期事实;
- 多步流程:入职引导、调试、客服工单;
- 生命周期: 数分钟到数小时;
- 管理者: Mem0,通过
run_id参数; - 清理方式:
client.delete_all(run_id="session_id")。
用户记忆(User memory)
- 与人或账户绑定的长期知识;
- 个人偏好、账户状态、合规信息;
- 生命周期: 数周到永久;
- 管理者: Mem0,通过
user_id参数; - 跨所有会话与交互持久存在。
分层在实际代码中的组合方式
def chat(user_input: str, user_id: str, session_id: str) -> str:
# 1. 检索用户记忆(长期偏好)
user_mems = mem0.search(user_input, filters={"user_id": user_id})
# 2. 检索会话记忆(当前任务上下文)
session_mems = mem0.search(user_input, filters={
"AND": [{"user_id": user_id}, {"run_id": session_id}]
})
# 3. 两层记忆合并为 LLM 上下文
context = format_memories(user_mems) + format_memories(session_mems)
# 4. 生成响应
response = llm.generate(context=context, input=user_input)
# 5. 分别写入会话作用域(临时)与用户作用域(持久)
messages = [{"role": "user", "content": user_input},
{"role": "assistant", "content": response}]
mem0.add(messages, user_id=user_id, run_id=session_id)
return response
注意第 2 步用了 AND 过滤——这里之所以安全,是因为两个条件(user + run)指向的是同一批带 run_id 的记忆记录,而非跨记录组合;这与上文"跨实体查询"陷阱并不矛盾。
性能特征
延迟(文档给出的典型参考值)
| 操作 | 典型延迟 |
|---|---|
| 混合检索(v3 默认) | ~100–150ms |
| + rerank | 额外 +150–200ms |
| Add(异步) | 响应 < 50ms |
add() 之所以能压到 50ms 以内响应,正因为 v3 把 LLM 抽取挪到了异步后台,API 只做受理并返回 event_id。
处理模型
- 异步(默认): 立即返回,后台处理;
- 批量操作:
batch_update/batch_delete每批最多 1000 条记忆; - Webhooks: 异步处理完成时实时通知。
面向性能的作用域策略
- 面向用户的所有查询都带上
user_id(最常见、最快); - 追加
run_id做会话隔离(收窄检索空间); - 避免在大数据集上使用
"*"通配过滤(会扫描所有非空记录); - 只需要少量记忆时,用
top_k限制返回数量。
与其他方案的取舍对比
| 方案 | 优点 | 缺点 |
|---|---|---|
| 裸向量数据库 | 快、完全可控 | 无抽取、无去重、无冲突消解 |
| 内存中的聊天历史 | 零延迟 | 重启即丢失、无跨会话能力、无界增长 |
| 基于文档的 RAG | 适合静态知识 | 无个性化、记忆不可更新 |
| Mem0 Platform | 托管式抽取 + 去重 + 实体链接 + 作用域 | 外部依赖、异步处理延迟 |
Mem0 Platform 的差异化在于把向量检索(语义召回)、LLM 抽取、冲突消解(去重)与结构化多租户作用域组合进同一个托管 API,以换取应用侧零基础设施成本。
小结与延伸阅读
回顾本文的核心结论:
- 写入侧是"单遍抽取 + 哈希去重 + 双存储落库"的 ADD-only 管线,
add()默认异步、以event_id受理(add() 实现); - 检索侧是"语义 + BM25 + 实体"三信号并行打分再融合,默认
top_k=20 / threshold=0.1 / rerank=False(search() 实现); - 作用域侧由
user_id / agent_id / app_id / run_id四维独立存储,隐式空值作用域防止跨域泄漏,SDK 用ENTITY_PARAMS强制 filters 传参(main.py L40); - 分层记忆(对话/会话/用户)通过
run_id与user_id的组合落地,配合delete_all完成会话级清理。
想继续深入,可以沿以下仓库内资料阅读:
- Mem0 技能入口 SKILL.md:v3 API 变更总览、常见边界情况与技能参考索引;
- SDK 指南 与 API 参考:双语言全部方法与端点细节;
- docs/api-reference/memory/:
add-memories.mdx、search-memories.mdx、update-memory.mdx等端点级文档; - Platform v2 → v3 迁移指南:端点、参数移除(
org_id/project_id/enable_graph)与默认值变化的完整对照; - 客户端类型定义:
AddMemoryOptions/SearchMemoryOptions等 Pydantic 模型,是各参数取值与默认行为的第一手参照。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00