jiangwei.me
Back to Home

深入探究Mem0记忆库

(Updated: May 4, 2026)
ai-memory

本文部分内容由AI辅助生成,AI生成部分内容都已经过人工审核验证

GitHub: https://github.com/mem0ai/mem0
项目版本: v2.0.1(来自 pyproject.toml)
Git Commit: 6d3486ca
代码行数: ~138,621 行
语言: Python + TypeScript(双 SDK 仓库)

目录

Repo 结构

这是一个 Polyglot Monorepo,包含 Python 和 TypeScript 两套 SDK,以及其他组件:

目录说明包名
mem0/Python SDK(同步 + 异步),核心实现mem0ai(PyPI)
mem0-ts/TypeScript SDK(同步 + 异步),与 Python 功能对齐mem0ai(npm)
cli/python/Python CLImem0-cli(PyPI)
cli/node/Node CLI@mem0/cli(npm)
server/自托管 FastAPI 服务器
openmemory/自托管记忆平台(API + Next.js UI)
vercel-ai-sdk/Vercel AI SDK Provider@mem0/vercel-ai-provider
openclaw/OpenClaw AI Agent 运行时平台记忆插件@mem0/openclaw-mem0
mem0-plugin/AI 编辑器插件(MCP 服务器)
docs/Mintlify 文档站点
evaluation/基准测试框架(LOCOMO 等)

本文档聚焦mem0/(Python SDK)和 mem0-ts/(TypeScript SDK)的核心实现。两者功能完全对等,以下代码示例以 Python 为主,TypeScript 对应 API 保持一致。

版本概述

Mem0 经历了几个重大版本演进:

版本发布时间核心变化
v0.1.x2024-2025早期版本,基础记忆存储功能
v1.0.02025-10正式版本,稳定 API,新增 Reranker、元数据过滤、Azure 向量存储
v2.0.02026-04V3 内存管道升级:单次 ADD-only 提取、混合检索、实体链接(实体关联通过内置 Entity Collection 实现,替代默认依赖外部图数据库的方案)

