跳到主要内容

那个记得你撤回了什么的智能体:将删除作为一等公民的记忆操作

· 阅读需 11 分钟
Tian Pan
Software Engineer

三月,一位用户告诉你的智能体停止推荐有室外座位的餐厅——他们搬到了一个带宝宝的公寓,深夜外出已经结束了。九月,智能体为他们的周年纪念建议了一家屋顶酒吧。用户感到恼火,而你感到困惑,因为你亲眼看着三月份的纠正生效了。它被写入了记忆。它仍然在那儿。问题在于它与最初的偏好并排存在,而最初的偏好也还在那里,检索算法浮现了旧的那个,因为它对“周年纪念晚餐”的向量嵌入(embedding)匹配度稍高一点。

这是没有人针对其设计的失败模式。团队在记忆写入上花费数周时间——提取、摘要、嵌入、命名空间——而将删除视为以后再解决的问题。长期记忆使得添加一个事实几乎是零成本的,因此事实不断堆积。但记忆库不是日记。日记允许包含过去真实的事情。智能体读取并据此做出决策的记忆库则不然,因为智能体无法区分事实与过时的残余。

残酷的现实是,一旦任何存储的事实变为虚假,只增不减(append-only)的记忆库就会变成一种负担。在任何服务真实用户的系统中,存储的事实会不断变假:人们搬家、换工作、改变主意、离婚、戒烟,并明确收回上个季度告诉你的话。如果你的架构将删除视为边缘情况,那么你构建的系统的准确性将随着使用而单调下降。用户留存的时间越长,他们的体验就越差。

为什么只增不减的记忆会腐烂

“只增不减”是自然的首选,因为它是简单的首选。写入从不阻塞,从不冲突,从不要求你找到你所反驳的内容。你只需推送另一行。向量数据库(Vector stores)鼓励这样做——更新插入(upserting)一个新的嵌入只需一次调用,而定位并使语义相关的先前嵌入失效则是你必须设计的一个搜索问题。

腐烂在检索时显现。当用户的偏好发生变化时,一个天真的存储库现在持有两个事实:“偏好室外座位”和“避免室外座位,有婴儿”。两者都是真实的条目。相似性搜索“我们应该去哪儿吃饭”时,两者都会被返回。智能体现在被迫在查询时解决矛盾,手里只有这两个字符串以及你碰巧附加的任何时间戳。通常它无法做到,即使最新的事实就在上下文中,尖端模型在执行它时也是不可靠的。

最近一项专门为此设计的基准测试——包含 400 个专家验证的冲突场景和 1,200 个评估查询——发现,表现最好的模型在检测和应用失效记忆方面的准确率仅为 55.2%。对开发者来说,有两个发现至关重要。第一,意识到一段记忆已过时并不意味着模型会应用更新后的信念;这是两种独立的能力,模型通常具备前者而不具备后者。第二,模型极易受到预设了陈旧信息的查询的影响。如果用户问“帮我预订老地方屋顶酒吧”,问题本身就偷换回了陈旧的事实,而智能体也会顺着说。

你无法通过检索工程(retrieval-engineering)来解决这个问题。如果两个事实都在库中,某些查询总会带出错误的那个。修复必须发生在写入和删除时,而不是读取时。

撤回不等于覆写

一旦你接受了删除的重要性,本能反应就是寻找 UPDATE(更新):当新事实到达时,找到旧事实并替换它。Mem0 风格的流水线通过四种操作——ADD、UPDATE、DELETE、NOOP——将其形式化,其中输入的事实会针对现有记忆进行分类,UPDATE 意味着“存在相关事实且新信息取代了它”。这比盲目追加有了实质性的改进。但 UPDATE 和撤回并不是同一种操作,混淆它们会导致一种特定类型的错误。

覆写假设新事实占据与旧事实相同的槽位。“在 A 公司工作”变成“在 B 公司工作”——相同的谓词,新的宾语,干净利落的交换。撤回则不同:用户在告诉你之前存储的信念是虚假的,而且可能根本没有替代值。“其实,我从没说过我是素食者”并不会更新饮食字段;它会删除一个字段。如果你的流水线只知道如何 UPDATE,它就无处安放撤回操作,因此它要么伪造一个替代值,要么丢失这个信号。

当同一个信念被多次存储时,问题会变得更加复杂。现实中的提取流水线很混乱。一个偏好可能以三种不同措辞的条目进入记忆——“素食者”、“不吃肉”、“点过两次素食选项”——而且它们可能存在于两个不同的命名空间中,比如 dietary 配置文件和 restaurant_history 日志。覆写通过相似性匹配找到其中一个并进行交换。另外两个则原封不动。撤回看起来成功了,但智能体仍然保留着那个被要求放弃的信念的三分之二内容。

这就是为什么撤回必须被建模为一个拥有自己语义的独立操作:找到该信念在每个命名空间中的每种措辞,并使它们全部失效。这是一个扇出(fan-out)操作,而不是简单的键值查找。它需要系统知道哪些存储条目是关于同一件事的,即使它们不包含共同的标记(token)——这意味着删除是一个聚类和实体解析(entity-resolution)问题,而不是简单的 DELETE WHERE id = ?

设为无效,而非擦除 —— 并在两端记录时间戳

加载中…
References:Let's stay in touch and Follow me for more thoughts and updates