loading

Loading

探索与发现

首页 Agent 工程

Agent 长期记忆写入:冲突怎么判、怎么消解

分类:Agent 工程
字数: (2369)
阅读: (8)
0
摘要:Agent 长期记忆的重点不在存储,而在写入侧的冲突裁决:相似不等于矛盾,失效也别真删。本文拆解 mem0、PowerMem、时序图三种思路,以及多跳冲突、跨 Agent 并发写入在工程上的坑与成本。

“帮我推荐一家今晚吃饭的地方。”这句话丢给向量检索,命中的大概率是“用户提过某家餐厅”“用户上周问过晚饭”。真正有用的是“不吃辣”“偏好日式”“对花生过敏”。记忆系统要解决的从来不是存的问题,而是在真发生冲突时怎么裁决。这篇只谈写入侧的冲突。

先看几套已知的裁决思路

mem0 给的解法是最直白的:把当前对话和已有记忆库一起交给 LLM,让它输出 ADD、UPDATE、DELETE 或 NONE。它的 Custom Update Memory Prompt 里写死了四选一,返回体带 event 字段;UPDATE 时要求保留原始 id、记录 old_memory。前者保证追溯链路不断,后者是给自己留的 Debug 抓手。这个库默认 infer=True,也就是说,写不写、写什么由模型判断,代码不主动全量落库。

PowerMem 走的是双时态那一路:每一条事实既有 fact_valid_from / fact_valid_to,也有 recorded_at。直接矛盾的旧条目置 fact_valid_to = now,置为失效而不是物理删除;时间演进类的按合并更新处理,旧版本留着,当前版本标 active。它的写入决策是四个动作的路由:新增、更新、合并、删除。这里没有让 LLM 独自兜底,前面挂了规则。

Zep / Graphiti 的角度又不一样:在时序图上让边失效,历史边保留。Mem0 是软降权多版本并存,MemGraph 靠关系遍历做后续追溯。三条路线的取舍不一样,但都有一个共同前提——不物理删除,让“错过的真相”还能被找到。

最容易踩的坑:相似不等于冲突

工程上初版的常见写法是:新事实 embedding 与存量记忆算余弦相似度,超过阈值就当作潜在冲突,交给 LLM 裁决。阈值定高漏检,定低误报。麻烦在于,“好吃”“不吃辣”“最近吃素”三句话之间相似度都很高,但它们既不互斥也该共存。“喜欢意大利菜”和“最近在吃素”不是矛盾,是并存。判成冲突,系统就会把一个叠加的偏好误杀成一个反转的事实。

可操作的划分是按动作路由,不是按相似度阈值。直接矛盾(“喜欢奶酪披萨”→“不喜欢奶酪披萨”)触发失效替换;时间演进(“计划下个月去东京”→“行程改到下季度”)触发合并;并存(“最近在吃素”叠加在饮食偏好之上)触发独立保留、各自按时间衰减。这三类要处理得不一样,靠一个二值“是否冲突”是分开的。

有一种更麻烦的情况:多跳冲突。A 说“Alice 是张三的上司”,B 说“李四是张三的上司”,语义上不算直接谬误,但是推理会冲突。在相关讨论中提到,这在 MemoryAgentBench 层级的评测里是被列出的薄弱点,目前多数方案并未处理。对这类冲突,短时间的工程答案往往是——不要在写入时硬解决,先并存、等强证据,或者在检索时对再一起看到的替代方案——具体做法要看场景的容错空间。

写入冲突和并发冲突是两件事

一个会话里两个 Agent 同时从不同渠道(邮箱摘要、日历同步)写一条用户偏好,就会产生并发问题。解法是在架构层埋一层隔离:Path/PowerMem 都有 user_id / agent_id / 作用域三级的写法,检索前就带好命名空间约束,而不是查回结果后过滤。默认推 PRIVATE 权限、明确收回范围,会比一开始就做全共享容易修。另一个更重要的问题:“一条记忆被 A 和 B Agent 同时更新,后者覆盖前者”——若没记版本,结果就是靠运气。

这里的工程成本很直接:old_memory 不是唯一保护措施,UPDATE(而不是内联写)加上不可变事件记录,才能让回退在回退到上一个版本时(对用户来说通常是“呃你说错了,我刚说的不是这个”)成立。

写入对之后的检索会有回音

另一个需要注意的副作用在“什么时候删除”。按照 mem0 的做法,矛盾的信息应该删除不用更新,因为反转属于“事实变了”,不比口味变化。这有道理,但用起来很容易过多删。更保险的做法是显式失效,保留旧行;只是在检索的那个时刻,用确定性聚合——同一 slot、多条 active 的情况下直接取最新的激活事实。在简单事实类问答上,一段代码聚合的结果强过“把几条都塞进 prompt 让 LLM 自己选”。复杂偏好留给模型。

另一个要关注的细节来自检索管线和写入管线的耦合:`infer=True` 并不保证信息全部落库,而记忆的“写入量”又靠查询改写在读取阶段补。如果用户的偏好只是在自然对话中出现一次,系统得有能力在新的查询里再次挖掘出来,否则一次丢就是这个记忆直接“死了”。这像是你删掉了那条查询的理由,但用户仍抱有期待——尤其在时效性强的信息上。

实用建议

从这类问题常见到的做法总结,有几个动作的成本和风险都较低:

  • 写入只保留“可能值得留下”的部分,默认保留原始对话片段和来源、时间戳、可更新的 flag;
  • 把裁决动作从 update/delete 两个字段拆调成四种(ADD / MERGE / INVALIDATE / NONE),前两个不需要硬删;
  • 保护性做法:所有决定都带 source_span 与 recorded_at,只要发生争议,能追溯到当时的对话;
  • 冲突处理仅走时效性裁决容易漏掉“偏好叠加”,在规则侧至少得核一下“这个新值与旧值是否能共存”,不要全交模型。

记录与脱节的关键词——“全交给 LLM 判断”“先过滤再聚合”——作者对此的判断是有保留的。主判断交给 LLM 是可以的,但记忆这种需要向后一直精确的内容,以及写回后的检索一致性,最好有人在旁注视它半分钟。记忆系统的质量不体现在写过多少条,而在于用户重复一条偏好时,Agent 不会一边记下一边又去犯老错;这点解决了,剩下的再逐步优化都不算着急。

转载请注明出处: 皮哈皮阿

本文的链接地址: https://blog.phpat.com/agent-engineering/625.html

手机扫码阅读

微信或手机浏览器扫一扫,随时随地随心阅读与分享

您可能对以下文章感兴趣
1 / 1
预览图