news 2026/10/21 8:38:49

Mem0源码解析:构建大模型外部记忆的add与search链路

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Mem0源码解析:构建大模型外部记忆的add与search链路

1. 这个项目到底在解决什么问题

我最近把 Mem0 的源码从头到尾翻了一遍。说实话,刚开始看到这个名字的时候,我以为它只是又一个记忆插件,但真正读完 search 和 add 的核心链路之后,我发现它本质上做的是一件很实际的事:让大模型在对话之外拥有一套可持续读写的外部记忆。

先解释一下背景。过去我们做 AI 应用,所谓的“记忆”基本靠拼 prompt 上下文。聊得长了,塞不进去;聊得久了,前面的关键事实被后续内容冲淡。更麻烦的是,这种记忆是一次性的,下次会话又得从零开始。Mem0 的思路是把这个过程显式化:用专门的数据结构存记忆,用专门的抽取逻辑从对话里提炼事实,用专门的检索逻辑在需要的时候把相关记忆找回来。

这套东西适合谁读?如果你在做智能助手、客服机器人、知识库问答,或者任何需要跨会话保持用户特征的 Agent 应用,那 Mem0 的源码值得认真看一遍。它不复杂,但把“记忆”这件事拆成了几个关键环节:写入、抽取、存储、检索。这四个环节的取舍和实现方式,基本就是所有记忆系统的通用骨架。

读完源码之后我最深的感受是:记忆系统最难的其实不是存,而是“什么时候记住什么”和“什么时候想起什么”。这两个问题,Mem0 分别用 add 和 search 两条链路来回答。接下来的内容,我会按源码的实际执行顺序来拆。

2. 源码总览与模块边界

2.1 从入口到核心类的依赖关系

Mem0 的源码入口并不花哨。最上层暴露出来的接口就是add、search和get_all这类方法。以 Python 版本为例,核心类维护了三个关键组件:一个负责和大模型对话的接口层,一个负责向量化和向量检索的存储层,还有一个负责记录关系和实体之间的图存储层。

这种分工一开始可能让人困惑,为什么要同时用向量库和图库?我的理解是:向量库擅长做“语义相似”召回,适合回答“哪条记忆和当前问题相关”;图库擅长做“关系扩散”,适合回答“这条记忆涉及的人和事还有什么关联”。Mem0 把两者串起来,先用向量库粗筛,再顺着图关系往外延展,最终汇总成上下文。

这正好解释了为什么它在很多评测里比纯向量检索效果好。纯粹靠 embedding 召回,你只能拿到语义上像的片段;但如果这条记忆链接到了用户最近提到的某个项目,顺着图边就能把那个项目的背景也拉进来。这就是“记一件事”和“把一件事周围的背景也记下来”的区别。

2.2 配置层与可插拔设计

源码里有一个很明显的设计取向:存储层和 LLM 层全部做成了可替换的接口。比如向量存储可以换成不同的实现,图存储也可以换,LLM 调用也可以接不同的后端。这样做不是单纯为了炫技,而是因为记忆系统的性能瓶颈通常不在算法,而在底层存储和大模型调用的稳定性。

我在看的时候特别注意了配置加载部分。它把数据库地址、模型名称、向量维度、超时时间这类参数都收敛到一个配置对象里。开发者不需要改业务代码,只改配置就能切换到不同的存储组合。这一点对于生产环境很重要:你在本地测试时可能用轻量库,线上想换成高可用集群,如果源码里到处硬编码了连接方式,那改造代价会大得惊人。

2.3 记忆单元的数据结构

源码里定义了一套记忆的数据结构,核心字段包括:记忆文本、关联的实体、所属会话、时间戳、以及用于向量化的 embedding。我读的时候特意画了一下这个结构的关系,发现它其实像一个“以实体为中心”的知识图谱,每条记忆都挂在若干实体下面。

这里有个值得学习的点:它并没有把记忆当成一串隐藏向量,而是保留了原始文本和结构化信息。原始文本用来给大模型阅读,embedding 用来给向量检索比对,实体和关系用来给图库扩散。三份数据各司其职,既有冗余,又互相补充。很多团队自己实现记忆系统时只存 embedding,结果召回的结果虽然相似,但缺少解释性,也很难做去重和更新。

