跟着 Mem0 代码走一次写入和检索
返回 Mem0 剖面 · 固定源码版本 f8082a73
本文只读 Python OSS 的同步 Memory.add(infer=True) 与默认不启用 rerank 的 Memory.search。下面的伪代码是阅读顺序,不是上游源码或运行轨迹。两次调用可以由应用连接,但 search() 的返回值不会自动进入某个 Agent 的下一轮模型输入。
text
add(messages, user_id="u1", infer=True)
→ 校验作用域与消息 → 读取近期消息和已有记忆
→ LLM 提取事实 → embedding / hash 排重 → 尝试写向量与历史
→ 保存本轮消息 → 返回 {results: [] 或 [{event: "ADD", ...}]}
search(query, filters={"user_id": "u1"})
→ 校验查询与作用域 → 语义搜索建立候选集
→ 可用时计算关键词分数和实体加分 → 语义阈值 / 综合排序
→ 格式化 → 返回 {results: [...]}(也可能为空)写入:消息与可检索事实是两种状态
add()拒绝 OSS 不支持的timestamp,经_build_filters_and_metadata要求user_id、agent_id、run_id至少一个,并将字符串或字典消息正规化为列表。procedural_memory与infer=False不是下列路径。入口与分支 · 作用域校验_add_to_vector_store()读取同一会话作用域的最近消息,以新消息生成查询 embedding,最多取十条已有向量记忆,连同它们一起构造提取提示。这一步的已有记忆是提取时的参考,不是本轮准备覆盖的记录清单。上下文采集- LLM 返回的事实解析为
memory列表。调用失败会抛LLMError;若返回空事实或无法解析出事实,则保存原消息并返回空列表。消息已保存不等于产生了新的向量记忆。提取与空结果 - 对提取出的文本生成 embedding;源码用文本 MD5 与刚检索到的记录及本批次比对,跳过重复或无法取得 embedding 的项。排重只针对这次读到的候选和批次,不能推断整个向量库已全局去重;若没有记录留下,同样保存消息并返回空列表。embedding 与排重
- 有记录时先批量
vector_store.insert,失败后逐条尝试;随后写 ADD 历史、尝试关联实体,最后保存本轮消息。这里的 ADD-only 指此分支不自动更新或删除既有记忆记录;实体关联本身可更新其实体存储,显式update/deleteAPI 也另有路径。向量写入与回退 · 历史、实体与消息 - 返回值
{"results": ...}的每个ADD项按构造的records生成,而逐条回退中的插入异常仅被记录、没有从records移除。因此在故障条件下,返回 ADD 不足以独立证明每个 ID 确已持久化;这项风险是源码推断,尚需故障注入与向量库读回验证。回退异常 · 返回项构造
检索:谁入候选池,谁只能加分
search()拒绝顶层传入的实体 ID,要求在filters中给出至少一个user_id、agent_id或run_id,并检查 query、阈值和top_k;不通过就不会进入向量检索。参数校验_search_vector_store()对 query 做语义 embedding,并以超过请求top_k的内部数量取得语义结果;它也尝试关键词搜索和实体关联。这些是不同信号,但candidates只从语义结果构造,并在默认情况下跳过已过期记录。三种信号与候选池score_and_rank()先用语义分数过滤候选,再叠加可用的 BM25 分数与实体加分、按活跃信号归一化并取top_k。仅被关键词命中、却未进入语义候选池的 ID 不会单独成为结果。排序算法- 搜索结果被格式化为记忆对象;仅当调用方启用
rerank、配置了 reranker 且已有结果时,才再排序,失败则保留原结果。最终返回{"results": ...},是否将其注入模型提示由调用方决定,本篇源码并不展示该应用层步骤。格式化与返回 · 可选 rerank
这份导读没有运行模型、数据库或向量库。LLM 提取质量、适配器是否实现 keyword_search()、实体链接准确性、写入失败时返回值与实际存储的一致性,以及检索结果进入 Agent 上下文后的效果,仍需在固定配置下分别采集运行轨迹和读回结果。
在线预览稿:书稿仍在校稿,系统篇以文内固定源码版本为准;静态阅读不等于运行验收。