V3 说明:文档中提到的”V3 管道”是代码内部的管道版本号(# === V3 PHASED BATCH PIPELINE ===),并非产品版本号。产品版本号为 v2.0.1。

v2.0 核心变化

  1. Single-pass ADD-only 提取:从 2 次 LLM 调用简化为 1 次,延迟降低约 50%。不再产生 UPDATE/DELETE 事件,只产生 ADD 事件,记忆通过 linked_memory_ids 关联
  2. Multi-signal 混合检索:语义向量 + BM25 关键词 + 实体匹配三种信号融合
  3. 内置实体链接:替代 Graph Memory,无需外部图数据库

迁移注意:v2.0 后,add() 不再产生 UPDATE/DELETE 事件。如需更新或删除记忆,需手动调用 update() / delete() API。

项目背景

团队信息

Mem0 由 Taranjeet Singh(CEO)和 Deshraj Yadav(CTO)于 2024 年创立,公司总部位于旧金山。

创始人背景
Taranjeet Singh前 Paytm 工程师、Khatabook 高级产品经理。创建了 EmbedChain(8.9K GitHub Stars)
Deshraj Yadav前 Tesla Autopilot 高级工程师,负责 AI 平台后端架构设计。创建了 EvalAI(2K+ GitHub Stars)

孵化背景:Mem0 是 Y Combinator Summer 2024 批次的创业公司,于 2025 年 10 月完成 $24M 融资(Seed + Series A)。

商业化模式

Mem0 采用 开源 + 托管平台 双模式:

模式说明适用场景
开源 SDKApache-2.0 许可,完全免费,自托管数据敏感、需要完全控制、成本敏感
托管平台SaaS 服务,按量付费快速迭代、生产环境、无需运维

托管平台定价

方案价格记忆数量API 调用/月特性
Hobby免费10,0001,000社区支持
Starter$19/月50,0005,000社区支持
Pro$249/月无限50,000Graph Memory、高级分析
Enterprise定制无限无限私有部署、SSO、SLA

维护模式

方面说明
开源维护GitHub 社区贡献 + 核心团队审核合并
核心团队分布在旧金山、印度等地
发布节奏频繁迭代,平均每周 1-2 个版本
社区规模GitHub 54.7K+ Stars,100K+ 开发者使用平台
组织规模GitHub 组织 1.1K+ followers,9 个开源仓库

相关项目

Mem0 团队还维护了以下开源项目:

项目说明GitHub Stars
EmbedChainRAG 框架,简化 LLM 应用开发(Mem0 的前身项目,200万+ 下载)已归档,不再维护
EvalAIAI 模型评估平台,支持大规模 ML/AI 算法评测2K+

什么是 Mem0?

Mem0(发音为 “mem-zero”)是一个专为 AI Agent 和助手设计的智能记忆层。它的核心价值在于:为 AI 应用提供持久化、个性化的记忆能力,让 AI 从”陌生人”变成”认识你的老朋友”。

快速上手

from mem0 import Memory

# 初始化记忆实例
memory = Memory()

# ========== 存储记忆 ==========
# 从对话中自动提取并存储记忆
memory.add(
    messages=[{"role": "user", "content": "我叫小明,是一名程序员,最近在学习 Rust"}],
    user_id="user_123"
)
# 返回: {"results": [{"id": "mem-uuid-1", "memory": "用户叫小明,是一名程序员,最近在学习 Rust", "event": "ADD"}]}

# ========== 检索记忆 ==========
# 语义搜索相关记忆
results = memory.search("用户是做什么的?", filters={"user_id": "user_123"})
# 返回: {"results": [{"id": "mem-uuid-1", "memory": "用户叫小明,是一名程序员...", "score": 0.92}]}

# ========== 获取所有记忆 ==========
# 列出用户的所有记忆
all_memories = memory.get_all(filters={"user_id": "user_123"})
# 返回: {"results": [{"id": "mem-uuid-1", "memory": "...", ...}, ...]}

# ========== 获取单条记忆 ==========
memory_item = memory.get(memory_id="mem-uuid-1")

# ========== 更新记忆 ==========
# 手动更新记忆内容(会产生 UPDATE 事件)
memory.update(memory_id="mem-uuid-1", data="用户叫小明,是一名高级程序员,精通 Rust")

# ========== 删除记忆 ==========
# 手动删除记忆(会产生 DELETE 事件)
memory.delete(memory_id="mem-uuid-1")

# ========== 清空记忆 ==========
# 删除用户的所有记忆
memory.delete_all(user_id="user_123")

# ========== 查看历史 ==========
# 查看记忆的变更历史
history = memory.history(memory_id="mem-uuid-1")
# 返回: [{"event": "ADD", "old_memory": null, "new_memory": "...", "created_at": "..."}]

核心能力

方法说明
add()智能提取并存储记忆(LLM 驱动)
search()混合检索(语义 + 关键词 + 实体)
get_all()获取所有记忆
get()获取单条记忆
update()手动更新记忆
delete()手动删除记忆
history()查看记忆变更历史

核心架构

Mem0 采用典型的分层架构,各组件职责清晰:

Memory 类
├── embedding_model  → 向量化模型(文本 → 向量)
├── vector_store      → 向量数据库(记忆存储)
├── llm               → 大语言模型(理解记忆内容)
├── db                → SQLite(历史消息)
└── reranker          → 重排序模型(可选)

关键特点:

  • 工厂模式:通过 Factory 类动态创建各种 Provider
  • 配置驱动:使用 Pydantic 管理所有配置
  • 懒加载:Entity Store 在首次使用时才初始化

数据存储

Mem0 采用双存储架构:关系型数据库管理历史消息和记忆变更历史,向量数据库存储记忆向量。

关系型存储(历史消息)

数据库支持

数据库Python SDKTypeScript SDK用途生产环境
SQLite✅ 默认✅ 默认历史消息 + 变更历史✅ 推荐(本地部署)
Supabase (PostgreSQL)✅ 可选云端历史存储✅ 推荐(云端部署)

Python SDK:SQLite

Python SDK 目前仅支持 SQLite 作为关系型存储。配置项为 history_db_path

# 本地文件(持久化)
config = {"history_db_path": "/data/mem0_history.db"}

# 内存数据库(临时,适合测试)
config = {"history_db_path": ":memory:"}

SQLite 管理两类数据:

1. history :记忆变更历史

CREATE TABLE IF NOT EXISTS history (
    id TEXT PRIMARY KEY,
    memory_id TEXT,
    old_memory TEXT,
    new_memory TEXT,
    event TEXT,
    created_at DATETIME,
    updated_at DATETIME,
    is_deleted INTEGER,
    actor_id TEXT,
    role TEXT
);
字段类型说明
idTEXT (PK)记录唯一ID (UUID)
memory_idTEXT关联的记忆ID
old_memoryTEXT变更前的记忆内容
new_memoryTEXT变更后的记忆内容
eventTEXT事件类型:ADD / UPDATE / DELETE
created_atDATETIME记忆创建时间
updated_atDATETIME记录更新时间
is_deletedINTEGER软删除标记 (0/1)
actor_idTEXT发言者标识(来自消息的 name 字段,用于多参与者场景)
roleTEXT触发此操作的消息角色(userassistant),记录记忆是由用户消息还是 AI 回复触发生成/修改的

事件类型说明

事件触发方式说明
ADDmemory.add()新增记忆,由 LLM 从对话中智能提取
UPDATEmemory.update(memory_id, data)手动更新记忆内容,用户主动调用 API
DELETEmemory.delete(memory_id)手动删除记忆,用户主动调用 API

V3 管道变化

  • V3 之前add() 可能产生 ADD、UPDATE、DELETE 三种事件,LLM 会判断是否需要更新或删除已有记忆
  • V3 之后add() 只产生 ADD 事件,不再自动产生 UPDATE 和 DELETE。记忆通过 linked_memory_ids 建立关联,而非直接修改已有记忆

迁移注意:如需更新或删除记忆,必须手动调用 update() / delete() API。

2. messages :历史会话消息

字段类型说明
idTEXT (PK)消息唯一ID
session_scopeTEXT会话作用域标识
roleTEXT角色:user / assistant
contentTEXT消息原文
nameTEXT发言者名称(如多轮对话中的发言人)
created_atDATETIME消息时间

session_scope 构建规则

session_scope 是一个字符串,由 user_idagent_idrun_id 组合而成,用于隔离不同会话的消息:

def _build_session_scope(filters):
    """
    根据 user_id、agent_id、run_id 构建会话作用域标识。
    
    任何一个参数不同,都会产生不同的 session_scope,
    从而隔离不同会话的历史消息。
    """
    parts = []
    # 按 key 排序,确保相同参数组合产生相同的 session_scope
    for key in sorted(["user_id", "agent_id", "run_id"]):
        val = filters.get(key)
        if val:
            parts.append(f"{key}={val}")
    return "&".join(parts)  # 返回字符串,如 "user_id=user_123&agent_id=agent_456"

示例

调用方式session_scope 值
memory.add(messages, user_id="user_123")"user_id=user_123"
memory.add(messages, user_id="user_123", agent_id="agent_456")"user_id=user_123&agent_id=agent_456"
memory.add(messages, user_id="user_123", agent_id="agent_456", run_id="run_789")"user_id=user_123&agent_id=agent_456&run_id=run_789"

关键点"user_id=user_123""user_id=user_123&agent_id=agent_456"不同的 session_scope,它们的消息互不干扰。

隔离意义

场景session_scope说明
同一用户,无 agentuser_id=user_123用户的所有消息共享上下文
同一用户,不同 agentuser_id=user_123&agent_id=agent_456不同 agent 的消息隔离
同一用户同一 agent,不同 runuser_id=user_123&agent_id=agent_456&run_id=run_789不同运行实例的消息隔离

实际影响

# 第一次调用:session_scope = "user_id=user_123"
memory.add([{"role": "user", "content": "我叫小明"}], user_id="user_123")

# 第二次调用:session_scope = "user_id=user_123&agent_id=agent_456"(不同的 session!)
memory.add([{"role": "user", "content": "我喜欢编程"}], user_id="user_123", agent_id="agent_456")

# 结果:第二次调用无法获取第一次的消息作为上下文,因为 session_scope 不同

核心作用

messages 表不是用来存储记忆的,而是为 LLM 提供上下文理解的支撑:

  1. 代词消解:当用户说”他”、“它”、“那个”时,LLM 需要历史消息来理解指代对象
  2. 上下文连贯:理解”继续”、“还是那个问题”等需要前文背景的表达
  3. 时间推理:帮助 LLM 解析”昨天”、“上周”等相对时间

工作流程

用户发送新消息

Phase 0: 根据 session_scope 获取最近 10 条历史消息

将历史消息 + 新消息一起传给 LLM

LLM 提取记忆时可以理解完整上下文

Phase 8: 将当前消息存入 messages 表(带 session_scope)

获取消息逻辑

# 只获取相同 session_scope 的消息
def get_last_messages(self, session_scope: str, limit: int = 10):
    query = """
        SELECT role, content, name 
        FROM messages 
        WHERE session_scope = ? 
        ORDER BY created_at DESC 
        LIMIT ?
    """
    return self.db.execute(query, (session_scope, limit)).fetchall()

自动清理机制:每次保存消息时,自动删除该 session_scope 超过 10 条的旧消息,保持每个会话只保留最近 10 条。这样既保证上下文足够,又避免存储膨胀。

TypeScript SDK:SQLite + Supabase

TypeScript SDK 提供了可插拔的历史存储提供者接口(HistoryManager):

// 默认使用 SQLite(本地 better-sqlite3)
const historyManager = new SQLiteManager({
  dbPath: "./mem0_history.db",
});

// 或者使用 Supabase(云端 PostgreSQL)
const historyManager = new SupabaseHistoryManager({
  client: createClient(supabaseUrl, supabaseKey),
});

HistoryManager 接口定义如下(位于 mem0-ts/src/oss/src/storage/base.ts):

interface HistoryManager {
  addHistory(memoryId, previousValue, newValue, action, ...): Promise<void>;
  getHistory(memoryId): Promise<any[]>;
  reset(): Promise<void>;
  close(): void;

  // V3 可选方法 — 不需要的实现可以省略
  saveMessages?(messages, sessionScope): Promise<void>;
  getLastMessages?(sessionScope, limit?): Promise<...>;
  batchAddHistory?(records): Promise<void>;
}

各实现类

  • SQLiteManager:实现了全部 7 个方法,同时管理 memory_historymessages 两张表
  • SupabaseHistoryManager:实现了 4 个必需方法(addHistory, getHistory, reset, close),通过 memory_history 表(Supabase PostgreSQL)存储历史
  • MemoryHistoryManager:内存实现,同样只实现 4 个必需方法
  • DummyHistoryManager:空实现,当 disableHistory=true 时使用

选择建议

  • 本地开发/单机部署SQLiteManager,零依赖,性能优秀,功能完整(含消息上下文管理)
  • 云端多实例部署SupabaseHistoryManager,支持水平扩展(注意:不支持消息管理,需自行处理会话上下文)
  • Python SDK:目前仅支持 SQLite,如需云端存储建议使用托管平台 API

向量数据库(记忆向量存储)

向量数据库是 Mem0 的核心存储,以 Qdrant 为例介绍其数据结构。

支持 30+ 向量数据库,包括:

分类数据库说明
托管云服务Pinecone, Qdrant Cloud, Weaviate Cloud, Milvus Cloud, Elasticsearch Cloud零运维,适合生产
自托管Qdrant, Milvus, ChromaDB, Weaviate, Faiss, ElasticsearchDocker/K8s 部署
关系型扩展PGVector, Supabase, Azure MySQL基于现有数据库的向量扩展
NoSQL 扩展MongoDB, Redis, DynamoDB, S3 Vectors多用途存储的向量能力

主记忆 Collection

默认名称:mem0(配置项:collection_name,可通过配置修改)

这是存储所有记忆的核心 Collection,每条记忆以 Point(Qdrant 的数据单元)形式存储。

Collection 配置

配置项英文配置名默认值说明
Collection 名称collection_name"mem0"Collection 的唯一标识
向量维度embedding_model_dims1536由 Embedding Model 决定(如 OpenAI text-embedding-3-small 为 1536)
距离度量distanceCOSINE支持:Cosine(余弦相似度)、Euclidean(欧氏距离)、Dot Product(点积)
持久化on_diskFalse向量存储位置,见下方详细说明
存储路径path"/tmp/qdrant"本地数据库文件路径

on_disk 持久化说明

on_disk 值存储位置特点适用场景
False(默认)内存速度快,重启后数据丢失开发测试、临时缓存
True磁盘速度稍慢,数据持久保存生产环境、长期存储

注意on_diskFalse/True 决定了向量数据是存内存还是磁盘。请对照上表选择:False 时重启数据丢失,True 时持久保存。Qdrant 本地模式会在 path 下创建 RocksDB/WAL 等元数据文件,但这不等于向量数据的持久化——只有 on_disk=True 才能保证向量数据重启后不丢失。

向量类型

向量类型英文名用途
Dense Vectorvectors语义搜索,由 Embedding Model 生成
Sparse BM25 Vectorsparse_vectors_config关键词搜索,由 fastembed 的 BM25 模型生成

距离度量选择

度量方式英文名适用场景支持的向量库
CosineDistance.COSINE文本语义相似度(推荐)Qdrant, Pinecone, FAISS, Milvus…
Euclidean (L2)Distance.EUCLID需要绝对距离度量Qdrant, Milvus, FAISS…
Dot ProductDistance.DOT已归一化向量Qdrant, FAISS…

Point 结构

Point 是 Qdrant 的核心数据单元,包含 ID、向量和元数据:

{
  "id": "uuid-string",
  "vector": {
    "": [0.123, -0.456, ...],    // Dense 向量(主向量,用于语义搜索)
    "bm25": {                     // Sparse 向量(用于关键词搜索)
      "indices": [12, 45, 89],
      "values": [0.8, 0.6, 0.4]
    }
  },
  "payload": {
    "data": "User's name is Marcus and works as a Senior Engineer at Shopify",
    "text_lemmatized": "user name be marcus and work as a senior engineer at shopify",
    "hash": "a1b2c3d4...",
    "user_id": "user_123",
    "agent_id": null,
    "run_id": null,
    "actor_id": null,
    "role": "user",
    "created_at": "2026-05-03T10:30:00Z",
    "updated_at": "2026-05-03T10:30:00Z",
    "attributed_to": null
  }
}

关键字段说明

  • data:记忆的原始文本
  • text_lemmatized词形还原后的文本,用于 BM25 关键词搜索。将词语还原为词根形式,如:
    • 原文:"User's name is Marcus and works as a Senior Engineer"
    • 词形还原后:"user name be marcus and work as a senior engineer"(works → work, is → be)
  • hash:MD5 哈希值,用于精确去重
  • user_id用户标识,用于跨会话记忆共享
  • agent_idAgent 标识,用于区分不同 AI Agent 的记忆
  • run_id运行标识,用于区分同一 Agent 的不同运行实例
  • actor_id发言者标识,来自消息中的 name 字段,用于多参与者场景(如群聊、多角色对话)中区分不同发言人。例如:{"role": "assistant", "content": "...", "name": "Maria"} 会将 actor_id 设为 “Maria”
  • role触发消息角色,记录此记忆是由 user 消息还是 assistant 消息触发生成的

实体 Collection

默认名称:mem0_entities(主 Collection 名称 + _entities 后缀)

什么是实体?

实体是从记忆文本中提取的命名实体,代表记忆中提到的具体对象。

实体提取方式

Mem0 使用 spaCy NLP 模型(而非 LLM)进行实体提取,这是一种基于规则和词性标注的轻量级方法:

from mem0.utils.entity_extraction import extract_entities

entities = extract_entities("Marcus has a dog named Poppy and works at Shopify")
# 返回示例: [("PROPER", "Marcus"), ("PROPER", "Poppy"), ("PROPER", "Shopify")]

为什么使用 spaCy 而非 LLM?

对比项spaCy NLPLLM
速度毫秒级秒级
成本无 API 调用每次 API 调用有成本
准确性对命名实体识别足够准确更灵活但成本高
批量处理支持 nlp.pipe() 高效批处理需要多次 API 调用

spaCy 提取的四类实体

实体类型说明示例
PROPER专有名词(人名、地名、品牌名)“Marcus”, “Shopify”, “Osteria Francescana”
QUOTED引号内的文本(书名、电影名、特定术语)“‘The Nightingale’”, “‘Eternal Sunshine‘“
COMPOUND复合名词短语”machine learning”, “senior engineer”
NOUN单个名词(作为兜底)“dog”, “cat”, “project”

实体提取示例

原文:"Marcus has a dog named Poppy and works at Shopify"

提取结果:

  • ("PROPER", "Marcus")
  • ("PROPER", "Poppy")
  • ("PROPER", "Shopify")

实体 Collection 的 Point 结构

{
  "id": "entity-uuid",
  "vector": [0.1, 0.2, ...],    // 实体文本的向量
  "payload": {
    "data": "Marcus",
    "entity_type": "PROPER",
    "linked_memory_ids": ["mem-uuid-1", "mem-uuid-2"],    // 关联的记忆 ID 列表
    "user_id": "user_123",
    "agent_id": null,
    "run_id": null
  }
}

实体的作用

  1. 关系查询:通过 linked_memory_ids 快速找到所有提到某实体的记忆
  2. 实体提升:搜索时如果查询包含实体,相关记忆的得分会被提升
  3. 知识图谱基础:为构建更复杂的知识图谱提供基础

实体链接机制

当新记忆被添加时:

  1. 从记忆文本中提取所有实体
  2. 对每个实体,在实体 Collection 中搜索相似度 ≥ 0.95 的已有实体
  3. 如果找到匹配:更新已有实体的 linked_memory_ids,添加新记忆 ID
  4. 如果未找到:创建新实体,linked_memory_ids 设为当前记忆 ID

混合检索

Mem0 v3 采用混合检索策略,结合三种信号进行记忆召回。

检索流程

graph TD
    A["用户查询"] --> B["Step 1: 查询预处理"]
    B --> B1["词形还原"]
    B --> B2["实体提取"]
    B --> C["Step 2: 查询向量化"]
    C --> D["Step 3: 语义检索<br>Dense Vector"]
    C --> E["Step 4: 关键词检索<br>BM25 Sparse Vector"]
    D --> F["语义结果<br>top_k * 4"]
    E --> G["BM25 原始分数"]
    G --> H["Step 5: BM25 分数归一化<br>Sigmoid → [0,1]"]
    B2 --> I["Step 6: 实体提升计算"]
    F --> J["Step 7: 构建候选集"]
    H --> K["Step 8: 综合评分"]
    I --> K
    J --> K
    K --> L["Step 9: 排序返回"]

三种检索信号

信号类型来源作用
语义分数Dense Vector 相似度捕捉语义相似性,“苹果手机” 和 “iPhone” 语义相近
BM25 分数Sparse Vector 关键词匹配精确关键词匹配,“iPhone 15” 能精确匹配
实体提升实体 Collection查询包含实体时,相关记忆得分提升

Step 1-2: 查询预处理

# 词形还原:将词语还原为词根形式,用于 BM25 关键词匹配
# 例如:"works" → "work", "is" → "be", "running" → "run"
query_lemmatized = lemmatize_for_bm25(query)

# 实体提取:使用 spaCy NLP 模型提取命名实体
# 例如:"Marcus's dog Poppy" → [("PROPER", "Marcus"), ("PROPER", "Poppy")]
query_entities = extract_entities(query)

# 查询向量化:使用 Embedding Model 将查询文本转换为向量
# 用于后续的语义相似度检索
embeddings = embedding_model.embed(query)

各步骤执行者

步骤执行者说明
词形还原lemmatize_for_bm25()内置函数,基于 spaCy 词性标注
实体提取extract_entities()spaCy NLP 模型(非 LLM)
查询向量化embedding_model.embed()配置的 Embedding Provider(如 OpenAI)

Step 3: 语义检索

使用 Dense Vector 进行相似度检索:

internal_limit = max(limit * 4, 60)    # 过采样:取 limit * 4 或 60 中的较大值
semantic_results = vector_store.search(
    query=query, 
    vectors=embeddings, 
    top_k=internal_limit, 
    filters=filters
)

过采样原因:语义检索只是第一步,后续还要结合 BM25 和实体提升重新排序,需要足够的候选池。

Step 4-5: 关键词检索与 BM25 归一化

BM25 原始分数是无界的(通常 0-20+),需要归一化到 [0, 1]:

# 使用 Sigmoid 函数归一化
normalized = 1.0 / (1.0 + exp(-steepness * (raw_score - midpoint)))

Sigmoid 参数自适应:根据查询词数量调整

查询词数量midpointsteepness
≤ 35.00.7
4-67.00.6
7-99.00.5
10-1510.00.5
> 1512.00.5

原因:长查询的 BM25 原始分数通常更高,需要调整参数保证归一化效果。

Step 6: 实体提升

如果查询中包含实体,从实体 Collection 中查找相关记忆并给予提升。

实体提升计算流程

def _compute_entity_boosts(query_entities, filters):
    # 1. 实体去重(最多处理 8 个)
    deduped_entities = deduplicate(query_entities[:8])
    
    memory_boosts = {}
    
    for entity_type, entity_text in deduped_entities:
        # 2. 将实体文本向量化
        entity_embedding = embedding_model.embed(entity_text, "search")
        
        # 3. 在实体 Collection 中搜索相似实体(阈值 >= 0.5)
        matches = entity_store.search(
            query=entity_text,
            vectors=entity_embedding,
            top_k=500,
            filters=filters,
        )
        
        for match in matches:
            if match.score < 0.5:
                continue
            
            # 4. 获取该实体关联的记忆 ID 列表
            linked_memory_ids = match.payload.get("linked_memory_ids", [])
            
            # 5. 计算衰减因子:关联记忆越多,单个记忆的提升越小
            num_linked = len(linked_memory_ids)
            memory_count_weight = 1.0 / (1.0 + 0.001 * ((num_linked - 1) ** 2))
            
            # 6. 计算最终提升分数
            # boost = 相似度 * 0.5 * 衰减因子
            boost = match.score * 0.5 * memory_count_weight
            
            # 7. 将提升分数分配给关联的记忆
            for memory_id in linked_memory_ids:
                memory_boosts[memory_id] = max(memory_boosts.get(memory_id, 0), boost)
    
    return memory_boosts

衰减因子说明

关联记忆数衰减因子说明
11.000单记忆实体,无衰减
50.984轻微衰减
100.917中等衰减
500.310显著衰减
1000.091强衰减

衰减原因:如果一个实体关联了太多记忆(如 “User” 可能关联所有记忆),则该实体对单个记忆的区分度较低,需要衰减以避免过度提升。

示例

查询:"Marcus's dog Poppy"

提取实体:[("PROPER", "Marcus"), ("PROPER", "Poppy")]

实体 “Poppy” 在实体 Collection 中找到匹配(相似度 0.95),关联记忆 ["mem-uuid-1"]

boost = 0.95 * 0.5 * 1.0 = 0.475

最终 entity_boosts = {"mem-uuid-1": 0.475}

Step 7-8: 综合评分与合并

这是混合检索的核心。候选集来自语义检索结果,然后叠加 BM25 分数和实体提升:

for candidate in semantic_results:
    semantic_score = candidate.score           # 语义分数(已归一化,0-1)
    bm25_score = bm25_scores.get(mem_id, 0)    # BM25 分数(已归一化,0-1)
    entity_boost = entity_boosts.get(mem_id, 0) # 实体提升(0-0.5)
    
    raw_combined = semantic_score + bm25_score + entity_boost
    combined = raw_combined / max_possible     # 归一化到 [0, 1]

max_possible 计算

激活的信号max_possible说明
仅语义1.0只有语义分数
语义 + BM252.0两种信号各占 50%
语义 + 实体1.5实体提升权重 0.5
语义 + BM25 + 实体2.5完整混合检索

阈值过滤:语义分数低于 threshold(默认 0.1)的候选会被直接排除,即使 BM25 或实体提升很高也无法挽救。

Step 9: 排序返回

按综合分数降序排列,返回 top_k 结果。

示例

查询:"Marcus's dog Poppy"

记忆语义分数BM25 分数实体提升综合分数
”Marcus has a dog named Poppy”0.850.720.5(0.85+0.72+0.5)/2.5 = 0.828
”Marcus works at Shopify”0.700.300.25(0.70+0.30+0.25)/2.5 = 0.50
”User has a cat”0.450.00.00.45/1.0 = 0.45

索引与过滤

向量数据库为常见过滤字段创建了索引:

  • user_id (keyword)
  • agent_id (keyword)
  • run_id (keyword)
  • actor_id (keyword)

支持丰富的过滤操作:eq, ne, in, nin, gt, gte, lt, lte, contains, icontains,以及逻辑组合 AND, OR, NOT

重排序(Reranker)

Mem0 支持在搜索结果返回前进行二次重排序,通过专门的 Reranker 模型对候选记忆进行精细打分,提升检索精度。

工作流程

混合检索(语义 + BM25 + 实体)

        候选记忆列表

    rerank=True 启用重排序?
       ↙        ↘
     Yes         No
      ↓           ↓
  Reranker      直接返回
  重新打分

  按 rerank_score 排序

    返回结果

支持的 Reranker

Reranker说明依赖包适用场景
cohereCohere Rerank APIcohere云端服务,高质量
sentence_transformer本地 CrossEncoder 模型sentence-transformers本地部署,无 API 成本
llmLLM 对每个文档打分需要 LLM 配置灵活,但成本较高
zero_entropyZero Entropy Rerank APIzeroentropy云端服务

配置方式

from mem0 import Memory

# 配置 Cohere Reranker
memory = Memory(config={
    "reranker": {
        "provider": "cohere",
        "config": {
            "model": "rerank-english-v3.0",
            "top_k": 10
        }
    }
})

# 配置本地 Sentence Transformer Reranker
memory = Memory(config={
    "reranker": {
        "provider": "sentence_transformer",
        "config": {
            "model": "cross-encoder/ms-marco-MiniLM-L-6-v2",
            "device": "cuda"  # 或 "cpu"
        }
    }
})

使用方式

# 搜索时启用重排序
results = memory.search(
    "用户喜欢什么编程语言?",
    filters={"user_id": "user_123"},
    rerank=True  # 启用重排序
)

# 返回结果会包含 rerank_score 字段
# {"results": [{"id": "...", "memory": "...", "score": 0.85, "rerank_score": 0.92, ...}]}

注意事项

  1. 性能影响:重排序会增加额外延迟(Cohere API 约 100-300ms,本地模型取决于硬件)
  2. 成本:Cohere 和 LLM Reranker 会产生 API 调用成本
  3. 容错:如果重排序失败,会自动降级返回原始排序结果
  4. top_k:Reranker 的 top_k 会覆盖搜索的 top_k 参数

记忆存储:add() 核心流程

add() 方法在 mem0/memory/main.py 文件中实现,内部调用 _add_to_vector_store() 方法执行 V3 分阶段批处理管道(V3 是内部管道版本号,非产品版本)。当调用 memory.add() 时,实际上发生了一件相当复杂的事情。

流程图

graph TD
    A["add() 入口<br>messages 列表"] --> B{"infer=True?"}
    B -->|"Yes"| C["V3 批处理管道"]
    B -->|"No"| D["快速直存模式"]
    C --> E["Phase 0: 上下文收集"]
    D --> H["直接插入向量库"]
    E --> F["Phase 1: 现有记忆检索"]
    F --> G["Phase 2: LLM 智能提取"]
    G --> I["Phase 3: 批量向量化"]
    I --> J["Phase 4-5: 去重与哈希"]
    J --> K["Phase 6: 批量持久化"]
    K --> L["Phase 7: 实体链接"]
    L --> M["Phase 8: 保存消息"]
    M --> N["返回结果"]
    H --> N

infer 参数说明

add() 方法接受一个 infer 参数(默认 True),决定记忆的处理方式:

infer 值模式说明
True(默认)V3 批处理管道通过 LLM 智能提取记忆,支持去重、关联、实体提取
False快速直存模式直接将消息内容作为记忆存储,不经过 LLM 处理

快速直存模式infer=False):

