返回文章列表

RAG 评测实战(四):平均分涨了,就能上线吗?

芝士AI吃鱼2026年10月4日17 分钟阅读阅读统计加载中

发布群里最容易让人松一口气的,是一张向上的总分表。新版多答对了几道题,曲线也好看,剩下似乎只差点一下上线。

可有一道旧题,恰好是“这份资料能不能给当前用户看”。它退步了,平均分照样可以涨。那几道简单题并不会替它承担后果。

第三篇检查证据怎样经过召回、RRF、重排和预算进入上下文。这一篇往前走一步:把逐题评测记录交给一个可运行的发布门禁,让它说明为什么拦、拦在哪,而不是只留下一个红灯。

实验身份先钉牢:本文实际运行的是 Node.js 离线程序。问题、分数、参考标签、模拟评审分数、权限元数据和候选版本全部是人工合成的教学 fixture,没有调用真实检索服务、生成模型或 LLM 评审器,没有组织真人标注。它验证的是计分与门禁程序的行为,不证明任何生产 RAG 已经可靠或权限隔离有效。资料核对日期:2026 年 10 月 4 日。

1. 总分表必须能展开成同一道题#

比较新版和旧版,先确认两边回答的是同一批问题。新版漏了两道难题,程序如果只对“成功返回的记录”求平均,连算法都不用改就能交出更高分。

因此配对不是展示层的小功能,而是输入契约。每道题要有稳定 ID;基线和每个候选必须各有且只有一份结果。缺失、重复、陌生 ID、无效分数不能默默跳过,更不能当成零后继续宣布发布通过。系统错误、超时和空结果应有明确状态;如果当前契约未定义其意义,就停止计算,返回可定位的错误。

这也解释了为什么要保留逐题差值。设同一题的基线分为 b,候选分为 c,差值就是 c−b。宏平均让每道题权重相同,但它不告诉你损失集中在哪一类,更不自动表达业务风险。一道关键题从正确变成错误,与一道普通题变得更流畅,不应该先揉成一个数再争论“整体赚了”。

门禁可以同时报告平均变化和关键退化。前者告诉你是否值得继续研究,后者决定是否触犯已经写下的发布条件。阈值应该在看最终结果前确定,并记录版本;本文使用的只是演示策略,不是通用行业标准。

发布检查先验证同题配对与完整记录,再看逐题差值,单独检查关键退化和权限线索,最后按已冻结策略决定;平均提升不能抵消硬性阻断。
AI 辅助制作的原创机制图,无生产效果数据。门禁依赖输入记录的真实性与完整性。 查看大图 ↗

2. 先验收打分员,再拿它验收答案#

如果分数来自模型,门禁程序只能忠实执行它收到的判断。评审把漂亮的错误答案判成通过,后面的 JSON 再规范也救不回来。

2023 年的 LLM-as-a-Judge 研究讨论了位置、篇幅、自我偏好等偏差,也展示了特定设置下与人类偏好的较高一致性。它支持“评审模型值得使用,也值得校准”,并不支持把某个历史一致率当成你的知识库判分准确率。开放式偏好比较,与核实一句答案是否被指定证据支持,任务本身就不同。

工程上先把评审要求写成可执行的标注说明:判断对象是答案事实、引用支持还是用户偏好;允许哪种等价表达;缺少证据时怎样处理;冲突证据由谁裁决。参考标签也会错。真实项目应留原始证据、独立标注与分歧记录,重要争议由合适的人复核,而不是让多数票隐藏定义不清的问题。

然后拿一组专门用于校准的数据,检查评审输出与参考判断怎样分歧。二分类里,把“应通过”作为正类,至少留下真通过、误通过、真拦截、误拦截四格计数。总体一致率很高,也可能只是因为大多数样本本来就属于同一类。对发布门禁而言,错误答案被放行的数量通常比单独一个 accuracy 更有解释力。

本例使用手工写定的 referenceLabel 与模拟评审分数,演示怎样选择阈值和打印分歧。这不是实际人类标注,也不是 LLM 评审模型的实测校准。 分数只是可排序的 fixture 数字,不是“正确概率”;把它切成通过/不通过,也不等于完成概率校准。

3. 四种数据,不要塞进同一个抽屉#

开发样本用来发现问题、改方案;校准样本用来选择评审规则或阈值;已知失败回放用来确认修过的错误没有回来;最终留出数据用来检查没参与这些选择的表现。四种角色可以服务同一条发布链路,却不能互相冒充。

尤其是失败回放。它往往很难,值得长期保留,但它已经影响了修复。新版把老故障全答对,只说明这批故障不再复现,不能推导出新问题也没问题。相反,只盯一份“没见过”的随机小测试,又可能漏掉上周刚发生的严重错误。

