samememory.life
项目/ 技术博客/ RAG 与检索

从 43% 到 86%:帮助中心 Agentic RAG 的十轮评测迭代

RAG 与检索 Agentic RAG 检索评测 cross-encoder 多路召回
2026-10-15 发布 · 2026-10-21 更新 · 约 2732 字 · 约 7 分钟
本文目录(9 节)
  1. 一、项目是什么
  2. 二、先修尺子:评测口径的两次踩坑
  3. 三、检索架构:两路召回 + 降权 + 重排
  4. 四、十轮评测总表
  5. 五、三个深坑与一个负结果
  6. 六、agentic 快慢路径与意外收益
  7. 七、数据闭环:日志、缺口报告与澄清反问
  8. 八、四条方法论
  9. 九、现状

「帮助中心问答助手」上线在 samememory.life/rag:用中文向 Postman 官方文档提问,回答带引用编号,答不了会拒答或反问澄清。全部跑在一台 1.8GB 内存 / 2 核的 VPS 上——ONNX 量化版 e5-small 做向量、Chroma 做库、deepseek-v4.1-flash 做生成(OpenAI 兼容端点),FastAPI 单进程同时服务多个文档项目。

语料是 Postman 帮助中心 732 篇英文文档切出的 8187 个块。架构本身不复杂,复杂的是「怎么知道每次改动是变好还是变坏」。这篇文章讲的就是这条评测迭代线:十轮评测,命中 43% → 86%,LLM-judge 2.55 → 4.56,其中三个负结果比三次升级更有讲头。

💡 一句话结论:先修尺子(评测口径),再动架构(双路召回 + 重排),最后校准自动化(agentic 快慢路径)——顺序反了,一切优化都是对噪音做梯度下降。
图 1 · 十轮评测迭代的四个战场(v2 → v10)
图 1 · 十轮评测迭代的四个战场(v2 → v10)十轮评测迭代先把尺子做对检索架构快路径门校准数据闭环LLM 反推出题虚高教训50 题分层黄金集双路召回 + 降权cross-encoder 重排多查询扩检父子块回填dense 门失效发现rerank 单门 0.35查询日志 + 缺口报告澄清式反问

一、项目是什么

与站内 pycamp 那套课程知识库检索不同,这次的语料是英文官方文档、提问是中文口语,跨语言是主要矛盾。检索链路最终形态:

  • 双路召回:英文原文 dense + 中文摘要 dense(离线批量为每个块生成 60-80 字中文摘要,查询时中文问题先命中摘要再映射回英文块),RRF(k=60)融合;
  • 模板页降权:CLI 等模板化页面得分 ×0.45——硬排除会让真正问 CLI 的问题永远无法召回,降权保留可能性;
  • cross-encoder 重排:SiliconFlow 免费的 bge-reranker-v2-m3,零内存占用,延迟 +0.5s;
  • 多查询扩检:原问题 + LLM 生成的中文改写 + 英文翻译,3 路并行召回合并重排;
  • 父子块:小块(800 字)做召回保精度,命中后回填所属小节全文(≤3200 字)给生成保完整。

二、先修尺子:评测口径的两次踩坑

坑 1:LLM 对着语料反向出题,全对是假的

最早用 LLM 读着文档出 14 道评测题,检索配置一跑:100% 命中 / 4.33 分。换了人工把关的「真实用户问法」集合同样配置重测:43% / 2.55。原因:出题人知道答案,会不自觉复用文档措辞,题面和文档词面高度重合——评测集分布和真实提问分布完全是两回事。

坑 2:多跳题的命中口径

「命令行怎么跳过确认提示?推送前先查无效文件用什么命令?」这种题要跨段落拼答案,gold 只登记一篇文档会误判 miss。50 题版黄金集改成 source_docs 数组、任一命中即算,并把多跳题占比固定在 20%。

最终口径:50 题 LLM 集(口语化约束 + 分区分层抽样 + 20% 多跳双文档)做主口径,14 题人工集做悲观参照——LLM 集系统性偏乐观,两个数字都报,不挑好看的。

三、检索架构:两路召回 + 降权 + 重排

每个组件都是消融出来的,不是设计出来的:

改动触发的问题实测
模板页硬排除 → 降权 ×0.45CLI 模板页污染向量空间(裸 dense 33%);但硬排除让真正问 CLI 的题永远无法召回pd-008 从拒答修复为满分
+ 中文摘要双路中文问题与英文文档词面/语义双断裂,BM25 在跨语言语料上是纯噪音(消融掉分)中文多跳题召回率大幅回升
+ cross-encoder 重排e5 余弦分数挤在 0.91-0.92 无区分度断崖式区分:正确文档 0.984,第二名 0.008
+ 多查询扩检单次 embedding 对措辞敏感3 路并行召回合并重排,命中率 84%→86%