infer=False 时,Mem0 会:

  1. 跳过所有 LLM 调用
  2. 直接将每条消息的 content 作为记忆文本
  3. 为每条消息生成向量并插入向量库
  4. 返回插入的记忆列表

适用场景

  • 需要快速存储大量原始消息
  • 不需要智能提取和去重
  • 成本敏感场景(节省 LLM API 调用)

注意:快速直存模式下,不会进行:

  • 语义去重
  • 实体提取
  • 记忆关联
  • 上下文理解

Phase 0: 上下文收集

从 SQLite 获取最近 10 条历史消息,用于让 LLM 理解对话上下文。

Phase 1: 现有记忆检索

  1. 将新消息向量化 → query_embedding
  2. 从向量数据库搜索 Top 10 相关记忆
  3. UUID 映射为整数(防 LLM 产生幻觉 ID)

Phase 2: LLM 智能提取

这是 Mem0 最核心的步骤。Mem0 调用 LLM,让 AI 决定应该记住什么。

Prompt 构成

LLM 接收的 Prompt 由系统提示词 + 用户提示词组成:

系统提示词ADDITIVE_EXTRACTION_PROMPT)定义角色和提取规则:

  • 你是”记忆提取器”,负责从对话中提取丰富、上下文相关的事实
  • 从 user 和 assistant 消息中提取信息
  • user 消息揭示个人事实、偏好、计划、经历
  • assistant 消息包含推荐、计划、建议和用户可能参考的可操作信息