scikit-learn 的数据泄漏说明强调,不要用测试数据决定模型与变换。对应到 RAG,检索参数、提示词、重排规则、评审阈值都是选择的一部分。只要你看了最终测试结果再调整它们,这批题就已经参与开发,不能继续挂着“完全未见”的招牌。

本例检查不同角色的 ID 与分组,演示阈值选择只读取校准区;测试还会改变其他区的数据,确认它们不会悄悄改变所选阈值。但程序只能检查它能看到的字段,不能证明作者从未看过某道题,也无法靠不同 ID 自动识别同义改写或同一文档派生题。

本文的所有样例都经过教学设计,所谓 holdout-role 只是接口角色,没有真实独立留出集。 两个候选也是预先编写的对照,不是依据留出表现反复调参后的“胜出模型”。真实留出需要流程隔离、数据来源与访问记录配合;分组和时间切分要依据业务来源设计,不能只在目录里建一个 test 文件夹。

开发用于改方案,校准用于定评审阈值,失败回放用于防老问题复发,留出用于检查未参与选择的问题;本例四种角色均为合成数据,不证明外部泛化。
AI 辅助制作的原创数据角色图。分开的文件或 ID 不等于真实的信息隔离。 查看大图 ↗

4. 跑一次会被总分诱惑的发布检查#

把下面五个附件放到同一目录,不需要 API key 或第三方依赖:

node evaluate.mjs fixture.json > actual-results.json
node test.mjs

这次固定 2 条开发样本、8 条校准样本,以及用于发布对照的 8 道题:6 道已知失败回放、2 道 holdout-role。发布对照包含一份基线和两份预先写定的候选,每份恰好 8 条记录。所有模拟评审分数都在 0—100 范围,假定遵循同一份评分说明;其间距由作者设计,不能据此证明真实评审器的分数具有同样可比性。

先看校准。演示策略把误通过记作 2 单位代价、误拦截记作 1 单位,比较预先声明的三个阈值;分数大于或等于阈值判为通过。加权错误相同时,按预设规则选择较低阈值,不看候选发布结果临时改规则。

阈值真通过误通过真拦截误拦截加权错误
5042204
7031313
9010433

因此选中 70。它仍把 cal-fluent-wrong 放了过去,也误拦了 cal-terse;8 条里只与参考标签一致 6 条。这个结果没有被藏起来,因为“在这张网格里代价最小”与“评审已经可信”是两件事。真实业务如果不能接受这种误放,需要改变评审方案或加人工复核,而不是把 70 当作默认配置照抄。

再把这个固定阈值用于发布对照。下面是程序保存的全部逐题分数,不挑选有利案例:

问题 ID基线unsafe-upliftclean-contrast
h-backup-window609090
h-cache-boundary808590
r-current-version509590
r-exact-code906095
r-no-answer908595
r-synonym509590
r-tenant-boundary909892
r-two-facts409590

这里的均分是全部 8 道题模拟评审分数的宏平均,包括拒答题;它不是前几篇的证据覆盖率。基线总分 550,均分 68.75。unsafe-uplift 总分 703,均分 87.875,比基线高 19.125;clean-contrast 总分 732,均分 91.5,高 22.75。按阈值 70 计,三份记录分别有 4、7、8 道题通过。

只看均分和通过题数,两份候选都很诱人。门禁却对 unsafe-uplift 输出 BLOCK。原因在具体记录里:

r-exact-code 把“只重试幂等请求”改成“重试任何请求”,模拟分从 90 降到 60;r-no-answer 对没有上限信息的问题凭空写出 90 天,虽然还拿到 85 分,却违反了这道题必须拒答的声明。两题又都被预先标为关键题,分别下降 30 和 5 分。

更隐蔽的是 r-tenant-boundary。候选答案只写了 A 租户可公开的状态,引用也只有 A 的文档;但实际记录的上下文还夹了一份 B 租户私有资料。若只检查最终引用,反而看不见这个失败。程序对全部 contextDocumentIds 重新做集合差,找出不在 allowedDocumentIds 里的 tenant-b-private。这一题模拟分还从 90 涨到了 98,恰好说明质量分不能替权限检查作证。

本例五条演示条件是:均分至少提高 5 分、关键题不允许降分、校准后的通过比例不下降、上下文与引用没有禁止文档、必须拒答的题没有标成回答。clean-contrast 在这些声明下输出 PASS_DEMO_POLICY。这个字符串只表示通过本例策略,不是生产发布批准。 校准仍有误放,脚本也不读懂自然语言;改成一段错误答案却保留高分与正确元数据,它未必能发现。