四、十轮评测总表

版本命中judge变更
v2 基线43%2.55oversample + 硬排除(14 题人工集)
v379%4.27降权 + 摘要双路 + cross-encoder
v479%4.45+ agentic 快慢路径(14 题集)
v584%4.3250 题集 + 多查询扩检
v682%4.28❌ 只调 rerank 阈值无效
v786%4.50门控校准(见下)
v882%4.38❌ 中分段强制过判,负结果
v986%4.38+ 父子块(细节收益 judge 测不出)
v1086%4.56+ 60-80 字摘要 v2

五、三个深坑与一个负结果

坑 A:快路径门 0.78 永远放行

agentic 检索设计了快慢路径:分数够高直接答,不够就触发「LLM 判断资料充分性 → 子查询补充检索」。上线后 50 题只触发 1 次,形同虚设。排查发现 e5 的余弦分布天然跑在 0.85-0.93,dense≥0.78 的门永远放行。改成「有 rerank 分时只信 rerank 门(0.35)」后,同一配置 84%→86%、judge 4.32→4.50。阈值必须对着分数的实际分布校准,不能拍脑袋。

坑 B:judge 拦不住"自信错误"(负结果)

想把门提到 0.60、让中分段全部过一遍「资料充分性判断」。结果 82%/4.38 低于基线:6 次判断全部"资料足够"。原因:judge 只看问题和检索片段,看不到标准答案,对「同主题但错误文档」天然无感。这类错误的根治在语料补齐与父子块,不在门控。

坑 C:增量入库的 idx 错位陷阱

中文摘要用 idx 指向 chunk 的全序位置。新文档按文件名排序会插进中间,一旦全量重建,所有摘要映射整体错位。解法是增量追加:新块续号排到尾部,旧 idx 不动。离线索引与增量更新的耦合,必须在设计期想清楚。

坑 D:容器迁移丢环境变量

docker restart 不会重新读取 env_file——容器从 docker run 迁到 compose 后,模型路径变量丢了,触发一个潜伏 bug(hf_hub_download 未导入,此前一直靠预置路径变量绕过)。

六、agentic 快慢路径与意外收益

快路径:rerank ≥0.35 直接生成,绝大多数问题零额外延迟。慢路径:LLM 判断资料充分性 → 生成 ≤2 个子查询补充检索 → 合并去重 → cross-encoder 全局重排,最多 2 轮,全程 fail-open。

🌟 意外收益:评测日志显示,慢路径的 LLM 会自动把中文问题翻译成英文子查询再检索——相当于免费做了跨语言桥接。最难的一道多跳题靠这个从 judge 1 分修到 5 分。

为什么不用 LangGraph:1.8GB 的机器装不动 langchain 依赖树;自研循环 60 行、流程透明、每个环节可单独评测。这个取舍本身比用了什么框架更值得讲。

七、数据闭环:日志、缺口报告与澄清反问

  • 查询日志:每次问答落 SQLite(问题、拒答与否、置信度、top 分数),日志失败绝不影响主流程;
  • 缺口报告:脚本聚类拒答/低置信查询,输出「用户在问什么但语料没有」——上线后的准确率是靠这个循环养出来的;
  • 澄清式反问:拒答前先判断,问题含糊就反问澄清(带 2 个选项),完全无关才拒答。上线以来尚未自然触发——语料覆盖好到连「怎么把 Hoppscotch 的集合导入」这种竞品问题都能正确作答,属于健康状态。

八、四条方法论

  1. 先修尺子,再优化:口径错了,一切优化都是对噪音做梯度下降——LLM 反推出题那次 100% 的假繁荣差点成为"上线成绩";
  2. 负结果也是产出:LLM 反推出题、BM25 跨语言、门控过判——三个"不要做"省下的弯路比三个"要做"值钱;
  3. 阈值对着分布校准:rerank 0.98 vs 0.008 的断崖让 0.35 很安全,dense 0.85-0.93 的分布让 0.78 永远失效;
  4. 长链路一致性:离线索引与增量更新的耦合、评测口径与线上实现的一致,这类问题不在设计期想清楚,就会在深夜以最恶心 debugging 的形式出现。

九、现状

线上现状:50 题口径命中 90% / judge 4.54,拒答 3/3 正确,首字 1-3s。检索质量的边际收益已经见顶,下一步的准确率增长来自真实流量:查询日志攒够数据后跑第一份知识缺口报告,让 bad case 自己开口说话。

在线体验:samememory.life/rag · 评测明细与消融数据见项目仓库 docs/eval-*.md。