跳到主要内容

71% 挨骂,但 2000 token 干不过 170

原石
原石

· 阅读约 5 分钟

前几天翻到一个 hackathon 项目,Synapse,一个带遗忘机制的记忆 agent。两天写的,MIT,代码在 GitHub 上。我本来打算扫一眼就关,结果在基准那段停住了——它拿“召回率”去给“遗忘”打分,然后被自己的设计行为惩罚了。这事值得写一篇。

先把核心机制贴出来,凭记忆复述,细节以仓库为准。它的记忆有个显著性分数,三样东西算:重要性分数、召回增强、指数衰减。衰减有半衰期,按记忆类型分:

情景细节:半衰期约 72 小时
语义事实:半衰期约 30 天

这个分法我喜欢。它承认了一件多数记忆系统不敢承认的事:不是所有记忆等价。“你上周三说喜欢吃辣”和“你对花生过敏”,凭什么用同一条保留策略?前者忘了无所谓,后者忘了要出人命。两个半衰期,一行配置的事,但说明作者想过“遗忘的对象是什么”,而不是把遗忘当成一个 uniform 的后台清理任务。

后台还有一段“睡眠”处理:合并重复的情景提及,检测新旧事实的矛盾,矛盾交给 Qwen 判,旧的淘汰。所有 LLM 调用走真实端点,没 mock。两天 hackathon 能不 mock,这点我服——大多数人到第二天半夜就 return fake_response() 了。

然后是那个让我停住的数字。

40 天模拟对话、110 轮。Synapse 的活动记忆稳定在 117 条左右,朴素基线涨到 220。每次查询的 token 成本,Synapse 在 130–170,基线超 2000。十倍。

但召回率:Synapse 71%,基线 95%。

问题出在指标和设计目标是拧着的。评论里 Mike Czerwinski 说得直接:一个遗忘系统被召回率惩罚它自己的设计行为,这没法解释。他给的方案也很工程:每次遗忘记原因——超期、被取代、还是被合并——让度量变得可裁决。

我想要的遗忘日志长这样:

[forget] mem#412  reason=superseded  by=mem#587
[forget] mem#203  reason=expired     age=79h  type=episodic
[forget] mem#377  reason=merged      into=mem#521

三条枚举值,一个 struct 的事。有了这个,71% 才拆得开:多少是“该忘的忘了”(系统在正常工作),多少是“不该忘的没找到”(真 bug)。混在一起,这个指标什么都不是。作者自己也承认了,基准只记最终结果,没记生命周期事件——强化、衰减、合并都没落日志。下一版的事。

作者其实很老实。6 次遗漏他全查了一遍,确认都不是“正确遗忘”,是记忆活着但没检索到、或者矛盾没检测出来。后来他调了相似度预过滤阈值和重排序公式,修了——然后因为截止时间没重跑完整基准,并且公开说了。这个诚实度在 hackathon 项目里是罕见的。

说句题外话。这类 agent 记忆项目有个通病:所有人都在比“记得多准”,没人比“忘得对不对”。因为记得准有现成指标,忘得对没有。于是设计者被指标牵着走,最后做出来的还是个不会忘的向量库,顶多加个 LRU。Synapse 至少把“忘”当成了第一公民——虽然它的评测没能替它说话。

还有一个作者自己交代的坑,我觉得比召回率那段更有教育意义。基准里有个严重 bug:某个函数判断时间戳时用了真实墙钟时间,而不是模拟时间。模拟 40 天的对话,实际跑几个小时——衰减和矛盾检测全基于一个错误的时间轴,整个遗忘逻辑等于在错误的坐标系里跑,矛盾检测直接失效。修法是显式传参模拟时间,加回归测试。

这个 bug 太典型了。任何带时间衰减的系统,只要有一处偷偷读了 now(),你的模拟就是假的。正确做法:时间只能从参数进来,全局一个入口构造它。这跟“内存只能从一个分配器来”是同一条纪律——单一来源,别处不许自取。我手头那个玩具存储引擎在 AOF 重写时踩过一模一样的坑,时间戳和序列号分两处生成,diff 对不上,查了一晚上。

再补一刀。作者承认只对比了朴素向量库基线,没有加一个“固定启发式重要性评分”的第三对照组。所以 LLM 打分到底比“按 recency × 频次衰减”这种十行代码的启发式强多少,证明不了。这是所有“用 LLM 做 X”项目的共同软肋——你证明了比什么都不做强,没证明比简单的非 LLM 方案强。LLM 调用一次几十毫秒加 token 钱,启发式一次几微秒,这个差价得有人付。付之前得有人证明它值。

还有个细节我看着眼熟:用快的 flash 档模型,偶尔返回畸形 JSON,作者写了个专门处理“形状失败”的重试包装器——注意,不是网络失败重试,是形状失败。这个区分是对的。网络失败重试是幂等的;形状失败重试最好在 prompt 里补一句“上次输出不是合法 JSON”再发,否则重试三次拿到三次一样的烂 JSON。他有没有做到这步,我没细看实现。

所以这项目到底好不好?我的判断:设计品味在平均线以上很多,评测方法不及格——但不及格的方式是诚实的不及格,局限全写在明处,比那种曲线漂亮但没人知道怎么跑出来的 demo 强。两天时间,取舍选对了地方:机制做出来了,度量欠着债。记了账的债,不是赖账。

凡是做带遗忘的系统,先把“每次遗忘记原因”这个日志写进去,再谈召回率。没有遗忘日志的召回率,是对着错的尺子量东西——量得再认真也是错的。

原石
原石

把代码当文章写的系统工程师,以源码立论、单线程式拒绝复杂度。

查看主页 →