任务提示词generate_additive_extraction_prompt() 动态构建,作为 LLM 的具体任务输入,包含以下部分:

组成部分说明
Summary用户画像的叙述性摘要(从已有记忆中生成)
Last k Messages最近 10 条历史消息(用于理解上下文和指代消解)
Recently Extracted Memories本次会话中已提取的记忆(用于会话内去重)
Existing Memories系统中现有相关记忆(来自向量数据库,用于语义去重)
New Messages当前需要提取的新消息
Observation Date对话实际发生的日期(用于解析相对时间)
Current Date当前系统日期
Custom Instructions用户自定义指令(如有)

Prompt 完整内容

以下是 Mem0 使用的系统提示词 ADDITIVE_EXTRACTION_PROMPT 的核心部分(完整 Prompt 还包含 Summary、Recently Extracted Memories、Last k Messages、Current Date、Optional Inputs、Casual Topics、Incidental Facts、Shared Photos 等段落,此处为关键摘录):

英文原文(关键部分):

# ROLE

You are a Memory Extractor — a precise, evidence-bound processor responsible for 
extracting rich, contextual memories from conversations. Your sole operation is ADD: 
identify every piece of memorable information and produce self-contained, contextually 
rich factual statements.

You extract from BOTH user and assistant messages. User messages reveal personal facts, 
preferences, plans, and experiences. Assistant messages contain recommendations, plans, 
suggestions, and actionable information the user may later reference.