3. add 链路拆解:一条记忆是怎么被写进去的

3.1 输入预处理与历史消息拼接

add 方法的输入并不是已经整理好的记忆句子,而是原始对话历史。这一点和很多人的直觉不一样。你以为要先由开发者写好“记忆要点”再传给 Mem0,实际上它接收的是消息列表,具体要记什么由它自己抽。

源码里第一步是把 messages 做标准化。每条消息要标明角色是用户还是助手,文本要清洗掉多余的空白和控制字符。然后它会把多轮对话拼接成一个带角色标记的长文本,作为后续抽取的输入。这个拼接过程看起来简单,但角色标记很重要,因为抽取逻辑需要知道哪些话是用户说的,哪些是助手生成的。用户说的往往是需要长期记住的偏好或事实,而助手的话更多是上下文补充。

我在实验的时候发现,消息顺序对抽取结果影响很大。如果最近几轮消息本来就是在纠正之前的误解,那么只截取最后一段文本会丢掉纠正过程。Mem0 的处理方式是把整段历史压缩后交给抽取,而不是简单截尾,这一点在长对话场景下非常关键。

3.2 向量化与存储索引的构建

在抽取实体之前,add 链路会先把拼接后的对话文本送给 embedding 接口,得到一段向量。这个向量并不是最终记忆的向量,而是用于临时比较和后续写入的辅助信息。源码里对向量化的细节做了封装,但核心逻辑很明确:凡是进入记忆系统的文本,都必须先有一套统一的向量表示。

向量化之后,文本内容会被写入向量存储,同时计算出这段内容的唯一标识。这个标识在后续图关系引用中反复出现。我在读的时候有个感触:很多人以为 add 就是“插一条记录”,但实际上它需要先完成“语义定位”。也就是判断新文本是不是已经有高度相似的内容存在,如果有,就应该走更新而不是新增。这个判断通常靠向量相似度阈值完成,阈值设置得过高,会产生大量重复记忆;阈值设置得过低,又会把真正不同的事实错误合并。

3.3 实体抽取与关系识别

这是 add 链路里最核心的一段。源码会把拼接好的文本交给 LLM,通过特定的 prompt 让它抽出实体、关系和事实。实际返回的结果是一个结构化 JSON,包含实体列表、实体间的关系、以及每条记忆的摘要句子。

这里有一个细节我特别留意:实体抽取不是简单地做命名实体识别,而是按“可被引用”和“可被关联”两个标准来选择。比如“用户喜欢喝美式咖啡”,会抽出一个“用户”实体和一个“美式咖啡”实体,两者之间建立“偏好”关系。至于那些一次性的、无法稳定复用的细节,比如“用户今天下午三点说了某句话”,通常不会被抽成长期实体。

实体关系的构建直接决定后续 search 能扩散多远。如果实体粒度太细,图会变得碎片化;如果太粗,图会丢失关键区分度。Mem0 的做法是在 prompt 里明确要求“仅提取关键实体,避免抽入明显的一次性临时信息”,同时用温度参数调低生成随机性,让输出尽量稳定。

3.4 增量更新与去重策略

等实体和关系抽完之后,add 还不会马上结束。它会先去图库里检查这些实体是否已经存在。如果已经存在,就把新关系合并进去;如果不存在,才新建实体节点。

合并逻辑涉及到去重,这是我在实际使用中最经常遇到问题的环节。源码里有一个相似度合并判断:当新记忆和已有记忆语义高度重合时,它会选择保留“信息更完整”的那一条,并把旧记录的引用转移到新记录上。这个策略的优点是能防止记忆无限膨胀,缺点是如果判断条件太宽松,可能把不同语境下的相似句子错误合并。

我在一次测试中故意输入“用户喜欢简洁的界面”和“用户偏好极简风格”,发现系统能够识别两者相关并做了合并。但换成“用户不喜欢等待”和“用户讨厌这个产品的等待时间”时,它会把两者合到同一个实体下,虽然没有造成严重后果,但你也应该意识到这只是启发式策略,不是绝对的事实判定。

4. search 链路拆解:一次 Query 怎么翻出记忆

4.1 查询向量化和初步召回

