
我怎么给 RAG 建评测集从实体重叠到 LLM-as-judgeW2 结束时我手上有一套能跑的评测50 条 golden set、召回 66%、忠实度下界 0.988。数字都挺好看。但我盯着那张表看了半天说不清一件事——这套尺子能量出我真正想知道的东西吗答案是不能。它有三个盲区而且每一个都藏着看起来不错的陷阱。这篇讲我怎么把尺子换掉以及换完之后回头体检发现自己的评测集本身是偏的。更新注2026-09-27这篇写完后golden set 已从 50 条扩到120 条judge 也完成了真实 API 全量复测。文中保留了发现问题时的原始数据作为过程记录最新数字在对应小节里以「复测」标出结尾有完整对照表。一、hitk 的三个盲区W2 我主要用 hitk召回5衡量检索。它只能回答该找的块找到了吗而在三个方向上它是瞎的盲区具体表现后果只测检索不测生成块召对了模型照样可能答错、瞎编检索满分也可能全在编命中一个就算对跨文档题只召到一个来源照样判通过掩盖了多来源答不全——W2 里 multi 全中率 0~25% 就是这么被总分 66% 盖住的只有质量没有成本效果涨了但成本涨十倍、p95 飙到 3 秒这系统不能上线第三个盲区最容易被忽略评测不测成本等于鼓励你把效果堆到不计代价。我见过太多优化完效果涨了 5 个点的结论没提单次成本从 ¥0.0009 涨到了 ¥0.009。二、回头体检我的评测集本身是偏的换尺子之前我先做了一件事——写个体检脚本scripts/golden_stats.py看看这 50 条到底长什么样。跑出来是这样【类型分布】 term 25 条 50.0% semantic 15 条 30.0% multi 3 条 6.0% boundary 5 条 10.0% conflict 2 条 4.0% 【来源分布】 deepseek 12 条 24.0% zhipu 20 条 40.0% bailian 18 条 36.0%加上三条告警线问题就出来了告警含义难例只占 20%10/50低于 30%天花板效应简单题上各方案都 80%差距被噪声吃掉deepseek 只占 24%低于 25%该平台覆盖不足评测分数在它上面不代表真实水平deepseek 缺 conflict、zhipu 缺 boundaryconflict平台 × 题型有空格存在结构性盲区这是我最想强调的一点评测集偏了你自己是感觉不到的。你只会看到召回 66%这个数字然后去优化它。但这个 66% 是在一半是送分题、deepseek 只占四分之一的集子上跑出来的——它描述的是你的评测集不是你的系统。所以我把复盘写成了脚本而不是靠感觉。来源偏、难度偏、平台×题型有空格这三种偏法都会让评测失真而它们都是能自动查出来的。到 120 条的缺口也算出来了term 还差 25 条、semantic 15、multi 12、boundary 10、conflict 8合计 70 条来源上 deepseek 差 28、zhipu 20、bailian 22。复测缺口补上了告警清零后来我把这 70 条真补上了。同一条命令再跑一次告警从 4 条变成 0 条golden set 120 条 目标 120 条 【类型分布】 term 50 / semantic 30 / multi 15 / boundary 15 / conflict 10 【来源分布】 deepseek 38(31.7%) zhipu 40(33.3%) bailian 42(35.0%) 【偏斜告警】 无告警分布达标难例占比20% → 33.3%deepseek24% → 31.7%之前两家缺的题型也补齐了。补集过程中踩到一个很阴的坑合并完一跑测试test_golden_expected_chunk_really_contains_answer直接红了——120 条里有 36 条的关键词不在标准答案块里。块里写的是1.5~2出题时标注成了1.5-2。关键词不在块里 这道题永远判不中。它不报错、看不出来只会悄悄把召回率拉低。所以写完题和题能用之间还差一道校验。顺带一个口径变化120 条下差 1 条 0.83 个百分点50 条时是 2 个百分点。样本变大后能分辨的真实差异也变小了——之前5 个百分点以内不下结论的门槛现在可以收紧到 2~3 个百分点。三、换尺子手写 LLM-as-judge 三指标W2 的忠实度 0.988是实体重叠算出来的下界——它能抓住凭空冒出来的数字抓不住用原文的词拼出的错误结论。后者才是 RAG 幻觉里最危险的一种数字都对结论是错的肉眼根本看不出来。要测出这种幻觉只能让 LLM 当裁判。这就是 W3 第一天的活app/evals/judge.py三个指标。指标测什么补哪个盲区faithfulness答案每句话能否由上下文推出真·忠实度替换下界answer_relevancy答案有没有正面回答、有没有跑题生成端跑题context_recall标准答案的关键信息被覆盖了多少多来源完整性软性召回三个指标统一成**「拆解 → 逐条判定」两段式**先把答案拆成原子陈述再逐句问 LLM这句能不能从上下文推出返回 YES/NO 聚合。为什么不让 LLM 直接打 0~1 的分数两个坑一是尺度漂移同一答案两次打分 0.7 和 0.9没法复现二是不可解释分数低了不知道哪句在编。拆成二值判定后分数 支持句数 / 总句数稳定、可复现、能定位到具体哪句翻了车。顺带一提context_recall和标准recall5的区别recall5是 chunk_id 精确命中二值context_recall是关键信息是否被覆盖软性。后者能捕捉 small-to-big 里父块覆盖了子块信息、但子块没进 top-k的情况——这两个指标不是重复劳动。为什么手写而不用 RAGASRAGAS 能做这三件事但它强依赖 LangChain 生态。我手写了一是省依赖二是 W1 就定了规矩——重点是对比「不用框架我自己怎么实现」面试会问。手写 vs 框架的取舍本身就是素材。四、一个必须提前说清的心理预期换更严的尺子数字会下降。我在动手前预估真·faithfulness 会把 0.988 压到 0.85 上下——实测是 92.4%比我预估的高但确实比下界 0.988低。这不是退步是换了一把更准的尺子。评测的价值恰恰在于敢用严尺子量自己——你用下界指标看到 0.988其实是自己骗自己。五、跑完之后120 条全量复测的真实数字judge 真调了 API也跑完了 120 条全量。先回答当初的两个担心模型确实会乖乖只答 YES/NO判定解析零失败拆陈述也没有出现合并成一行的退化——context_recall 没退化。全量结果120 条hybrid指标W2 baseline50 条现在120 条召回566.0%69.2%忠实度 faithfulness0.988下界92.4%真·LLM-as-judge答案相关性 relevancy—51.8%上下文召回 context_recall—76.7%跨文档全中0~25%3 条12.5%15 条拒答率20.0%22.5%p95 延迟1465ms1755ms召回从 66% 到 69.2%说明原来的 66% 不是小样本虚高——这个数字现在可以拿出去讲了。而跨文档全中只有 12.5%且这次是 15 条 multi 题跑出来的之前只有 3 条。样本从 3 条扩到 15 条这个弱点就被确认了而不是说不清跨文档综合依然是全链路最大的短板。这是我后续要主攻的方向。成本上还有一条值得单独说单次 ¥0.01217 ├ 检索/策略 ¥0.00000 ├ 生成 ¥0.00110 ← 系统运行成本 └ 评测 ¥0.01108 ← judge 开销不上生产评测成本 ≠ 运行成本。不分开统计你会看到单次成本涨了十几倍然后以为系统变贵了——其实涨的 91% 是 judge 的钱跟系统没关系。一个没解决的问题relevancy 偏低answer_relevancy只有 51.8%我一度以为是答案真的跑题。查下去发现不是——是这个方法的内生局限从答案反推的问题必然丢限定语。答案通常只有一句话“最小命中前缀是 64 token”而原问题带范围“DeepSeek 的上下文硬盘缓存最小命中前缀是多少”。反推出的是最小命中前缀是多少丢了DeepSeek 硬盘缓存。用二值判定是否语义等价模型会诚实地判 NO明明切题的答案被记 0 分。这正是 RAGAS 原版用 **embedding 余弦连续值**而不是二值判定的原因。我改成三档软性SAME 1.0 / PARTIAL 0.5 / DIFFERENT 0.0后手工用例从 0~0.33 回到 0.67~1.00但绝对值仍偏低。这个我还没解决下一步是拿 RAGAS 对拍一遍。一句话总结评测集不是攒够 50 条题就完事了。它得能分辨方案差距难例够不够、不能有平台盲区来源均不均、尺子得敢量真问题LLM-as-judge 而非下界。这三条我每条都踩过。而且建完不等于完工——扩集之后我才发现 36 条题的关键词根本不在答案块里那批题原本是永远判不中的。