Accuracy and completeness are critical. Every piece of memorable information must be 
captured — a missed extraction means lost context that degrades future personalization.

# INPUTS

## New Messages
The current conversation turn(s) with "role" (user/assistant) and "content".

Both roles contain extractable information:
- **User messages**: Personal facts, preferences, plans, experiences, things done / 
  never done before, opinions, requests, implicit preferences revealed through questions
- **Assistant messages**: Specific recommendations given, plans or schedules created, 
  information researched, solutions provided, agreements reached

## Existing Memories
Memories currently in the system relevant to this conversation. Formatted as:
[{"id": "uuid-string", "text": "..."}, ...]

Use these ONLY for deduplication and linking — do NOT extract new memories from 
Existing Memories. Your extractions must come exclusively from New Messages.

## Observation Date
When the conversation actually took place (e.g., "2023-05-24"). This is your ONLY 
temporal anchor for resolving time references.

Resolve ALL relative references against Observation Date:
- "yesterday" → day before Observation Date
- "last week" → week preceding Observation Date
- "next month" → month following Observation Date

CRITICAL: "User went to Paris last week" is useless 6 months later. 
"User went to Paris the week of May 15, 2023" is meaningful forever.

# GUIDELINES

## What to Extract

**From user messages:**
- Personal details, preferences, plans, relationships, professional context
- Health/wellness, opinions, hobbies, emotional states
- Entity attributes (breed, model, color, make, size)
- Implicit preferences revealed through requests
- **Shared content and reference material** — when a user shares documents, case studies, 
  articles, data, specifications, code, extract the key factual data FROM that content
- Firsts and milestones — 'first call-out', 'just started', 'recently joined', etc.

**From assistant messages (ONLY when genuinely new):**
- Specific recommendations given (books, restaurants, products, services)
- Plans or schedules created for the user
- Information researched or provided (facts, instructions, solutions)
- Agreements reached during conversation

Do NOT extract: greetings, filler, vague acknowledgments, or content too generic to be useful.

**When in doubt, extract.** A slightly redundant memory is far less costly than a missing one.

## Memory Quality Standards

### Contextually Rich, Not Atomic
Bad: "User has a dog"
Good: "User has a dog named Poppy and their morning walks together are the highlight of their day"

### Self-Contained
Every memory must be understandable on its own. Replace all pronouns with specific names or "User."

### Concise but Complete (15-80 words, up to 100 for detail-rich content)
1-2 sentences per memory (up to 3 for content with multiple proper nouns). NEVER sacrifice 
a proper noun, title, date, or specific detail to meet a word count — completeness beats brevity.

### Temporally Grounded
Preserve exact dates, durations. Convert relative → absolute using Observation Date.

### Numerically Precise
"416 pages" stays "416 pages", not "about 400 pages."

### Preserve Specific Details — Never Generalize Concrete Information
Book titles, movie titles, restaurant names, brand names are the HIGHEST-VALUE details.
- "watched 'Eternal Sunshine of the Spotless Mind'" → KEEP the full title
- "tried the new restaurant Osteria Francescana" → KEEP "Osteria Francescana", NOT "a new restaurant"

## Memory Linking

When extracting a new memory, check if it relates to any Existing Memory. Add related 
Existing Memory IDs to "linked_memory_ids". Link when:
- **Same entity/topic**: New fact about a person, place, or thing already mentioned
- **Updated preference**: A changed or evolved opinion on something previously captured
- **Continuation**: Follow-up event or next step in a previously captured narrative
- **Contradiction**: New information that conflicts with an existing memory

中文翻译版

# 角色

你是一个记忆提取器——一个精确的、基于证据的处理器,负责从对话中提取丰富、上下文相关的记忆。你的唯一操作是 ADD:识别每一个值得记忆的信息,并生成自包含的、上下文丰富的事实陈述。

你从 user 和 assistant 消息中提取信息。user 消息揭示个人事实、偏好、计划、经历。assistant 消息包含推荐、计划、建议和用户可能参考的可操作信息。

准确性和完整性至关重要。必须捕获每一个值得记忆的信息——一次遗漏意味着失去上下文,会降低未来的个性化质量。

# 输入

## 新消息
当前对话轮次,包含 "role"(user/assistant)和 "content"。

两种角色都包含可提取的信息:
- **user 消息**:个人事实、偏好、计划、经历、做过/从未做过的事、意见、请求
- **assistant 消息**:给出的具体推荐、创建的计划或日程、研究过的信息、提供解决方案、达成的协议

## 现有记忆
系统中与当前对话相关的现有记忆。格式:[{"id": "uuid-string", "text": "..."}, ...]

仅将这些用于去重和关联——不要从现有记忆中提取新记忆。你的提取必须完全来自新消息。

## 观察日期
对话实际发生的日期(如 "2023-05-24")。这是你解析时间引用的唯一锚点。

将所有相对引用转换为绝对日期:
- "昨天" → 观察日期的前一天
- "上周" → 观察日期前的一周
- "下个月" → 观察日期后的一个月

关键:"User went to Paris last week" 在 6 个月后毫无用处。"User went to Paris the week of May 15, 2023" 永远有意义。

# 指南

## 提取什么

**从 user 消息:**
- 个人细节、偏好、计划、关系、专业背景
- 健康/健身、意见、爱好、情绪状态
- 实体属性(品种、型号、颜色、品牌)
- 通过请求透露的隐含偏好
- **共享内容和参考资料**——当用户分享文档、案例研究、文章、数据、规格、代码时,提取其中的关键事实数据
- 第一次和里程碑

**从 assistant 消息(仅当真正是新信息时):**
- 给出的具体推荐(书籍、餐厅、产品、服务)
- 为用户创建的计划或日程
- 研究或提供的信息(事实、指令、解决方案)
- 达成的协议

**不要提取**:问候、填充词、模糊确认、太笼统而无用的内容。

**有疑问时,提取。** 略显冗余的记忆远比遗漏的记忆代价小。

## 记忆质量标准

### 上下文丰富,非原子化
坏的:"User has a dog"
好的:"User has a dog named Poppy and their morning walks together are the highlight of their day"

### 自包含
每条记忆必须独立可理解。将所有代词替换为具体名字或 "User"。

### 简洁但完整(15-80 词,细节丰富时最多 100 词)
每条记忆 1-2 句。不要牺牲专有名词、标题、日期来满足词数——完整性优于简洁。

### 时间锚定
保留确切日期、持续时间。将相对时间 → 绝对时间(使用观察日期)。

### 数值精确
"416 pages" 保持 "416 pages",不是 "about 400 pages"。

### 保留具体细节——永远不要泛化具体信息
书名、电影名、餐厅名、品牌名是最高价值的细节。
- "watched 'Eternal Sunshine of the Spotless Mind'" → 保留完整标题
- "tried the new restaurant Osteria Francescana" → 保留 "Osteria Francescana",不是 "a new restaurant"

## 记忆关联

当提取新记忆时,检查是否与任何现有记忆相关。将相关现有记忆的 ID 添加到 "linked_memory_ids"。关联条件:
- **相同实体/主题**:关于已提及的人、地点或事物的新事实
- **更新的偏好**:先前捕获的偏好发生变化或演进
- **延续**:先前捕获的叙述的后续事件或下一步
- **矛盾**:与现有记忆冲突的新信息

LLM 调用方式

Mem0 调用 LLM 时,通过 API 参数强制 JSON 输出:

response = self.llm.generate_response(
    messages=[
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ],
    response_format={"type": "json_object"},  # 强制 JSON 输出
)

response_format={"type": "json_object"} 是 OpenAI API 的特性,确保 LLM 输出合法的 JSON。

如果模型不支持 response_format

Mem0 有多层容错机制:

# 1. 首先尝试直接解析 JSON
try:
    extracted_memories = json.loads(response).get("memory", [])
except json.JSONDecodeError:
    # 2. 如果失败,尝试从文本中提取 JSON
    extracted_json = extract_json(response)  # 查找 { } 之间的内容
    extracted_memories = json.loads(extracted_json).get("memory", [])
except Exception:
    # 3. 如果还是失败,返回空列表
    extracted_memories = []

extract_json() 函数会:

  1. 尝试匹配 ```json ... ``` 代码块
  2. 如果没有,查找第一个 { 和最后一个 } 之间的内容
  3. 如果还是失败,返回原文本

LLM 返回格式

LLM 返回 JSON 格式的提取结果。输出格式通过 Prompt 中的 EXAMPLES 隐式定义,而非显式字段说明:

示例 1:多主题提取

{"memory": [
  {"id": "0", "text": "User's name is Marcus and was promoted to Senior Engineer at Shopify around August 12, 2025"},
  {"id": "1", "text": "Marcus has a wife named Elena and they celebrate special occasions at Osteria Francescana"},
  {"id": "2", "text": "Marcus and his wife Elena are expecting their first baby in March 2026"}
]}

示例 2:带关联的提取

