从意图到检索:指标拆解
上一篇讲了 runner 的双接口聚合——一条 query 跑两个接口,SSE 拿答案和 TTFT,JSON 拿检索证据和意图分类,合并成 EvalRecord 落进 runs/*.jsonl。20 条样本跑完,20 行 JSON 静静躺在那里。
打开 JSONL 随便看一行,里面有 intent_pred 等于 S14_退换货咨询、retrieved_doc_ids 包含 POLICY_RETURN_002 和 FAQ_RET_001、reference_doc_ids 包含 POLICY_RETURN_002。这些字段怎么变成一个能量化好坏的分数?
评测体系有两套指标。这篇先讲自建指标里最核心的两组:意图分类准确率和检索质量。意图是链路最上游的闸门——判错了后面全白干;检索是召回阶段的底线——文档都没找对,答案不可能对。这两组指标有个共同特点:不依赖 LLM,纯集合运算,秒级出结果,每次提交都能跑。
Part 1:意图分类——错了后面全白干
1. 为什么意图是最上游闸门
RAG 系统的处理链路是一条单向管道:意图识别 → 路由 → 检索 → 生成。意图是管道的第一个阀门,错了之后水流方向就偏了,后面每一步都在错误的方向上努力。
拿一个具体例子来说。用户问“买了两周的耳机能退吗”,正确意图是 S14_退换货咨询,系统应该去政策库里查退换货规则。但如果意图错判成了 S2_商品详情,系统会去商品库里检索耳机的产品参数——频响范围、蓝牙版本、续航时间——然后一本正经地回答一堆跟退货毫无关系的参数信息。
用户看到这个回答只会觉得:这系统是不是傻?
关键在于:RAGAS 的 5 个指标(faithfulness / answer_relevancy / answer_correctness / context_precision / context_recall)完全不评意图分类。它们只看给了什么上下文、答了什么内容,不管路由对不对。意图错判但恰好检索到了沾边的内容,RAGAS 可能还会给个不错的分数——但用户体验已经崩了。
所以意图指标必须自建,而且要放在所有指标的最前面看。
2. Top-1 Accuracy 怎么算
2.1 先搞懂这个名字
Top-1 Accuracy 拆开看两个词:
- Top-1:只看系统给出的第一个(最有信心的)预测结果
- Accuracy:准确率,就是猜对了多少
合起来就是:系统最有信心的那个意图预测,猜对了多少比例。
为什么要强调 Top-1?因为系统有时候会对一个问题给出多个候选意图(比如复杂问题被拆成几个子问题,每个子问题各识别出一个意图)。Top-1 的意思是只拿排第一的那个来判对错,是最严格的标准。如果将来做 Top-3 Accuracy,就是看前三个预测里有没有包含正确答案——条件更宽松,分数自然更高。
代码里把这个指标命名为 intent_top1,和 Top-1 Accuracy 是同一个东西,只不过一个是概念名,一个是代码里的变量名 。
2.2 怎么算
算法极其简单:逐条样本对答案,算命中比例。
假设有 10 条测试题,每条事先标注好了正确意图,让系统去核对,核对完逐条对答案:
| 题号 | 正确答案(人工标注) | 系统的预测 | 对了吗 |
|---|---|---|---|
| 1 | 退换货咨询 | 退换货咨询 | 对 |
| 2 | 商品详情 | 商品详情 | 对 |
| 3 | 退换货咨询 | 保修维修 | 错 |
| 4 | 闲聊 | 闲聊 | 对 |
| 5 | 选购推荐 | 对比选购 | 错 |
| 6 | 商品详情 | 商品详情 | 对 |
| 7 | 退换货咨询 | 退换货咨询 | 对 |
| 8 | 对比选购 | 对比选购 | 对 |
| 9 | 闲聊 | 闲聊 | 对 |
| 10 | 选购推荐 | 选购推荐 | 对 |
对了 8 条,总共 10 条:
intent_top1 = 8 / 10 = 80%
没有加权,没有部分得分——完全匹配就是对,不匹配就是错。
用公式写就是:
intent_top1 = count(intent_pred == intent_l2) / count(有标注的样本)
两个字段分别来自 EvalRecord 的不同段:
intent_pred(系统预测值):来自/rag/eval接口返回的intentLeafIds,经过build_record取第一个非空值intent_l2(人工标注值):从评估集eval_set_v1.jsonl原样复制的正确答案
对齐方式就是字符串直接比较——intent_pred 和 intent_l2 都是 S14_退换货咨询 就算对,不一致就算错。
2.3 哪些样本参与计算
所有有 intent_l2 标注的样本都参与计算,不限 requires_rag。闲聊类(CHAT)和反馈类(FEEDBACK)样本虽然不走 RAG 检索,但它们同样需要正确的意图分类来路由——闲聊判成知识检索,系统会无意义地去查知识库,白白浪费算力和用户等待时间。
2.4 多子问题的取值
Ragent 处理复杂 query 时会做子问题拆分,每个子问题独立跑意图识别。EvalRecord 里的 intent_pred_all 记录了所有子问题的意图列表,intent_pred 取的是第一个非空值:
intent_pred = next((c for c in intent_codes if c), None)
绝大多数情况下只有一个子问题,intent_pred_all 长度为 1,intent_pred 就等于列表里唯一的那个值。多子问题的场景极少,目前评估集里的 query 都是单轮单意图的,不需要过多纠结这个细节。