Skip to main content

评估集没建好,RAG 评测都是白搭

上一篇画了评测的全景图——两仓库、四流程、两套指标。地图有了,接下来开始落地。

评测体系的第一步不是写代码,也不是装 RAGAS,而是准备一份靠谱的评估集。评估集就是评测的地基——后续所有指标(自建的 Hit@K、Recall@K、MRR,RAGAS 的 faithfulness、answer_correctness 等)都建立在评估集的质量之上。地基歪了,后续的执行都是南辕北辙。

抽几条线上 query 不行吗

最直觉的想法:从线上日志里随机抽 50 条用户 query,跑一圈,看看指标不就行了?

不行,三个原因。

分布不均。 线上 80% 的问题可能集中在 3~5 个高频意图——这个手机多少钱、怎么退货、发货了吗。冷门意图(固件升级怎么操作、跨生态设备能不能联动、充电器兼容哪些型号)完全覆盖不到。而恰恰是这些低频场景最容易翻车——知识库文档少、检索容易漏、prompt 没覆盖过。你抽的 50 条可能全是高频的简单问题,指标好看得很,但一到冷门场景就崩。

没有 ground truth。 线上日志有用户问题和系统回答,但没有标准答案,也没有“这条问题应该召回哪些文档”。没有 gold label,Hit@K 算不了(不知道该命中谁),Recall@K 算不了(不知道总共该召回几篇),RAGAS 的 answer_correctness 也算不了(没有 reference 做对比)。

隐私和合规。 线上 query 可能包含订单号、用户名、手机号。直接拿来当评估集,存到仓库里、传到 RAGAS 的 judge 模型去打分,有合规风险。

所以评估集需要专门设计——控制覆盖范围、标注 ground truth、脱敏处理。看着麻烦,但这是所有后续评测的前提。

一条评估样本长什么样

先看一条真实的评估样本,有个直觉感受:

{
"query_id": "S1-01",
"query": "预算 3000 元左右,想买一台拍照还不错的手机,推荐哪款?",
"intent_l1": "SUPPORT",
"intent_l2": "S1_选购推荐",
"difficulty": "medium",
"requires_rag": true,
"expected_answer_type": "recommendation",
"expected_doc_ids": ["GUIDE_PHONE_002", "GUIDE_PHONE_003"],
"expected_doc_ids_nice": ["PROD_PHONE_006", "PROD_PHONE_003"],
"trap_type": "budget_scene",
"ground_truth": "3000 元内首选 Redmi K70(约 2499 元起)...",
"eval_metrics": ["recall@3", "recall@5"]
}

一条评估样本 = 一个用户问题 + 一组标注信息。标注信息告诉评测系统:这条问题的意图是什么、应该召回哪些文档、标准答案长什么样、该用哪些指标来评。

12 个字段不少,但不用一个个平铺着看。按重要性分三层:核心字段决定评测能不能跑,辅助字段让评测更精细,分层字段提供额外口径。

1. 核心字段:决定评测能不能跑

五个必须讲透的字段。

1.1 query:用户原始问题

这不是格式化的标准提问,而是口语化的真实表达。比如“预算 3000 元左右,想买一台拍照还不错的手机,推荐哪款?”——就是用户在对话框里真正会打出来的话。

评估集里的 query 要尽量贴近真实用户的表达习惯:有省略(“能退吗”而不是“我购买的商品可以退货吗”)、有口语(“那个扫地机器人咋配网”)、甚至有不完整的信息(“送父母礼物,想实用一点,买什么比较合适?”——没说品类、没说预算)。如果评估集里全是工整的标准句式,测出来的效果会比真实场景好看得多,但上线就会露馅。

1.2 intent_l1intent_l2:两级意图标签

意图分两级。intent_l1 是粗粒度,三个取值:SUPPORT(售前售中售后咨询)、FEEDBACK(故障报告 / 功能建议 / 投诉)、CHAT(寒暄 / 越界提问)。intent_l2 是细粒度,22 个取值,比如 S1_选购推荐S14_售后政策C2_越界提问

被谁消费?意图 Top-1 准确率——评测系统拿 ragent 预测的意图跟这里标注的意图做比对,算准确率。

为什么需要两级?如果只有 L1,你只知道 SUPPORT 类的意图准确率是 90%,但 SUPPORT 下面有 17 个二级意图,到底是 S1 选购推荐拉低了还是 S14 售后政策拉低了,看不出来。有了 L2,就能精确定位到最差的几个二级意图,定向优化。

比特严选的完整意图体系是 3 个一级 + 22 个二级。意图体系的设计本身也是一门学问,但不在本篇展开——这里只需要知道评估集用两级标签覆盖了所有意图就够了。

1.3 requires_rag:最关键的分流字段

这是整个 schema 里最重要的一个布尔字段。true = 这条问题应该走 RAG 检索,false = 不应该检索,应该走系统兜底(直接回复或转人工)。

为什么必须有?看两个场景:

场景一:不该检索的样本污染检索指标。 用户问“今天天气怎么样?”,这是越界提问,系统应该礼貌拒答引导回业务范围。如果没有 requires_rag 字段,这条也被拿去算 Hit@5——检索肯定 miss,指标被拉低。但这不是检索的问题,是这条根本就不该检索。

场景二:该检索但没检索的误拒被淹没。 用户问“手机电池能用多久?”,系统应该检索产品文档回答,但它说了“抱歉,找不到相关信息”——这是误拒。没有 requires_rag=true 的标记,你不知道这条是误拒还是本来就不该回答。

requires_rag 的三个核心作用:

  • 检索指标的样本过滤:Hit@K、Recall@K、MRR 只统计 requires_rag=true 的样本,避免被天气、寒暄这类问题污染
  • 误拒率requires_rag=true 但系统没有给出 RAG 答案的比例——该答的不答
  • 错答率(过召回)requires_rag=false 但系统去检索了的比例——不该答的瞎答

150 条里 132 条是 true(88%),18 条是 false(12%)。false 分布在四类意图里:

意图条数正确行为
F2 功能建议5记录反馈,转工单系统
F3 投诉吐槽3安抚情绪,转人工客服
C1 寒暄问候5直接回复问候
C2 越界提问5礼貌拒答,引导回业务范围

注意 F3 投诉吐槽有 5 条,但其中 2 条 requires_rag=true——因为那两条投诉里夹带了故障描述(“洗地机用了三个月就坏了,太差了”),系统应该先检索故障排查文档再安抚,而不是直接转人工。requires_rag 不是按意图一刀切的,是逐条标注的。

1.4 expected_doc_ids:标准召回文档集

解锁付费内容,👉 戳