{"memory": [
  {"id": "0", "text": "User's dog Poppy loves morning walks", "linked_memory_ids": ["uuid-existing-memory"]}
]}

示例 3:无内容可提取

{"memory": []}

输出字段说明

字段类型必填说明
memoryArray提取的记忆数组,可为空
idStringLLM 生成的序号(“0”, “1”, “2”…),用于内部处理
textString记忆文本内容
linked_memory_idsArray关联的已有记忆 UUID 列表

提取规则要点

LLM 必须遵循的规则:

  1. 不要提取助手 Echo:如果 assistant 只是重复 user 已说过的内容,不重复提取
  2. 提取附带事实:用户问问题时透露的背景信息同样重要
  3. 时间锚定:将相对时间(“昨天”、“上周”)转换为绝对日期
  4. 保留具体细节:书名、电影名、产品型号、数量等必须完整保留
  5. 多维度提取:一次对话可能包含多个话题,每个话题都要单独提取
  6. 语义去重:如果新信息与现有记忆语义等价,跳过提取
  7. 记忆关联:相关记忆要建立链接关系

Phase 3: 批量向量化

批量将提取的记忆文本向量化,减少 API 调用次数。

# mem0/memory/main.py - Memory._add_to_vector_store()

def _add_to_vector_store(self, messages, metadata, filters, infer, prompt=None):
    # ... Phase 2: LLM 提取完成后 ...
    
    # Phase 3: Batch embed all extracted memory texts
    mem_texts = [m.get("text", "") for m in extracted_memories if m.get("text")]
    
    try:
        # 批量向量化:一次 API 调用处理所有文本
        mem_embeddings_list = self.embedding_model.embed_batch(mem_texts, "add")
        # 构建文本 -> 向量的映射
        embed_map = dict(zip(mem_texts, mem_embeddings_list))
    except Exception:
        # 容错:如果批量失败,逐个向量化
        embed_map = {}
        for text in mem_texts:
            try:
                embed_map[text] = self.embedding_model.embed(text, "add")
            except Exception as e:
                logger.warning(f"Failed to embed memory text: {e}")

批量向量化的优势

方式API 调用次数适用场景
批量向量化1 次正常情况,效率高
逐个向量化N 次批量失败时的容错方案

注意embed_batch()"add" 参数表示这是用于存储(而非搜索)的向量,某些 Embedding Model 会根据此参数选择不同的编码策略。

Phase 4-5: 去重与哈希

Mem0 使用 MD5 哈希进行精确去重:

# mem0/memory/main.py - Memory._add_to_vector_store()

# Phase 4: Per-memory CPU processing + Phase 5: Hash dedup
# 构建已有记忆的哈希集合
existing_hashes = set()
for mem in existing_results:
    h = mem.payload.get("hash") if hasattr(mem, "payload") and mem.payload else None
    if h:
        existing_hashes.add(h)

records = []  # (memory_id, text, embedding, payload)
seen_hashes = set()  # 当前批次去重

for mem in extracted_memories:
    text = mem.get("text")
    if not text or text not in embed_map:
        continue

    # 计算哈希
    mem_hash = hashlib.md5(text.encode()).hexdigest()
    
    # 两层去重:已有记忆 + 当前批次
    if mem_hash in existing_hashes or mem_hash in seen_hashes:
        logger.debug(f"Skipping duplicate memory (hash match): {text[:50]}")
        continue
    seen_hashes.add(mem_hash)

    # 词形还原(用于 BM25 搜索)
    text_lemmatized = lemmatize_for_bm25(text)

    # 构建元数据
    memory_id = str(uuid.uuid4())
    mem_metadata = deepcopy(metadata)
    mem_metadata["data"] = text
    mem_metadata["text_lemmatized"] = text_lemmatized
    mem_metadata["hash"] = mem_hash
    mem_metadata["created_at"] = datetime.now(timezone.utc).isoformat()
    mem_metadata["updated_at"] = mem_metadata["created_at"]

    records.append((memory_id, text, embed_map[text], mem_metadata))

Phase 6: 批量持久化

将向量、ID、元数据打包后批量插入向量数据库,并记录历史。

# mem0/memory/main.py - Memory._add_to_vector_store()

# ... Phase 4-5: 去重完成后,records 包含所有待插入的记忆 ...

# Phase 6: Batch persist
all_vectors = [r[2] for r in records]    # 提取向量
all_ids = [r[0] for r in records]        # 提取 ID
all_payloads = [r[3] for r in records]   # 提取元数据

try:
    # 批量插入向量数据库
    self.vector_store.insert(
        vectors=all_vectors,
        ids=all_ids,
        payloads=all_payloads,
    )
except Exception:
    # 容错:逐个插入
    for mid, vec, pay in zip(all_ids, all_vectors, all_payloads):
        try:
            self.vector_store.insert(vectors=[vec], ids=[mid], payloads=[pay])
        except Exception as e:
            logger.error(f"Failed to insert memory {mid}: {e}")

# 批量记录历史(ADD 事件)
history_records = [
    {
        "memory_id": r[0],
        "old_memory": None,
        "new_memory": r[1],
        "event": "ADD",
        "created_at": r[3].get("created_at"),
        "is_deleted": 0,
    }
    for r in records
]
try:
    self.db.batch_add_history(history_records)
except Exception:
    # 容错:逐个添加历史
    for hr in history_records:
        try:
            self.db.add_history(hr["memory_id"], None, hr["new_memory"], "ADD", created_at=hr.get("created_at"))
        except Exception as e:
            logger.error(f"Failed to add history for {hr['memory_id']}: {e}")

批量持久化的优势

方式向量库操作次数适用场景
批量插入1 次正常情况,效率高
逐个插入N 次批量失败时的容错方案

Phase 7: 实体链接

Mem0 v2 引入了实体存储机制,将记忆中的实体单独管理:

  1. 从记忆文本中批量提取实体
  2. 全局去重(不同记忆中的同一实体只保留一个)
  3. 搜索已有实体(相似度 ≥ 0.95 则复用)
  4. 新实体批量插入,已存在实体更新 linked_memory_ids

实体存储的好处:

  • 支持实体级别的关系查询
  • 加速实体相关搜索

Phase 8: 保存消息

将原始消息存入 SQLite(用于未来上下文理解),返回新增的记忆列表。

最终返回

{
  "results": [
    {"id": "mem-uuid-1", "memory": "用户叫小明,是一名程序员", "event": "ADD"},
    {"id": "mem-uuid-2", "memory": "用户最近在学习 Rust", "event": "ADD"}
  ]
}

高级过滤操作符

Mem0 支持丰富的元数据过滤:

操作符示例说明
eq{"age": {"eq": 25}}等于
ne{"age": {"ne": 25}}不等于
in{"role": {"in": ["admin"]}}在列表中
contains{"name": {"contains": "john"}}包含
AND/OR/NOT{"AND": [f1, f2]}逻辑组合

支持的 Provider

Mem0 是一个高度模块化的系统,支持丰富的后端选项:

类别数量代表提供商
LLM24种OpenAI, Anthropic, AWS Bedrock, Azure OpenAI, Gemini, Groq, Ollama, DeepSeek…
向量数据库30种Qdrant, Pinecone, Chroma, Weaviate, Milvus, PostgreSQL, Redis, Elasticsearch…
嵌入模型15种OpenAI, Azure OpenAI, Gemini, HuggingFace, FastEmbed, Together…
图数据库4种Neo4j, Memgraph, Kuzu, Apache AGE

对接策略

Mem0 采用混合对接策略

1. 直接对接(主要方式)

对于主流 Provider,Mem0 直接使用官方 SDK 对接,性能更好、依赖更少:

Provider使用的库说明
OpenAIopenaiOpenAI 官方 Python SDK
GroqgroqGroq 官方 SDK
OllamaollamaOllama Python 客户端
Qdrantqdrant-clientQdrant 官方客户端
PineconepineconePinecone 官方 SDK
ChromachromadbChroma 官方客户端
Weaviateweaviate-clientWeaviate 官方客户端
MilvuspymilvusMilvus Python SDK
Redisredis + redisvlRedis 官方客户端 + RedisVL 向量扩展
ElasticsearchelasticsearchElasticsearch 官方客户端

2. 适配器模式

对于需要灵活性的用户,Mem0 提供了 LangChain 和 LiteLLM 适配器:

LangChain 适配器

from langchain.chat_models import init_chat_model
from mem0 import Memory

# 使用 LangChain 支持的任何 LLM
langchain_model = init_chat_model("gpt-4o", model_provider="openai")

memory = Memory(config={
    "llm": {
        "provider": "langchain",
        "config": {
            "model": langchain_model  # 传入 LangChain BaseChatModel 实例
        }
    }
})

LangChain 适配器支持:

  • LangchainLLM — 接受任何 LangChain BaseChatModel
  • LangchainEmbedding — 接受任何 LangChain Embeddings
  • LangchainVectorStore — 接受任何 LangChain VectorStore

