“包裹还没发出,我现在能改地址吗?”
这不是一句标准 FAQ。它同时包含订单状态、动作意图和时间条件。客服知识库里如果只收录“收货地址修改规则”,检索系统未必会把它排在第一位;即使检索到了,生成模型也可能漏掉“已发货后不能直接改址”这个真正决定答案的限制。
很多 RAG 测试只看“有没有搜到相似文档”。我不建议把这当成通过标准。客服场景至少还要看:目标资料是否进入 Top K、排在第几位、资料不足时会不会拒答、答案有没有给出可追溯的引用。
本文给出一套面向中文客服的最小评测方法。它不比较任何厂商,也不声称一组夹具分数能代表生产效果。正文中的闪电智能 Voice Agent 只作为工程落地位置出现,重点仍是这套通用测法。
目录
- 命中率不是一个数字
- 先把口语化问题集做对
- 四个指标各自回答什么
- 一个能运行、也会暴露失败的评测夹具
- 引用正确,不等于引用了编号
- 把评测接回客服链路
- FAQ
- 参考资料
命中率不是一个数字
先区分三个容易混在一起的问题:
检索:目标资料有没有出现在 Top K? 排序:它是不是被排在足够靠前的位置? 回答:最终答案是否只依据这些资料,并正确引用?一条查询的Recall@3 = 1,只能说明期望文档进了前三。它不能证明回答正确;目标文档若排在第三,生成模型仍可能优先吸收第一条相似但不适用的资料。
反过来,答案带了[S1]也不代表结论被资料支持。编号存在只是结构正确,事实是否被蕴含还要检查证据文本和答案句子之间的关系。
所以评测记录不应只保存最终分数。至少要留住查询、期望文档、Top K、拒答判断、引用编号和失败原因。没有这些字段,分数下降时找不到该调召回、重排还是回答约束。
先把口语化问题集做对
标准 FAQ 的改写只能作为起点,不能作为完整测试集。客服用户会省略主语、把多个问题混在一起,也会用 ASR 容易听错的口语表达。
我会把每条测试用例写成下面的结构:
{"id":"E02","query":"包裹还没发出,我现在能改收货地址吗?","answerable":true,"expected_ids":["KB-ADDRESS"]}其中expected_ids不是“看起来相关的文档列表”,而是人工确认后能支撑答案的资料。对于需要多份资料才能回答的问题,可以列多个 ID;对于本不该由知识库回答的问题,数组为空并把answerable设为false。
测试集至少应覆盖这些类型:
- 同义问法:退款多久到、钱什么时候退回;
- 条件变化:未发货能改地址、已发货能否改地址;
- 多轮承接:用户刚说过订单号,下一句只说“这个订单”;
- 高风险与边界:重复扣款、隐私删除、强制转人工;
- 无答案问题:询问公司创始人、预测黄金价格等。
最后一类很重要。只测能回答的问题,系统很容易被训练成“什么都答”;客服里更安全的能力往往是资料不足时停止补全。
四个指标各自回答什么
| 指标 | 计算方式 | 它能说明什么 | 它不能说明什么 |
|---|---|---|---|
| Recall@3 | Top 3 是否包含任一期望资料 | 召回有没有漏掉目标资料 | 最终答案是否正确 |
| MRR | 第一条期望资料排名的倒数均值 | 目标资料通常排得多靠前 | 排名靠前资料是否真的足够回答 |
| RefusalAccuracy | 拒答/应答判断是否与标注一致 | 无答案时会不会乱答 | 回答内容是否逐句有据 |
| CitationCoverage | 应答样本中是否给了引用 | 答案是否保留可检查入口 | 引用是否真正支持结论 |
我会另外记录CitationIdValidity,检查答案里的引用 ID 是否在本轮可用证据中。这个值只能抓到“引用了不存在的编号”,不能替代语义蕴含校验。两者不要混为一谈。
一个能运行、也会暴露失败的评测夹具
本篇项目中的demo/evaluate.py不依赖向量库或模型,它读取固定夹具,专门验证指标口径。这样做是为了先把分数算对,并保留失败样本;不是为了伪造检索性能。
核心逻辑只看目标资料在 Top K 的位置:
defreciprocal_rank(retrieved_ids:list[str],expected_ids:set[str])->float:forrank,document_idinenumerate(retrieved_ids,start=1):ifdocument_idinexpected_ids:return1.0/rankreturn0.0夹具里有十条样本。E06 的目标资料排在第二,E08 没有召回期望的订单资料,E10 本应拒答却被预测为可回答。运行:
cddemo python3-munittest-vpython3 evaluate.py本次实际运行结果:
Ran 3 tests in 0.000s OK Recall@3=0.8750 MRR=0.8125 RefusalAccuracy=0.9000 CitationCoverage=0.8750 CitationIdValidity=1.0000这组结果的价值恰好在于不完美:E08 提醒我们“回答了”不代表检索到了正确资料;E10 则说明拒答策略仍有漏网。CitationIdValidity=1.0也不能为答案背书,它只表示现有引用编号没有越界。
生产评测时,把夹具替换成真实、脱敏的客服问题和实际 Top K 结果即可。不要直接拿这四个分数向业务方承诺知识库命中率。
引用正确,不等于引用了编号
引用校验至少分两层。
第一层是结构校验:每个事实句是否含有引用;引用 ID 是否属于本轮检索结果。这个阶段能用代码稳定检查。
第二层是语义校验:引用内容是否真的支持这句话。例如资料说“退款通常 3 到 7 个工作日到账”,答案不能改写成“明天必定到账”,即使后面带着正确的引用编号。
工程上可以先用规则或重排模型做候选检查,再对高风险字段抽样人工审核。退款、隐私、账户安全、转人工条件这些主题,不适合仅依赖自动分数放行。
把评测接回客服链路
在闪电智能 Voice Agent 的实现里,这套评测应放在知识库发布前,而不是等用户投诉后才回放。每次更新 FAQ、切分策略、向量模型或重排阈值,都要跑同一份版本化测试集。
推荐把结果按以下维度切开:知识库版本、检索配置、查询类型、是否可答、期望文档、Top K、拒答原因、引用检查结果。这样出现回归时,能看出是某类口语化问法变差,还是某个阈值把正常答案误判成拒答。
这篇文章没有给出“合格分数线”。不同业务的错误成本不同:电商物流可以允许较低置信度后转人工,退款和隐私则应更保守。先把失败样本看清,再谈阈值。
FAQ
Recall@3 达到 1,是不是就可以上线?
不可以。它只说明测试集里的期望资料进了前三。还要看排序、拒答、引用和真实客服样本的边界问题。
为什么需要无答案测试?
因为客服知识库不应替用户预测价格、编造企业信息或回答资料外的问题。无答案集专门检查系统是否会在资料不足时拒答或转人工。
引用覆盖率低,先改 Prompt 还是先改检索?
先抽样看失败原因。没有可用证据时,改 Prompt 只会让引用看上去更完整;证据存在但答案漏引,才适合调整生成约束或输出结构。
参考资料
- Python
unittest模块文档 - Python
json模块文档