给 Agent 接了个"记忆",才发现它分不清"曾经对"和"现在对"
最近团队在评估要不要给我们的合同/票据审查系统加一层跨会话记忆——简单说就是希望系统能记住"这个客户上次审查时发现过什么问题""这份合同的风险点历史上是怎么变化的",而不是每次审查都从零开始。
技术选型阶段我们把市面上几个主流方案过了一遍:Mem0、Zep/Graphiti、Letta。过程中发现几个挺反直觉的点,记录下来,也算是给同样在做企业级 Agent 记忆系统选型的同学一个参考。
先说结论:这事没有"最好的方案",只有"和你的数据时效性匹配的方案"。下面展开讲讲为什么。
先搞清楚一个基本误解:记忆中间件不是向量库的替代品
我们一开始的心智模型是错的。会下意识觉得"上个 Mem0"就跟"上个 pgvector"是同一层东西的两种选择——后来发现完全不是一回事。
向量库(pgvector、Qdrant 这些)只是个"存 embedding、做相似度检索"的仓库,它对存进去的内容毫无理解,分不清一条用户偏好和一句随机的话,没有时间感知,也不知道内容有没有互相矛盾。
Mem0 这类工具做的事情,是架在向量库之上的一层业务逻辑:什么该写进去、什么时候该更新旧记录、检索时怎么把语义相似度和关键词匹配揉在一起排序。它的底层存储完全是可插拔的——你可以让它用 pgvector,可以用 Qdrant,自托管的官方方案默认走的就是 PostgreSQL + pgvector 这套组合。
所以准确的说法是:你现有的向量库不会被这些工具取代,它们直接复用你已有的基础设施,自己只是加了一层"什么值得记、什么时候该忘"的决策逻辑。
这层决策逻辑听起来简单,落地起来才是真正分化出不同技术路线的地方。
第一个分歧点:要不要管"时间"
我们最初想当然地认为,记忆系统的核心能力就是"记住事实"。调研完才意识到,真正难的不是"记住",而是**"知道一个事实从什么时候开始不再为真"**。
举个我们业务里真实会遇到的场景:一份合同的某条款在 V1 版本里写的是一种责任划分,V2 修订之后变了。如果只是简单地把"条款内容"当一条记忆存进去,新版本进来的时候,旧版本的描述要不要删?要不要保留?万一某天需要追溯"V1 版本当时是怎么约定的",这条历史信息还在不在?
Mem0 的默认处理方式是:调用写入接口时,会有一个 LLM 判断步骤,决定新信息和已有记忆是新增、更新还是重复丢弃。这套机制依赖语义相似度匹配——如果新旧两条表述在语义上离得不够近,系统识别不出"这是同一件事的更新",于是新旧两条记录就会一起留在库里,谁被检索召回,看的是相似度排名,不是时间先后。
这个机制在很多场景下没问题,比如"用户喜欢 Python"变成"用户现在更喜欢用 Go 了",这种偏好类信息语义重叠度通常够高,能被正确识别为更新。但对我们这种条款级别的细节比对,语义相似度未必可靠——条款修订前后用词可能差异很大,但指向的是同一件事。
Zep 背后的 Graphiti 引擎走的是另一条路:把时间做成图谱里的一等属性。每条事实在存的时候会带两个独立的时间戳——一个是"这件事在现实世界里何时为真",一个是"系统是什么时候得知这件事的"。当新信息和旧信息冲突时,旧的那条不会被删掉,而是被标记成"失效",新的一条单独存在。查询的时候默认只返回有效的那条,但只要你想回溯,历史轨迹完整保留。
这套机制理论上更贴近我们需要的"条款历史追溯"场景。但代价也很直接:每次写入都要走一次更"重"的结构化抽取(实体、关系、时间戳全套),LLM 调用的 token 消耗明显比 Mem0 默认的事实抽取要高。官方文档给出的数字大概是单次抽取 500-2000 输入 token、200-800 输出 token,量级上不算小。
第二个分歧点:谁来决定"该不该自己整理记忆"
调研中还碰到一个完全不同维度的选项——Letta(前身是 MemGPT)。
Mem0 和 Zep 本质上都是"被动工具":你的代码主动调用写入接口、主动调用检索接口,记忆系统不知道、也不关心 agent 此刻在干什么。
Letta 反过来,是把"记忆管理"这件事直接做成了 agent 自己能调用的工具。整套设计借鉴的是操作系统虚拟内存的思路——当前对话窗口相当于内存,归档存储相当于磁盘,agent 在自己的推理循环里可以主动判断"现在这部分上下文不重要了,挪到归档里腾地方",或者反过来把归档里某条记忆翻出来塞回当前上下文。
这套机制对我们的场景来说,价值没那么明显。审查任务的边界是清晰的——上传文档、跑抽取、出风险报告,整个流程是可控、可预测的,不太需要 agent 自己去琢磨"现在该记什么、该忘什么"。这种自我管理能力更适合长程自主运行、任务边界本身不固定的场景,比如持续对话的客服 agent。所以这条路线我们调研完之后基本排除了,但理解它的存在意义还是有必要的——至少能帮你判断自己到底需不需要这种复杂度。
一个容易被忽略的成本结构差异
选型时我们还纠结过一个具体的工程问题:多久提炼一次记忆。
直觉上"每轮对话都提炼"听起来就很烧钱,查证下来发现 Mem0 官方默认行为确实就是逐轮触发——每来一对用户消息和助手回复,就走一次抽取流程。一次完整的写入要经过两次 LLM 调用(一次抽取候选事实,一次跟已有记忆比对决定增删改),外加一次向量检索。如果是个 100 轮的长对话,光记忆管理这一项就要消耗 200 多次模型调用,这个数字确实容易让人意外。
好消息是这一层完全可以自己控制——SDK 不强制你必须逐轮调用,你可以选择在任务/会话结束时统一调用一次,把整段交互一次性喂给抽取流程。代价是单次抽取要处理的上下文变长,但跟逐轮触发 N 次比起来,总成本通常还是要低一个量级。
Zep/Graphiti 这边的提炼单元叫 episode,本身粒度也不是强制绑定的,可以攒批之后再统一摄入,官方说法是攒批能在抽取成本上省下三到五成。但有个现实的小麻烦:目前没有官方支持的"只用图谱做时序逻辑、抽取环节自己接管"的标准接口,想做精细化的成本控制,得自己想办法绕。
这里延伸出另一个容易搞混的概念:上下文压缩和记忆系统不是一回事。压缩解决的是"当前这一次任务上下文太长塞不进窗口"的问题,是会话内的临时手段,任务结束后压缩结果基本也就作废了;记忆系统解决的是"这次交互里有没有什么值得被未来某次完全不同的会话查到"的问题。两者经常会搭配使用——任务执行过程中做上下文压缩控制单次窗口不爆,任务结束后再单独触发一次提炼写入长期记忆,是两套互相独立、各管各事的机制,不能指望一套方案两头兼顾。
摆在桌面上的决策框架
把调研过程整理成几个可以直接拿来用的判断维度:
先问数据时效性。 你的记忆里存的信息,本质上是稳定不变的(用户偏好、长期画像),还是会随时间被覆盖、且覆盖前后的状态都有保留价值(合同条款版本、风险等级变化、客户关系演变)?前者向量检索类方案就够,后者时序图谱的投入才有意义——否则旧信息很可能在检索时悄悄混进来,污染当前判断,而你完全意识不到。
再问读写比例。 写入频率远高于检索频率的场景,任何带 LLM 抽取的记忆系统都是在烧钱换不来的收益;反过来检索远多于写入,抽取这笔投入才划得来。
再问任务边界是否固定。 边界清晰、可预测的任务型流程(我们的审查 pipeline 属于这一类),外部编排的被动式工具足够,没必要上 agent 自我管理记忆这种复杂度。
最后别忘了把"提炼频率"当成一个独立的工程决策,不要直接套用任何工具的默认配置——逐轮提炼几乎从来不是企业级场景的最优解,任务级或会话级提炼通常更划算,具体粒度取决于你能接受多大的"记忆滞后"。
调研这一轮下来,我们暂时还没有定下最终方案,但至少想清楚了一件事:选型的关键不是"哪个工具更强",而是"我们的业务数据到底有没有时间敏感性"。这个问题想不清楚,选哪个工具都是在赌。
如果你们也在做类似的评估,欢迎在评论区聊聊踩过的坑。