跳过正文
  1. 博客/

我跑了 50+ 次实验修复 AI 的记忆系统——召回率从 15% 提升到 82.6%

彭再茂 (Michael)
作者
彭再茂 (Michael)
Monash University 博士后研究员。通过 3D 生物打印、AI 赋能医疗设备和跨学科合作,将生物医学研究转化为现实世界的影响力。

这是《我的 AI 助手今天把自己杀死了》的续集。那次自终止事件,导火索就是一个记忆召回失败的 bug。这篇记录我如何修复它。


问题是什么
#

我的 AI 助手 Lulu 有超过 120 个知识文件——每日日志、科研笔记、部门知识库、项目记录。但当我问"可降解合金项目进展如何",她返回了试剂采购记录。当我问"生物材料表征方法",她返回了细胞培养 SOP。

信息就在那里,只是找不到。

我构建了一个包含 40 条查询的测试集——全是我真实提过的问题——建立了一个基准指标:Recall@3(正确文件是否出现在前 3 个结果中?)

基准:15%。

每 20 个问题,只有 3 个返回有用的结果。


我试过什么(以及哪些没用)
#

调搜索参数——+0pp
#

我的假设是向量搜索权重设置错了。于是我尝试:

  • 关闭时间衰减(近期偏差)
  • 将向量权重从 0.6 调到 0.8
  • 调整多样性/MMR 参数

结果:几乎零提升。

关键洞察:搜索参数只能在 embedding 模型的能力边界内微调。如果模型无法区分语义含义,再怎么调参也提取不出它没有的东西。

这相当于在爆胎的赛车上调引擎参数。

扩大搜索范围——+5pp
#

我发现 40 条测试查询中有 23 条失败,原因是目标文件在 departments/ 子目录里——而记忆搜索索引只覆盖 memory/MEMORY.md

配置 extraPaths: ["departments"] 解决了这个问题。Recall@3 从 15% 跳到 20%。

还是很糟,但至少正确的文件在搜索范围内了。

文档端 Anchor——+17.5pp
#

我不再试图改进查询的搜索方式,而是尝试改进文档的索引方式。

在每个文件顶部加一句自然语言描述——一句话说明这个文档回答什么问题:

<!-- auto-anchor-v1 -->
> 本文档回答:某可降解合金项目当前状态如何?已完成哪些实验?下一步是什么?
<!-- auto-anchor-v1 -->

结果:Recall@3 从 20% 跳到 37.5%,+17.5pp。

原因是 embedding 模型在文本明确声明自身相关性时,表现远好于从结构化内容中隐式推断。

这个规律可以推广:文档端优化比查询端优化更持久,因为它对所有未来查询都生效。写一条好的 anchor,比做 10 次参数实验更有价值。


真正的修复:换 Embedding 模型
#

原来的模型为什么错了
#

原来的配置用的是 nomic-embed-text——一个强大的英文 embedding 模型。我的知识库大约 60% 是中文,40% 是英文。

nomic-embed-text 主要在英文语料上训练。对于中文语义区分,它基本上在产生噪声向量。

模型对比(40 条查询,相同测试集)
#

模型Recall@3
nomic-embed-text(基准)15.0%
+ extraPaths 修复20.0%
+ Anchor37.5%
Gemini text-embedding-001,无 anchor65.0%
Gemini text-embedding-001,有 anchor67.5%
bge-m3,无 anchor65.0%
bge-m3,有 anchor72.5%
bge-m3,anchor + 扩展测试集(121 条)82.6%

nomic-embed-text 换到 bge-m3(多语言模型,568M 参数),在其他所有条件不变的情况下,召回率提升了 +50pp

bge-m3 本地运行,无 API 费用。模型 1.3GB。在 RTX 5090 上重建 120+ 个文件的索引,不到 2 分钟。


自动生成 Anchor
#

手动为 94 个文件写 anchor 描述不现实,于是我用本地 LLM 自动完成。

设置:

  • 模型:qwen3:8b,通过 Ollama 在 RTX 5090 上运行
  • 任务:读取每个文件,生成一句话描述它回答什么问题
  • 输出:写回文件的 anchor 格式

94 个文件一夜跑完,总花费:0 元。


核心教训
#

1. Embedding 模型是基础设施,不是配置项。

选错 embedding 模型就像用英文词典查中文单词。没有任何搜索参数能修复这个问题。在做其他任何事之前,先用你实际的语言分布来审计你的模型选择。

2. 文档端优化比查询端优化更持久。

Anchor 的效果超过了我调过的所有参数。而且效果会累积:改进是永久的,对每一次未来的查询都有效。

3. 异构知识库有独特的失败模式。

我的知识库里有一类"万金油"文件(MEMORY.md、AGENTS.md),几乎出现在每个搜索结果的前三——不是因为相关,而是因为它们包含太多话题。加类别感知过滤解决了这个问题。

4. 小型本地模型足够完成标注任务。

qwen3:8b 生成了有用的 anchor。对于有明确输出格式的结构化标注任务,8B 参数足够了。不是每个步骤都需要 GPT-4。


现状
#

系统现在运行在 bge-m3 上,全部文件都有自动生成的 anchor。实际召回率(原始 40 条查询)约 72.5%,扩展的 121 条测试集上达到 82.6%

这还不完美。剩余失败的查询主要集中在:

  • 非常近期的事件(文件尚未重新索引)
  • 可以匹配多个有效文档的模糊查询
  • 跨语言查询(中文问题,英文文档)

这些值得做后续实验。但现在,Lulu 终于能记住我告诉她的事了。


下一篇:本地 embedding 模型 vs API 的实际对比——延迟、成本和质量的取舍。