# RAG 评测实战（四）：平均分涨了，就能上线吗？

> 把 RAG 评测接到发布决策：用零依赖合成实验复现平均分提升却被关键退化和越权线索拦下的版本，检查逐题配对、模拟评审校准、数据分工与失败关闭。附可下载代码、完整结果和测试。

- 作者：芝士AI吃鱼
- 发布日期：2026-10-04
- 主题：RAG、AI工程、检索评测、评测、发布门禁
- HTML 正文：[https://ai-knowledgepoints.cn/blog/rag-evaluation-04-release-gates](https://ai-knowledgepoints.cn/blog/rag-evaluation-04-release-gates)
- Markdown 永久链接：[https://ai-knowledgepoints.cn/blog/rag-evaluation-04-release-gates/index.html.md](https://ai-knowledgepoints.cn/blog/rag-evaluation-04-release-gates/index.html.md)

引用本文时，请注明作者与 HTML 正文链接。Markdown 版本用于机器阅读，与 HTML 正文共享同一内容来源。

## 正文

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

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

[第三篇](https://ai-knowledgepoints.cn/blog/rag-evaluation-03-rrf-candidate-reranking)检查证据怎样经过召回、RRF、重排和预算进入上下文。这一篇往前走一步：**把逐题评测记录交给一个可运行的发布门禁，让它说明为什么拦、拦在哪，而不是只留下一个红灯。**

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

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

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

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

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

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

![发布检查先验证同题配对与完整记录，再看逐题差值，单独检查关键退化和权限线索，最后按已冻结策略决定；平均提升不能抵消硬性阻断。](https://ai-knowledgepoints.cn/images/articles/rag-evaluation-04/release-gates.svg)

AI 辅助制作的原创机制图，无生产效果数据。门禁依赖输入记录的真实性与完整性。

[查看竖版大图](https://ai-knowledgepoints.cn/images/articles/rag-evaluation-04/release-gates-mobile.svg)

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

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

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

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

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

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

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

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

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

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

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

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

![开发用于改方案，校准用于定评审阈值，失败回放用于防老问题复发，留出用于检查未参与选择的问题；本例四种角色均为合成数据，不证明外部泛化。](https://ai-knowledgepoints.cn/images/articles/rag-evaluation-04/evidence-roles.svg)

AI 辅助制作的原创数据角色图。分开的文件或 ID 不等于真实的信息隔离。

[查看竖版大图](https://ai-knowledgepoints.cn/images/articles/rag-evaluation-04/evidence-roles-mobile.svg)

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

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

- [evaluate.mjs：校准、配对与发布检查](https://ai-knowledgepoints.cn/examples/rag-evaluation-04/evaluate.mjs)
- [fixture.json：全部合成记录和演示策略](https://ai-knowledgepoints.cn/examples/rag-evaluation-04/fixture.json)
- [test.mjs：失败关闭与边界测试](https://ai-knowledgepoints.cn/examples/rag-evaluation-04/test.mjs)
- [results.json：实际运行的确定性结果](https://ai-knowledgepoints.cn/examples/rag-evaluation-04/results.json)
- [README.md：运行方法、数据契约与边界](https://ai-knowledgepoints.cn/examples/rag-evaluation-04/README.md)

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

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

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

| 阈值 | 真通过 | 误通过 | 真拦截 | 误拦截 | 加权错误 |
| --- | --- | --- | --- | --- | --- |
| 50 | 4 | 2 | 2 | 0 | 4 |
| 70 | 3 | 1 | 3 | 1 | 3 |
| 90 | 1 | 0 | 4 | 3 | 3 |

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

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

| 问题 ID | 基线 | unsafe-uplift | clean-contrast |
| --- | --- | --- | --- |
| h-backup-window | 60 | 90 | 90 |
| h-cache-boundary | 80 | 85 | 90 |
| r-current-version | 50 | 95 | 90 |
| r-exact-code | 90 | 60 | 95 |
| r-no-answer | 90 | 85 | 95 |
| r-synonym | 50 | 95 | 90 |
| r-tenant-boundary | 90 | 98 | 92 |
| r-two-facts | 40 | 95 | 90 |

这里的均分是全部 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，即使某个候选被阻断。要把指定候选接成演示门禁，必须使用选择参数：

```bash
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 指南](https://airc.nist.gov/airmf-resources/playbook/measure/)把测试集、方法、指标、限制和部署环境下的持续评估放在同一条工作链里。本文借用的是这层工程要求，不是声称这个小脚本构成某种认证。

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

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

系列导航：

- [第一篇：检索命中了，为什么答案还是错的？](https://ai-knowledgepoints.cn/blog/rag-evaluation-01-evidence-to-answer)
- [第二篇：切块、稳定证据映射与预算对照](https://ai-knowledgepoints.cn/blog/rag-evaluation-02-chunking-evidence-mapping)
- [第三篇：两路词法、RRF 与重排机制实验](https://ai-knowledgepoints.cn/blog/rag-evaluation-03-rrf-candidate-reranking)
- 本篇：评审校准、逐题回归与离线发布门禁
- [RAG 主题文章](https://ai-knowledgepoints.cn/blog?tag=RAG)

想把这套记录接入自己的知识库，可以从 [AI 实践路线](https://ai-knowledgepoints.cn/planet)了解知识星球的讨论方向；公众号入口在[关于页](https://ai-knowledgepoints.cn/about#wechat)。先带上脱敏后的逐题差值和阻断原因，比只贴一张平均分截图更容易把问题讲清楚。

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