search 的入口接收一个 query 字符串。源码会先对 query 做同样的向量化处理,得到查询向量,然后在向量存储里做相似度召回。召回数量通常不是只取 top1,而是取 topK,这个 K 值决定了后续图扩散的上限。

K 值的选择直接影响效果。我测试过把 K 从 5 调到 20,发现 K 太大时,后面的关系扩散会把很多弱关联的内容都拉进来,导致最终拼给大模型的上下文过长,反而降低回答准确率。当前源码默认的 K 相对保守,实际使用时需要根据自己的数据量调。数据量大、记忆条目多,K 可以适当调大;如果记忆只有几百条,K 设太大纯粹是浪费。

初步召回的结果是一个候选记忆列表,每条都带相似度分数。这个阶段的目标是“别漏掉”,哪怕召回了一些不太准确的内容也没关系,因为后面还有过滤和重排。

4.2 基于实体关系的图扩散

拿到候选记忆之后,search 会提取这些记忆关联的实体,然后去图存储里查询这些实体周围的邻居。这一步才是 Mem0 区别于普通向量检索墙的关键。

图扩散的逻辑可以这么理解:有一条记忆说“用户和 A 共同参与某项目”,当你问“用户最近在忙什么”时,向量召回可能只找到直接提到项目的片段。但通过实体“用户”和“某项目”之间的边,系统能继续找到“该项目的上线时间”“用户在该项目里负责的部分”等相关记忆,这些在字面上未必和当前查询高度相似,但在关系上是强相关的。

源码里对图扩散的层数做了限制。默认只扩散一层,也就是直接邻居。我试过改成两层,发现能找回更多背景信息,但连接顺序很容易发散。尤其是当某个实体关联了特别多记忆时,两层扩散可能把无关的内容也带出来。所以默认一层其实是平衡效果和精度的选择。

4.3 融合排序与上下文构造

图扩散结束之后,search 手上有两批候选:纯向量召回的,以及通过图关系扩展出来的。源码会计算两个集合的并集,同时给每条候选记忆附上一个分数。

打分公式大致是:向量相似度权重加上关系亲密度权重。不同版本的源码细节有差异,但方向一致:既不能只看语义相似,也不能只看关系远近。如果查询本身是个很具体的事实问题,向量相似度的权重应该更高;如果查询是个开放性问题,比如“聊聊这个人的做事风格”,那关系扩散的作用会更大。

最后一步是把选中的记忆按时间戳顺序排列,再拼接成一段上下文文本。拼接时它会刻意控制总长度,避免把记忆一股脑全塞进去。这个拼接结果最终作为系统提示的一部分传给大模型,让模型在回答时“知道它应该知道的东西”。

4.4 无结果时的降级路径

search 还有一个容易被忽略的分支:当向量召回的结果相似度都低于某个阈值时,源码不会强行返回低质量记忆,而是返回空结果。这种情况下,上层应用可以选择完全依赖模型自身知识来回答,而不是被一段不相关的记忆干扰。

这个降级路径我觉得特别实用。很多记忆系统的问题是无论查什么,都会回几条不太相关的记录,反而污染了模型的判断。Mem0 用阈值来控制“有多确定才使用记忆”,从产品体验上说,这比“永远有结果”更靠谱。

5. 记忆抽取的 Prompt 设计与输出解析

5.1 Prompt 模板的作用

记忆抽取的效果有很大一部分不是靠模型能力,而是靠 prompt 怎么问。源码里的 prompt 写得非常结构化,它会明确告诉模型:你需要从对话中抽取出实体和关系,并以 JSON 格式返回。同时它还会附上一些例子,用 few-shot 的方式引导模型输出符合预期的格式。

我看这段代码的时候特别注意到一个关键词:它要求模型把记忆分成“当前会话相关的短期记忆”和“值得长期保留的核心记忆”。这个区分直接关系到 add 后是否真的写入持久化存储。对于那些聊天气氛、临时情绪、或者一次性事件,抽取结果会被标记为临时,不会被放进长期图关系里。

在实际部署中,你可以调整这个 prompt 来改变记忆抽取的口径。比如在客服场景,你希望多抽取用户的身份特征和购买偏好;在医疗场景,你希望把症状描述和用药历史放在最前面。源码允许开发者覆盖默认 prompt,这个灵活性很重要,毕竟没有一个通用抽取规则能适应所有领域。

