有一次我问我自己做的 agent:"我现在预算多少?"它特别自信地回我:800。可我三天前就改成 1200 了。
它不算撒谎——它的向量库里 800 和 1200 两条都在,检索时俩一起冒出来,模型一懵,挑了旧的那条。你说它错吧它没错,库里确实有;你说它对吧,这明显是过期信息。
就是这事让我下决心自己做一套 agent 记忆。其实最早烦我的是另外两件更基础的:退出再进来,它把我之前说的全忘了;哪怕同一轮对话聊久一点,它也会忘了我前面讲过什么。但真正让我对主流做法死心的,是上面那个"预算 800"——它暴露的不是"记不住",是"管不住"。
做记忆这块,几乎所有教程都一个路子:对话切块、embedding、丢向量库、检索 top-k。我也照着抄过。但用着用着就别扭,尤其事实会变的时候。向量库天生是个往里堆的东西,它不擅长"这条已经作废了"这件事——东西越堆越多,过期的旧的也一直躺在里面,照样被捞出来。而且它是个黑盒,召回错了,我连"为什么召回这条"都看不到。
后来我想明白,记忆的核心矛盾根本不是"检索得准不准",是"我能不能管得住它"——哪条最新、哪条该作废、改过几次、为什么改。这些向量库都给不了我。
于是我干脆反着来,做了个叫 MASE 的东西,召回压根不用向量库,用的是 SQLite 自带的全文检索(FTS5/BM25)。
我知道 2026 年了还用全文检索听着有点反骨。但数字说话:同一个 qwen2.5:7b 本地模型,在 LV-Eval 这个 256k 长文档的事实召回测试上,啥都不做、把全文一股脑塞给模型,只有 4.84%(124 道题蒙对 6 道);接上 MASE,88.71%。还有个更狠的测试叫 NoLiMa,故意让问题和答案字面对不上、必须靠语义理解,这个 7B 裸跑 1.79%,MASE 之后 60.71%。
差这么多,不是模型突然变聪明了,是喂进去的东西不一样了。256k 几十万字一股脑塞进去,模型注意力会散,埋在中间那条关键信息经常就被忽略掉(有个词叫 Lost in the Middle,模型对开头结尾敏感、中间最容易漏)。MASE 不这么干:先检索出相关的几条、把过期的盖掉、只留最新最干净的,最后真喂给模型的(英文)也就 2.8k token 左右。大海捞针变成针就放你手边,当然准。
我觉得这套东西真正值钱的一处设计,是事实"覆盖"不"堆积"。用户改口说预算 1200,MASE 把旧那条 800 标记成被替代(底层就一句 INSERT…ON CONFLICT 的 SQL),旧值不删、转进历史表。模型永远只看到最新的 1200;可我要查"这预算改过几次、从多少改到多少、啥时候改的",历史一清二楚,整个库还能像代码一样 git diff。这是黑盒向量库给不了的——回到开头那个 bug,MASE 这边根本不会把 800 和 1200 一起端给模型。
下面讲个不光彩的,但我觉得这才是真做过的人该写的:我亲手把自己的纠错机制坑了一把。
MASE 改口时要去旧记录里找那条老的、盖章作废,办法是关键词匹配。英文没问题,我拿中文一测,它误伤了一堆不相干的记录。我没装没看见,写了个小脚本专门复现,结果挺打脸。
问题出在中文分词。FTS5 默认分词器是 unicode61,按空格和标点切词。英文天然有空格,"Apollo-3"能切成 apollo 和 3;可中文哪有空格啊,"我的预算"被当成一整坨,你搜"预算"俩字反而搜不到。中文搜不到,系统就降级用 LIKE 做子串匹配兜底。子串这玩意太糙——拿个光秃秃的"3"去匹配,"预算 3000""3 月开会"全给你匹配上,全被误标作废。
最阴的一点是我测出来的:**FTS5 只要有命中,LIKE 就不启动。**所以同一句话在不同的库里,误伤范围完全不一样——库里碰巧有能整词匹配的,就只伤一条;没有,LIKE 一出手就伤一片。这个 bug 的表现是不可预测的,比"误伤多"还让人头疼。我把它老老实实记成已知边界,也想好了怎么修(纯数字别进查询、拿不准的触发词先让大模型复核、批量作废前先数数要动几条、太多就停下报警)。
为啥愿意把这种事写出来?因为我自己看技术文章,最烦那种通篇"我多牛、我数字多好看"的。一个系统靠不靠谱,不看它吹了多少,看作者敢不敢说它哪儿会坏。我连自己纠错会误伤都测了、标了,你反而更能信我前面报的那些数。
顺带说下我怎么报分,这跟"能不能信我"直接相关。LongMemEval 我报两个数:61.0%(严格字符串,差一个字就算错)和 80.2%(大模型判语义)。同一批答案两把尺子量,不是判分放水刷高——其实 substring 那个反被我自己拖低了,因为我的校验让答案变详细("耳机"答成"无线降噪耳机"),意思更准、字面离标答更远,被严格匹配冤枉了。还有个 84.8% 是错题重做拼出来的,我标成"诊断参考",没当正经成绩报上去。
写到最后就一个观点:窗口越做越大的今天,我赌的不是更大的窗口,是更好的治理。窗口决定你能装多少,治理决定你能用对多少。
向量库不是不能用,但在我这种"事实老变、要审计、要能查根"的场景,一套 SQLite 加全文检索的治理逻辑,真的比黑盒向量库让我睡得着觉。
这套现在是 1.0,记忆治理、召回、审计都跑通了。接下来想加多模态,让它不光记文字,也能记图片、表格这类东西。代码开源在 github.com/zbl1998-sdjn/MASE-agent-memory,有兴趣的翻 examples 里那几个 demo,跑一下就有感觉了。