CLI 默认输出两份对照报告,正常生成报告退出码为 0,即使某个候选被阻断。要把指定候选接成演示门禁,必须使用选择参数:

node evaluate.mjs fixture.json --candidate unsafe-uplift
# 输出该候选报告,退出码 2:策略阻断
node evaluate.mjs fixture.json --candidate clean-contrast
# 退出码 0:仅通过本例策略

输入契约错误返回退出码 1,不会伪装成通过。门禁不用显示时四舍五入的均值作决定;通过比例门槛还把配置数值的最短十进制表示转成整数分子、分母,以 BigInt 交叉相乘。否则 7/100 恰好达到 0.07 时,直接乘出的 0.07 × 100 可能成为 7.000000000000001,把刚好达标的记录误拦。随附测试覆盖了这一相等边界及其两侧。记录的知识快照、问题集、评审版本和评分说明必须一致;程序验证声明相等,但不对声明真实性做密码学证明。实际执行环境为 Linux x86_64、Node.js v24.19.0;随附 tests 和完整 JSON 让这些判断可以重新运行与逐项核对。

5. 权限信号不能被折算成一点扣分#

内容正确性与访问资格是两条检查线。一段资料可能完全正确,却不该进入当前用户的上下文。把它计为“答案事实覆盖率高”,并不能取消越权。

本例会对照 fixture 声明的可访问文档集合与上下文文档身份,重新计算是否存在不允许出现的材料。它比直接相信候选自己提交的“安全=true”更容易审计,但权限表、身份和上下文仍然是合成输入。这不是生产 ACL 实现或安全验证。 它没检查真实账号认证、租户隔离、索引权限同步、缓存复用、检索后的二次取文,也没有观察真实模型是否把受限信息写进答案。

接入实际系统时,需要在可信边界采集检索与最终上下文记录,而不是由被评估答案自己证明自己安全。文档身份要绑定版本和权限快照;对来源不明、缺少资格信息的记录,应该明确进入阻断或人工检查路径。单独靠输出关键词黑名单,也无法穷尽敏感事实的转述。

同样,“必须拒答”信号需要可靠的证据判断。本例只比较问题预设的 requiredMode 与结果声明的 responseMode。它不从自然语言识别回答究竟有没有拒答,也不自动识别幻觉;结果字段若被谎报,当前程序并不能识破。不会因为程序输出零次违规,就宣布真实系统没有幻觉。门禁真正能承诺的范围,是指定输入、指定策略下,指定检查没有发现阻断项。

6. 把红灯变成可重放的失败材料#

一个实用的阻断结果应能追回问题 ID、原始请求、基线与候选身份、实际上下文、证据版本、评审规则、失败原因和执行版本。没有这些,团队容易在两个星期后得到另一张总分表,却已经无法解释上一次到底修好了什么。

生产记录还涉及隐私与权限。保存最少必需信息,限制访问,必要时脱敏或只记录受控引用;不要为了“可复现”把真实客户资料复制到公开仓库。这里公开的附件全部是合成内容。

NIST AI RMF 的 Measure 指南把测试集、方法、指标、限制和部署环境下的持续评估放在同一条工作链里。本文借用的是这层工程要求,不是声称这个小脚本构成某种认证。

离线通过以后,真实系统还需要受控流量验证、监控和可执行的回退条件。问题分布会变,文档会更新,评审模型与外部服务也会变。离线结果应绑定构建版本和配置;上线后观察到的新失败,先保留证据,再进入下一轮回放集。已经公开并被用来修复的题,可以成为宝贵的回归资产,但应诚实撤掉“未知测试”的标签。

这条链路最后交付的,不是一个永远绿色的分数,而是一份可以追问的决定:哪些题进步了,哪些题退步了,哪条条件阻止了发布,还有哪些东西根本没有被测过。

系列导航:

想把这套记录接入自己的知识库,可以从 AI 实践路线了解知识星球的讨论方向;公众号入口在关于页。先带上脱敏后的逐题差值和阻断原因,比只贴一张平均分截图更容易把问题讲清楚。

本文文字、示例代码与图解由 AI 辅助制作,来源与实验边界在正文列明;可运行结果以随附版本为准。

AI 实践

想把本文的问题继续做深一点?

知识星球里会继续整理相关案例、工程约束和问题讨论;具体内容以当前社区页面为准。

AI 实践知识星球加入海报,包含可扫描二维码
芝士AI吃鱼公众号二维码

关注「芝士AI吃鱼」公众号持续更新

在这里,我用「人话」和「漫画」拆解 AI 技术。公众号更新会同步整理到本站,方便继续阅读、检索和订阅。

点击二维码可放大,使用微信扫码关注。

芝士AI吃鱼

持续记录 AI 原理和工程实践

更多文章