LiteLLM 适配器

from mem0 import Memory

# LiteLLM 统一调用多种 LLM
memory = Memory(config={
    "llm": {
        "provider": "litellm",
        "config": {
            "model": "anthropic/claude-3-opus-20240229"  # LiteLLM 格式
        }
    }
})

LiteLLM 支持 100+ LLM Provider,包括:

  • OpenAI, Azure OpenAI, Anthropic, Cohere, Replicate
  • AWS Bedrock, Google Vertex AI, Azure AI
  • Ollama, vLLM, LM Studio 等本地部署

3. 核心依赖 vs 可选依赖

Mem0 的依赖设计非常精简:

核心依赖(必须安装):

qdrant-client    # 默认向量数据库
openai           # 默认 LLM 和 Embedding
pydantic         # 配置验证
sqlalchemy       # SQLite 操作

可选依赖(按需安装):

# 安装向量数据库支持
pip install "mem0ai[vector_stores]"

# 安装 LLM 支持
pip install "mem0ai[llms]"

# 安装额外功能(LangChain、FastEmbed 等)
pip install "mem0ai[extras]"

这种设计让你可以根据需求灵活切换后端,无需修改业务代码,同时保持核心依赖最小化。

OpenClaw 插件详解

openclaw/ 目录实现了一个 OpenClaw 内存插件@mem0/openclaw-mem0),为 OpenClaw Agent 提供长短期记忆能力。

项目概览

这是一个 Node.js / TypeScript 包,发布到 npm 作为 @mem0/openclaw-mem0(当前版本 1.0.11)。它是一个 OpenClaw 插件,通过 OpenClaw 的插件系统注册为 memory 类型的独占插槽(exclusive slot)。

技术栈

维度说明
语言TypeScript(ESM)
构建工具tsup(输出 ESM 格式 + 类型声明 + sourcemap)
测试框架vitest(带 Codecov coverage)
包管理器pnpm
核心依赖mem0ai 3.0.2(npm)、@sinclair/typebox 0.34.47
开发依赖typescript、tsup、vitest、@vitest/coverage-v8
兼容要求OpenClaw 插件 API >=2026.4.24、Gateway >=2026.4.24

构建产物:入口文件 index.ts 编译为 dist/index.js,与 openclaw.plugin.jsonskills/ 目录一起发布。

目录结构与模块划分

openclaw/
├── index.ts                  # 插件主入口:definePluginEntry、生命周期钩子注册
├── openclaw.plugin.json      # 插件清单:ID、工具契约、配置 Schema、UI 提示
├── config.ts                 # 配置解析:mem0ConfigSchema、默认指令/分类
├── providers.ts              # Provider 创建:Platform / OSS 后端工厂
├── isolation.ts              # 多代理隔离:userId 路由、子代理检测
├── filtering.ts              # 消息过滤:噪声去除、消息选择
├── recall.ts                 # Skills 模式召回:查询重写、记忆注入
├── dream-gate.ts             # Dream 门控:会话计数、锁机制
├── backend/                  # 后端抽象层
│   ├── base.ts               # Backend 接口定义 + 错误类
│   └── platform.ts           # Mem0 云平台后端实现
├── tools/                    # 8 个 Agent 工具(模块化)
│   ├── memory-search.ts      # 语义搜索
│   ├── memory-add.ts         # 存储事实
│   ├── memory-update.ts      # 更新记忆
│   └── memory-delete.ts      # 删除记忆
├── cli/                      # CLI 命令实现
│   ├── commands.ts           # 命令注册:init/login/search/add/list/dream/status
│   └── oss-wizard.ts         # OSS 交互式配置向导
├── skills/                   # Skills 模式协议文件
│   ├── memory-triage/SKILL.md        # 记忆提取:四重决策门控、提取类别
│   └── memory-dream/SKILL.md         # 记忆整理:四阶段流程
└── tests/                    # 测试文件(vitest,12 个测试文件)

插件架构

插件通过 definePluginEntry() 定义,实现 OpenClaw 的 OpenClawPluginApi 接口:

const memoryPlugin = definePluginEntry({
  id: "openclaw-mem0",
  name: "Memory (Mem0)",
  description: "Mem0 memory backend — Mem0 platform or self-hosted open-source",
  
  register(api: OpenClawPluginApi) {
    // 1. 初始化遥测
    // 2. 读取配置(openclaw.json)
    // 3. 创建 Provider(Platform 或 OSS)
    // 4. 创建 Backend 实例
    // 5. 注册 8 个工具
    // 6. 注册 CLI 命令
    // 7. 注册生命周期钩子(Skills 或 Legacy 模式)
    // 8. 注册 Service(start/stop)
  },
});

双模式架构

模式后端依赖向量存储特点
PlatformMem0 云端 API (api.mem0.ai)MEM0_API_KEY云端托管零配置,SaaS 服务
Open-Source本地 mem0ai SDKOpenAI/Anthropic/Ollama API Key本地 SQLite (~/.mem0/vector_store.db) 或 Qdrant/PGVector完全本地,数据自控

Platform 模式

  • 通过 PlatformBackend 类实现,直接调用 api.mem0.ai REST API
  • 支持事件列表和事件状态查询(异步处理)
  • 配置最简:只需 API Key + User ID

Open-Source 模式

  • 通过 providerToBackend() 适配器,将 mem0ai SDK 的 Memory 实例包装为 Backend 接口
  • 默认配置:text-embedding-3-small 嵌入(OpenAI)、gpt-5-mini LLM(OpenAI)、本地 SQLite 向量存储
  • 支持 Ollama 完全本地部署(LLM + Embedding 均本地)
  • 支持自定义 Provider:oss 配置块可指定 embedder、vectorStore、llm 的 provider 和 config

后端接口Backend 抽象类):

interface Backend {
  // 添加新记忆:content 为记忆文本,messages 为对话消息数组,opts 包含 userId/agentId 等选项
  add(content?, messages?, opts?): Promise<Record<string, unknown>>;
  
  // 语义搜索记忆:query 为搜索查询,opts 包含 userId/topK/threshold 等选项
  search(query, opts?): Promise<Record<string, unknown>[]>;
  
  // 按 ID 获取单条记忆
  get(memoryId): Promise<Record<string, unknown>>;
  
  // 列出所有记忆:opts 包含 userId/agentId/page/pageSize 等过滤选项
  listMemories(opts?): Promise<Record<string, unknown>[]>;
  
  // 更新记忆:memoryId 为记忆 ID,content 为新内容,metadata 为元数据
  update(memoryId, content?, metadata?): Promise<Record<string, unknown>>;
  
  // 删除记忆:memoryId 为记忆 ID,opts 包含 all/userId 等选项(all=true 删除全部)
  delete(memoryId?, opts?): Promise<Record<string, unknown>>;
  
  // 删除实体:opts 包含 userId/agentId 等选项,用于清理实体关联
  deleteEntities(opts): Promise<Record<string, unknown>>;
  
  // 获取状态:opts 包含 userId/agentId,返回记忆数量和后端状态信息
  status(opts?): Promise<Record<string, unknown>>;
  
  // 列出实体:entityType 为实体类型(如 PROPER/NOUN),返回匹配实体列表
  entities(entityType): Promise<Record<string, unknown>[]>;
  
  // 列出事件:仅 Platform 模式,返回后台处理事件列表(异步处理状态)
  listEvents(): Promise<Record<string, unknown>[]>;
  
  // 获取事件状态:仅 Platform 模式,eventId 为事件 ID,返回事件处理结果
  getEvent(eventId): Promise<Record<string, unknown>>;
}

8 个核心工具

插件通过 OpenClaw skill system 注册 8 个工具,供 Agent 调用:

工具功能后端
memory_search语义搜索记忆,支持 scope(session/long-term/all)、categoriesfilters两者
memory_add存储事实,接受 textfacts 数组、categoryimportancelongTerm两者
memory_get按 ID 获取单条记忆两者
memory_list列出所有记忆,可按 userIdagentIdscope 过滤两者
memory_update原子更新记忆文本,保留编辑历史两者
memory_delete按 ID、查询或全部删除两者
memory_event_list查看后台处理事件仅 Platform
memory_event_status获取特定事件状态仅 Platform

每个工具独立实现于 tools/ 目录下,通过 registerAllTools() 批量注册。工具实现共享 ToolDeps 依赖对象(provider、backend、config、userId 解析等)。

Skills 模式(推荐)

通过 OpenClaw 的 Skill 系统,Agent 获得结构化的记忆协议。每个 Skill 是一个 SKILL.md 文件,定义了 Agent 应该遵循的协议、工具和规则。Skills 模式在 openclaw mem0 init 时默认启用。

架构原理

Skills 模式使用 before_prompt_build 钩子(而非 before_agent_start):

  • prependSystemContext:静态记忆协议(provider 可缓存,无每轮成本)
  • prependContext:动态回忆的记忆(每轮变化)

