返回博客

我的代理总是忘记已保存的事实

2026-06-184 min read

AIdaemon 完全在我自己的机器上运行,使用的是 Gemma 4 等开源模型,没有任何部分调用云端 API。它在我与它交谈时保存关于我的事实,然后在被问到时查找它们。保存事实已经可以工作了。查找事实才是让我感到沮丧的地方。我问它一个我确信它已经存储的事实,但它却什么有用的信息都没有返回,或者返回两个事实,它们虽然在正确的议题上,但却抓不住重点。

搜索是基于含义进行的。每个事实在保存时都会变成一个向量,我的问题在被问到时也会变成一个向量,然后它根据这两个向量的接近程度对事实进行排序。问题在于,接近并不等于正确。一个问题会拉出所有共享其主题的内容,因此几个关于同一事物的冗长事实会挤掉那个真正包含答案的简短事实。在我一个反复失败的查询中,我检查了答案所在的位置。大约在第 30 位。搜索只读取了前几个就停止了,远远没有到达那里。

为什么只看摘要是不够的

第一次搜索使用的是双编码器(bi-encoder),一个名为 all-MiniLM-L6-v2 的小型模型。它在保存时单独编码每个事实,在搜索时单独编码你的查询,然后比较两者。它从未同时看到它们。这就是它速度快的原因。你只需嵌入一次所有内容,搜索就只是对存储进行廉价的数学运算,提问时无需调用模型。

这也是它排序错误的原因。双编码器能捕捉到大致的主题,但会弄错精确匹配。当你问及某事时,它会浮现出同一领域内的任何内容,而你想要的事实则会沉下去。它的成本足够低,可以运行在你拥有的每一个事实上,这正是当你所有内容都必须存储在自己的硬件上时会选择它的原因。它只是不够仔细,无法很好地对它们进行排序。大多数时候,这已经足够了。但当我需要一个具体答案时,它就失效了。

让第二个模型真正地阅读

解决方法是使用重排序器(reranker)。它是一个交叉编码器(cross-encoder),因此它会同时读取我的问题和一个候选事实,在同一次传递中进行评分,以判断该事实在多大程度上回答了问题。双编码器比较的是两个独立构建的摘要,而重排序器则读取这对内容。更好的问题,更好的答案。缺点是速度。这是每个事实运行一次模型,所以你无法将它指向整个记忆库然后等待。

解决这个问题的方法很老套,也很无聊,但它确实有效。先检索,再重排序。让廉价模型抓取一大堆可能相关的可能事实,然后将昂贵的模型只用于处理这一小堆事实。

在 AIdaemon 中,双编码器会根据一个宽松的阈值 0.22 拉取前 50 个候选。我将这个阈值设置得比用于直接放入提示中的记忆的 0.30 更宽松,因为同义词得分较低,而严格的阈值会在重排序器看到正确事实之前将其排除。第一阶段不必完全正确。它只需要将答案包含在 50 个结果中即可。然后,交叉编码器会重新读取所有 50 个候选事实与问题的匹配度,并重新排序它们。原本排在第 30 位的事实最终会得到它应有的重视,跃升到前面,并被返回。我没有改变事实的存储方式,只改变了它们在输出时的排序方式。

// 第一阶段:双编码器广泛撒网(对所有活动事实进行余弦相似度计算)
let mut pool = cosine_rank(&query_vec, &facts, MIN_SCORE); // 0.22
pool.truncate(CANDIDATE_POOL); // 50

// 第二阶段:交叉编码器重新读取每个(查询,事实)对并重新排序
let docs: Vec<String> = pool.iter().map(|c| c.text()).collect();
let ranked = reranker.rerank(&query, docs, false, None)?;

重排序器与所有其他组件运行在同一台机器上,通过 fastembed crate 作为 ONNX 模型运行,无需调用重排序 API。我使用的是 Jina Reranker v2 Base Multilingual,我特意选择了多语言模型,因为我给 AIdaemon 的笔记会在英语和西班牙语之间切换,而一个仅限英语的重排序器会在我最关心正确获取的事实上出错。

保持低成本

第二个模型是第二个可能出错的东西,所以它被严格控制。它只在第一次搜索需要它时加载,因为下载量不小。如果加载失败,搜索就会回退到它之前使用的普通余弦排序。不会有任何东西崩溃,只是准确性会降低。而且它只在我明确要求代理查找某项内容时运行。自动折叠到每个提示中的记忆仍然走的是廉价路径。将 50 个事实通过交叉编码器处理一次是合理的,在我提问时。如果每次消息都这样做,那就是浪费。

先检索后重排序并不是什么新鲜事。搜索引擎已经依赖它多年了。事实证明,它也非常适合代理记忆。第一个模型抓取一大堆内容,第二个模型正确地阅读这一堆内容,而第二次阅读几乎不花费什么,只需将五十个短字符串通过一个模型处理。这就是为什么当我提问时,AIdaemon 现在能返回它保存的事实,而不是一无所获。

保持更新

将最新文章和见解发送到您的收件箱。

Unsubscribe anytime. No spam, ever.