5.2 结构化输出解析与容错

LLM 输出 JSON 并不总是合法的。我读源码时专门找了解析这块,发现它做了好几层容错。首先它要求模型用严格的 JSON 格式,接着在解析时捕获解析异常,如果失败,会把原始输出扔给一个轻量的修复逻辑,尝试从文本块里截取 JSON 片段。

这种容错不是无脑重试。重试大模型调用会引入额外延迟和成本,而截取片段的方式在大多数情况下能解决 90% 的格式问题。我一开始以为它会直接抛异常,后来发现它连“代码块包裹的 JSON”这种常见错误都处理了,比如返回了带 ```json 标记的文本,它也能剥掉外壳再解析。

但这里有个坑:如果 LLM 输出的是合法 JSON,但缺少必要字段,源码不会纠正。它只是在这条记忆上标记为“部分有效”,然后选择性地跳过缺失关系的内容。这意味着你不能完全依赖模型正确输出,最好在业务层也做一次字段校验,哪怕 Mem0 内部已经做了容错。

5.3 抽取与搜索之间的反馈循环

我第一次读源码时以为 add 和 search 是两条完全独立的链路,后来才发现它们共享了同一个实体库和关系图。这就形成了一个隐性的反馈循环:add 写得越精确,search 能扩散的图越可靠;而 search 反馈的查询结果如果被用户标注为“有用”或“无用”,又可以反过来指导 add 阶段抽取哪些实体。

源码里目前没有做复杂的强化学习,但这种共享结构的含义已经很明显了。只要 add 阶段建立的实体关系是干净的、可追溯的,search 阶段的质量就有保障。反之,如果 add 阶段抽了一堆噪声实体,那 search 再努力也会把噪声扩散出去。所以你在使用中要重点监控实体抽取的质量,而不是只看搜索结果好不好看。

6. 我在阅读源码过程中踩过的几个坑

6.1 向量维度不匹配导致写入失败

我第一次跑通 add 的时候,遇到一个很典型的问题:配置里指定的向量维度是 768,但实际 embedding 接口返回的向量只有 384 维。写入向量库时直接报错。排查半天,发现是初始化时传错了模型名称,它使用的模型是另一个文本模型。

这个问题的根因是源码对向量维度做了严格校验,它宁可直接抛错,也不愿意用一个错误维度的向量污染整个索引。这从工程角度说是好事,但从使用者角度来说,报错信息一开始有点抽象。后来我总结出一个习惯:在你更换 embedding 模型之后,第一件事就是打印一条原始向量的 shape,确认维度再继续。

6.2 实体抽取反复出现同一实体大小写变体

在另一组测试里,我发现同一个名字在抽取结果中出现了“张三”“张三(zhang san)”“Zhang San”三种写法。这让图库里出现了三个看似不同、其实指向同一个人的节点,导致 search 扩散时不能很好地聚合。

这个问题在源码里有一定程度的归一化处理,但它默认的规则比较简单,主要靠文本小写化和去除首尾空格。如果想真正解决同义词和别名问题,你需要在外层再做一遍实体统一。我自己在项目里接了一个轻量的指代消解规则,效果立竿见影。别指望默认实现能覆盖所有业务里的别名场景。

6.3 时间戳导致的记忆覆盖顺序异常

add 链路在合并相似记忆时,会参考时间戳来判断哪条更新。我遇到的坑是,导入历史数据时所有消息都用了同一个时间戳,导致合并时它认为每一条都是最新的,结果后导入的内容覆盖了先前的内容,把之前保留的关键信息洗掉了。

后来去源码里确认,它的时间戳默认取系统当前时间,而不是消息里自带的时间。如果你要导入的是存量历史数据,一定要在调用 add 时显式传入时间戳,并且要保证时间递增,否则记忆的新旧判断会完全错乱。

6.4 高并发写入时的图关系锁冲突

我尝试用多线程并发调用 add 时,偶尔会触发图存储端的写入冲突。原因是两条记忆几乎同时尝试创建同一个实体节点,但图存储端对同一主键的创建操作没有做幂等保护。这个在单机开发环境不敏感,但只要你上了并发,就会暴露。

源码在设计时可能默认使用方通过队列或者串行方式写入。我在生产环境里的做法是加了一层内存队列,把并发写改成了批量串行写。这样既保证了实体创建的顺序,也避免图存储被并发请求打挂。

6.5 对 LLM 返回结果的过度信任

最后这个坑不是源码的 bug,而是使用心态问题。Mem0 的抽取结果本质上是大模型生成的,大模型有可能幻觉出用户从未提过的实体。源码不会去验证实体是否真实存在,它只会尽量把这些实体纳入关系中。所以在某些敏感场景下,你必须对外层做事实校验。

我建议在实际项目里加一道“人工反馈”通道:对于高风险的记忆写入,先让用户确认,再真正写入长期存储。不是说 Mem0 不好,而是它把“抽取”这个动作交给模型之后,本身就带着不确定性,设计系统的人要意识到这一点,而不是把一个概率性过程当成确定性事实。

最后一点心得和后续可以玩的方向

如果你要把这套源码的机制拆开重做,建议不要一上来就追求复杂。先实现一个最小版本:向量存储加图存储,再加一个 prompt 抽取外层。把 add 和 search 的链路跑通之后,再逐步调优实体抽取的策略和排序权重。

我在实际使用中发现,最影响体验的不是某一个独立模块,而是实体抽取的稳定性和阈值设置。这两个参数直接决定了记忆系统会不会变成“垃圾进垃圾出”。Mem0 的默认参数挺好,但不要迷信,一定得拿你自己的数据去压测。

后续我计划做的事情是:把抽取出的实体关系做成可视化,让用户能看到自己的长期记忆图谱。很多人对自己的记忆只有抽象感觉,一旦可视化出来,交互会完全不一样。如果你也在做类似的事,欢迎沿着这个方向继续折腾。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/21 8:38:46

开放模型降本实战:从成本拆解到本地部署与批量接入

Y Combinator 最近关于“开放模型正在让 AI 成本骤降”的判断,正在被大模型行业反复验证。这不是一个概念讨论,而是直接影响企业技术选型的现实问题:继续按 token 付费调用闭源 API,还是把开放权重模型部署到自己的服务器上&#…

作者头像 李华
网站建设 2026/10/21 8:38:35

LiteLLM:统一多模型API的协议翻译器与智能路由网关

1. 项目概述:LiteLLM不是“轻量版大模型”,而是一把精准适配所有大模型API的万能扳手LiteLLM这个词最近在开发者圈里突然密集出现,不是因为又出了个新模型,而是因为它彻底改变了我们调用大模型的方式。它不训练模型,不…

作者头像 李华
网站建设 2026/10/21 8:37:47

管理驾驶舱建设实战:指标体系与数据链路才是核心

管理驾驶舱这个词,这几年在老板嘴里出现的频率确实高。很多同行都接过类似需求:领导看完某家公司的数据大屏,回来就丢一句“我们也搞一个”,然后你打开画图软件准备大干一场,却发现连数据从哪来、要放什么指标都还没想…

作者头像 李华
网站建设 2026/10/21 8:37:42

Playwright驱动的电商自动化运营平台:合规实现与工程实践

1. 项目概述与合规边界先说结论:这个标题看着很带感,但真正落地时,第一道坎不是技术,是合规。我在梳理这个项目时,把“自动化运营”一词拆成了三个层次:一是完全人工辅助的脚本化点击,比如浏览器…

作者头像 李华
网站建设 2026/10/21 8:37:38

三电平NPC逆变器SVPWM驱动PMSM的V/F控制Simulink仿真调试记录

做电机控制的人对V/F(压频比)控制应该都不陌生,给定子一个恒定比例的电压幅值和频率,电机就能从静止慢慢转起来,这是最古老也最皮实的开环调速手段之一。但最近我把这套逻辑接到三电平NPC逆变器上,配合SVPW…

作者头像 李华
网站建设 2026/10/21 8:37:22

EvolveBench:大模型时间感知评测基准的实战解析

做模型评测这几年,我越来越确信一个道理:通用分数好看的模型,扔进真实世界里该翻车还是会翻车。尤其是当你把不断更新的知识扔给它时,你会发现一个很反直觉的现象——模型可能知道某件事,却完全不知道这件事现在已经变…

作者头像 李华