memory-triage

记忆提取协议。Agent 在对话后评估是否有值得持久化的事实。核心问题:

“一个新 Agent(没有任何先验上下文)是否会受益于知道这个?”

四重决策门控

  1. FUTURE UTILITY:这个事实几天或几周后还重要吗?(过滤工具输出、一次性命令、闲聊)
  2. NOVELTY:检查已回忆的记忆——是新的还是已知的?(已知不变→跳过,已知但有实质变化→更新,全新→继续)
  3. FACTUAL:是具体、可操作的事实吗?(过滤模糊印象、问题、通用助手回复)
  4. SAFE:是否包含凭证、密钥、令牌?(任何匹配→绝不存储值,只记录”已配置”)

提取类别(按优先级):

类别重要性保留期示例
Configuration & System State0.95永久工具/服务配置、模型分配、cron 计划
Standing Rules & Policies0.90永久用户行为指令、安全约束
Identity & Demographics0.95永久姓名、位置、职业、语言偏好
Preferences & Opinions0.85永久通信风格、工具偏好(保留原始措辞)
Goals, Projects & Milestones0.7590 天活跃项目、里程碑、截止日期
Technical Context0.80永久技术栈、开发环境、Agent 生态
Relationships & People0.75永久人名、角色、团队结构
Decisions & Lessons0.80永久重要决策及理由、经验教训

记忆存储原则

  • 实体分组:同一实体的所有信息合并为一条记忆(如 TechForward 事件的预算、WiFi、混合能力合并为一条)
  • 自包含:每条记忆必须独立可理解,不使用代词
  • 15-50 词:每条事实 1-2 句,蒸馏而非追加
  • 时间锚定:时效性事实必须包含 “As of YYYY-MM-DD”
  • 保留原话:用户表达感受/意见时保留原始措辞
  • 第三人称:“User prefers…” 而非 “I prefer…”
  • 按类别批量:同类别事实一次调用,不同类别分别调用

memory-recall(召回协议):

  • before_prompt_build 钩子中执行
  • 查询净化:sanitizeQuery() 将用户消息重写为搜索查询
  • 召回策略:
    • "always" — 长期 + 会话记忆搜索(每轮 2 次 API 调用)
    • "smart" — 仅长期记忆搜索(默认,每轮 1 次 API 调用)
    • "manual" — 无自动召回,Agent 通过 memory_search 工具控制
  • 子代理支持:子代理读取父级长期记忆,但不写入(避免孤立命名空间)

memory-dream

记忆整理协议。审查所有存储的记忆,合并重复项,清理噪声和凭证,重写不清晰条目,执行 TTL 过期。

四阶段流程

  1. Orientmemory_list 加载所有记忆,识别问题(重复、过短、缺少时间锚)
  2. Gather Targets:搜索最近添加的记忆,分类为 DELETE / MERGE / REWRITE
  3. Consolidate
    • 3a. 删除危险/过期条目(凭证、纯时间戳、原始工具输出、心跳记录、7天+操作记忆、90天+项目记忆)
    • 3b. 合并重复(选最完整版本为基准,memory_update 合并细节,memory_delete 冗余条目)
    • 3c. 重写不清晰条目(第一人称→第三人称、补充时间锚、压缩过长条目)
  4. Report:输出操作摘要(审查数、删除数、合并组数、重写数、最终计数)

自动 Dream 门控

Dream 不会每轮触发,而是通过门控机制:

  • 时间门控:距离上次 Dream 至少 N 小时(默认配置)
  • 会话门控:至少 N 次交互式会话
  • 记忆门控:记忆数量达到阈值
  • 锁机制acquireDreamLock() / releaseDreamLock() 防止并发 Dream
  • 写操作验证:只有 Agent 实际执行了 memory_addmemory_updatememory_delete 才标记为完成

Quality Targets

  • 零凭证/密钥记忆
  • 零重复记忆
  • 所有项目/操作记忆有时间锚
  • 所有记忆使用第三人称
  • 所有记忆正确分类
  • 每条记忆 15-50 词,自包含,原子化

Legacy 模式

不使用 Skills 时的自动管线。当 skills 配置未设置时自动启用。

1. Auto-Recall(自动回忆):

在 Agent 响应前通过 before_prompt_build 钩子搜索并注入相关记忆:

  • 触发条件cfg.autoRecall !== false(默认 true)
  • 超时保护:8 秒超时,超时后跳过注入
  • 会话检测:检测新会话(不同 sessionKey),首次短查询触发宽泛搜索
  • 动态阈值:过滤得分低于最高分 50% 的记忆
  • 子代理支持:子代理读取父级命名空间的长期记忆
  • 隐私保护:自动过滤 OpenClaw 元数据(Sender (untrusted metadata) 前缀)

召回结果注入格式:

<relevant-memories>
The following are stored memories for user "alice". Use them to personalize your response:
- User prefers TypeScript over JavaScript [technical]
- User works at TechForward as senior engineer [identity]
</relevant-memories>

2. Auto-Capture(自动捕获):

在 Agent 响应后通过 agent_end 钩子过滤噪声并存储新事实:

  • 触发条件cfg.autoCapture !== false(默认 true)
  • 跳过场景
    • 非交互式触发器(cron、心跳、自动化)
    • 子代理会话(主代理捕获整合结果)
    • Agent 已使用记忆工具(避免重复)
    • 用户内容过短(< 50 字符)
  • 消息选择:最近 20 条消息 + 任意位置的摘要消息
  • 噪声过滤:通过 filterMessagesForExtraction() 管线
  • 时间戳注入:系统消息注入当前日期,用于时间锚定
  • 异步执行:fire-and-forget,不阻塞 Agent 响应

关键特性

特性实现方式
多代理隔离通过 sessionKey 自动路由到不同 userId 命名空间(agent:<name>:<uuid>userId:agent:<name>
子代理支持子代理读取父级长期记忆,但不写入(避免孤立命名空间)
隐私保护自动过滤 OpenClaw 元数据、时间戳锚定、凭证过滤规则
CLI 命令openclaw mem0 init / login / search / add / list / dream / status / config / event
遥测PostHog 匿名遥测,记录 recall/capture/dream 事件
配置持久化~/.openclaw/openclaw.json,支持 SecretRef 敏感配置
记忆作用域Session(短期,run_id 隔离)+ User(长期,跨会话)
Skills 加载skill-loader.ts 读取 skills/ 目录下的 SKILL.md 文件
Dream 门控dream-gate.ts 实现会话计数、锁机制、完成记录
安全文件系统fs-safe.ts 提供安全的文件读写操作,避免路径遍历攻击
OSS 配置向导oss-wizard.ts 实现交互式 4 步向导(LLM → Embedder → Vector Store → User ID)
测试覆盖12 个测试文件,覆盖 backend、CLI、config、tools、telemetry 等

配置 Schema

插件通过 openclaw.plugin.json 定义完整的配置 Schema,支持 20+ 配置项:

核心配置

类型默认值说明
mode"platform" / "open-source""platform"后端模式
userIdstringOS 用户名用户标识符
autoRecallbooleantrue自动注入记忆(Skills 模式下忽略)
autoCapturebooleantrue自动存储事实(Skills 模式下忽略)
topKnumber5最大召回数量
searchThresholdnumber0.1最低相似度阈值

Skills 模式配置

类型默认值说明
skills.triage.enabledbooleantrue启用事实提取
skills.recall.enabledbooleantrue启用记忆回忆
skills.recall.strategy"always" / "smart" / "manual""smart"召回策略
skills.recall.tokenBudgetnumber1500最大 token 预算
skills.dream.enabledbooleantrue启用定期整理
skills.domainstring"companion"领域覆盖

Open-Source 模式配置

类型默认值说明
oss.embedder.providerstring"openai"嵌入提供商
oss.vectorStore.providerstring"memory"向量存储提供商
oss.llm.providerstring"openai"LLM 提供商
oss.historyDbPathstringSQLite 路径

安全与隐私

数据流

模式数据去向凭证需求
Platform对话发送到 api.mem0.ai 进行记忆提取MEM0_API_KEY
Open-Source (OpenAI)LLM/embedding 调用 OpenAI API;向量本地存储OPENAI_API_KEY
Open-Source (Ollama)完全本地 — LLM、embedding、向量均在本地

凭证存储

配置存储在 ~/.openclaw/openclaw.json,支持:

  • 环境变量引用:"apiKey": "${MEM0_API_KEY}"
  • SecretRef:"apiKey": {"source": "env", "provider": "default", "id": "MEM0_API_KEY"}

持久化位置

文件用途
~/.openclaw/openclaw.json插件配置(API Key、User ID、设置)
~/.mem0/vector_store.db本地向量存储(Open-Source 模式)
~/.mem0/history.db记忆编辑历史(Open-Source 模式)
<pluginStateDir>/dream-state.jsonDream 状态

© 2026 jiangwei.me. All rights reserved.