RAG检索评估实战:缺失问题、空结果与无答案题分开统计

检索评估有一种很隐蔽的“提升”:检索器没有变好,只是没有结果的问题从统计里消失了。比如批处理丢了一条日志,或者代码只遍历非空结果,最后的平均分就不再对应原先那批问题。

本文给维护RAG离线评估脚本的开发者提供一个完整检查流程:固定问题集,验证文档和问题ID,分别记录缺失、空结果和无答案题,再计算逐题指标与汇总。实验没有调用模型或检索服务;六份短文档、五道问题和排序列表均为手工构造的教学材料,用来验证评估器,不是模型跑分。

下载完整离线评估包:数据集、故障结果、CLI、对照运行器与14项测试(ZIP)

先运行,看看被漏掉的两道题

解压进入retrieval-eval-v1,执行下面两条命令。仅用Python标准库,需要Python 3.11+;本次实际运行环境为macOS与Python 3.14.4。

python3 run_lab.py
python3 -m unittest -v

虚构语料描述导出保留时间、工单受理与紧急入口、文件上限、批量上限及品牌颜色。结果文件故意漏掉q3,并让q4明确返回空列表。以k=3计算:

问题 相关资料 返回情况 Recall / RR
q1:导出保留时间 d1 d1,d2,d3 1 / 1
q2:工单与紧急入口 d2,d3 d4,d2 0.5 / 0.5
q3:文件上限 d5 整条记录缺失 诊断暂记0 / 0
q4:批量上限 d6 明确空列表 0 / 0
q5:退款手续费 语料中无答案 d4 单独统计

错误对照只选择“可回答且有非空结果”的q1、q2,平均Recall和MRR都为0.75。正确流程按事先固定的四道可回答题统计,二者均为0.375;Hit@3为0.5。但报告同时标记outcome=incomplete,因为q3根本没有运行记录。0.375是帮助排错的诊断值,不能拿它签收为完整评测。

q2说明了两个指标为什么要并排看:两份所需资料只找到一份,所以召回率是0.5;第一份相关资料出现在第二名,倒数排名也是0.5。碰巧数值相同,不意味着两者衡量相同问题。

缺失、空结果、无答案不能合并成一种状态

缺失记录可能来自超时、批处理漏任务、写日志失败,也可能是导出脚本遗漏。评估器无法仅凭结果文件判断原因,所以必须列出问题ID,让调用方回到执行记录查找。

空列表则是一项明确的运行结果:这个问题有记录,但本次没有返回文档。只有确认检索确实完成且结果为空,才可以写成空列表;不能为了消除告警,把超时或丢失日志补成空结果。

无答案题来自人工对资料范围的判断。示例的q5问退款手续费,六份文档中没有相关内容,因此不进入可回答题的Recall/MRR分母。它仍返回了品牌颜色文档d4,报告单独记录这类题的总数、已记录数和非空返回数。这个现象提示检索返回了缺乏答案依据的材料,却不能单独证明后续模型已经编造答案。

"coverage": {
  "expected": 5,
  "recorded": 4,
  "missing_ids": ["q3"]
},
"noanswer": {
  "expected": 1,
  "recorded": 1,
  "nonempty": 1
}

若无答案题自身也没有运行记录,recorded会减少,缺失清单会增加。不会因为“没有看到返回文档”,就把它算成成功拒答。

让命令退出码参与验收,而不只读一个分数

直接检查样本文件:

python3 evaluate.py dataset.json run-incomplete.json --k 3

这条命令故意退出2,同时把诊断JSON写到标准输出。批处理可以保存报告,但必须检查退出码。退出0只表示格式和查询覆盖完整,不代表检索质量达标;退出1表示格式、ID或标注契约不合法;退出2表示运行记录不完整。

运行器还演示了另一个受控对照:把q3改为明确的空结果记录。此时覆盖完整,MRR仍为0.375。完整性检查和效果指标是两道不同的检查:补齐真实记录不会自动修复检索失败,分数不变也不说明日志没有改善。

报告保留数据集ID、k、逐题状态、进入统计的题数及两份原始文件的SHA-256。对比两个版本时,应先核对问题集、文档版本与标注协议,再看分数。仅有一个“MRR=0.8”的字符串,无法说明统计对象是否一致。

先拒绝错误数据,再谈排名

每份输入文件最多2MiB,完整读入内存。数据集包含文档ID、正文及问题标注;运行文件只包含对应数据集ID和每题的有序文档ID列表。程序在计算前拒绝以下情况:

  • 同一问题出现两条结果,或出现问题集以外的ID。
  • 结果或标注引用不存在的文档,数据集版本不一致。
  • 同一个排序列表里重复文档ID,或JSON里重复字段。
  • 可回答声明与相关文档集合矛盾,或没有明确的完整标注声明。

本文对重复文档采用整份输入报错的规则,没有静默去重。这样能够暴露上游合并列表的问题,也不会因为去重后让第四名前移而悄悄改变评估口径。它与本站旧稿“先去重再截断”的演算规则不同;两种规则都必须写清楚,不能混用报告。

结果短于k时照实计算Recall、Hit和倒数排名,不为了凑满候选数而丢弃问题。本文不输出Precision@k,因此没有在分母用实际返回数还是固定k之间隐式切换。文档级ID与片段级ID也应统一,不能把一份文档的多个片段当成多份相关证据。

完整标注是前提,布尔字段不能替人工审阅

本包要求每题显式写judged_complete=true,只是为了阻止“没标注”自动变成“无答案”。程序没有能力证明这个声明真实。示例语料只有六份短文档,作者可以逐份检查;真实语料很大时,通常不能简单沿用这种完整判断假设。

如果标注只是部分相关文档,空相关集合可能代表尚未找到或尚未判断,不能直接视为无答案。应补充判断状态,采用适合不完整标注的评估协议,并报告未判定结果。不要给所有问题机械填true,以通过这个小实验的格式检查。

对照实验中两份配置分数相差一倍,是固定教学输入造成的结果。它说明统计遗漏能改变指标,不说明真实系统通常会虚高一倍,也不能证明某个检索算法更好。五道合成问题不足以估计线上效果或统计显著性。

接入真实流程时,还要保留什么

可以先把自己的检索日志转换成这里的运行格式:每个预先登记的问题都应有完成状态,已完成但没结果要显式记录,失败任务另保留原因。当前简化格式不承载完整异常细节,生产流程需要用问题ID关联到执行日志。

14项测试覆盖固定分母、显式空结果、无答案题分离、缺失无答案记录、重复和未知ID、版本不一致、标注不完整、声明矛盾、截断范围以及零结果/零可回答题。CLI对缺失记录返回2也在演示中实际执行过。测试验证的是评估器行为,没有运行模型、联网检索或生成答案。

检索命中不等于答案正确。资料版本、证据是否足以回答、引用是否支持结论、访问权限以及模型有没有超出材料推断,仍要另设检查。先确保所有预定问题都进入可追查的报告,才有依据讨论换检索器之后究竟改善了哪一步。

源码包SHA-256:9a10e5ab76cc93ed4890f922942b8b9d432a61eeb391e80789e5ed46a2fa3769,采用MIT许可。封面复用本站浅色知识检索概念图,非性能测试截图。

依据与前置阅读

资料查阅与编辑日期:2026年10月26日。作者:玖柒。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享