把切分长度调大以后,十天质量问题答对了。能否说明新方案更好?还需要检查四十天、特殊产品、无答案和历史政策问题。一次修改可能恢复了条件,也可能把其他主题混在一起。
RAG 评价的对象不是一段最终文字,而是一条信息处理链。只有把入库、召回、排序、阅读和生成分开观察,才能知道改善发生在哪里。本篇使用人工教学排序手算指标,不报告未经运行的提升百分比。
先给问题标注需要什么证据
对“十天质量退货运费谁出”,政策级问题需要质量条款;对“A100 是否已符合免费退回资格”,还需要订单事实与核实记录。标注不能只写一句标准答案,否则找回半条证据也可能被当成完整成功。
一个样本至少保存稳定 ID、问题、适用版本、必要证据、可接受结论与缺失时的行为。有多套等价证据时要允许替代集合,不应该只因为文档 ID 与标注不同就判错。切分变化后证据可以通过原文区间或文档版本关联,而不只靠易变的 chunk_id。
样本应覆盖正常表达、编号差异、条件跨块、多跳、冲突、无答案和权限边界。专门收集的极端样本不能冒充线上比例;分别报告常见分布和关键边界。客户数据需要按授权处理,不能为了评价把受限资料复制到不受控环境。
Hit Rate 与 Recall 回答的不是同一个问题
假设需要两条证据 quality 与 order,前三名是 invoice、quality、normal。至少命中一条,所以这个问题的 Hit@3 为 1;必要证据只覆盖一半,所以 Recall@3 为 1/2。多问题的命中率是这些 0/1 的平均。
如果任务必须同时具备两条才能回答,还可以单独记录完整证据集合是否覆盖。召回率 0.5 不代表答案有一半可信,更不能直接转成审批概率。
倒数排名关注第一条相关证据的位置。第一条在第二名,RR 为 1/2;多问题平均得到 MRR。使用 @k 时要明确只看前 k,超过截断范围按未命中。它适合观察早期有效结果,却不度量是否找到全部多跳依据。
nDCG 怎样考虑相关等级与位置
给核心政策相关等级 2,补充订单等级 1,无关等级 0。采用增益 2^rel-1,在第 i 位再除以 log₂(i+1),累加得到 DCG。把所有可用相关文档按理想顺序排列得到 IDCG,实际 DCG 除以它就是 nDCG。
前三名只有第二位的核心政策,DCG=3/log₂3≈1.893。理想前两位分别是核心政策和补充记录,IDCG=3+1/log₂3≈3.631,所以 nDCG@3≈0.521。这个数体现位置与等级,不是答案正确率。
相关等级是人工规则的一部分,必须定义“核心”和“补充”是什么意思。资料不存在的问题没有理想相关排序,IDCG 为零,应单独评价无答案处理,而不是随意填一个看起来好看的分数。
"""计算单个教学问题的检索指标;排序与相关等级均由作者标注。"""
import math
# 2 表示核心证据,1 表示补充证据;未列出的文档等级为 0。
relevance = {"quality": 2, "order": 1}
ranking = ["invoice", "quality", "normal", "order"]
k = 3
# 同一文档重复返回不应增加召回或 DCG,先保留第一次出现。
unique = list(dict.fromkeys(ranking))
top = unique[:k]
relevant = {key for key, grade in relevance.items() if grade > 0}
hits = relevant.intersection(top)
recall = len(hits) / len(relevant) if relevant else None
hit_rate = float(bool(hits))
rr = next((1 / rank for rank, doc in enumerate(top, 1) if doc in relevant), 0.0)
def dcg(grades):
# 使用指数增益 2^rel-1,排名从 1 开始,对较晚结果打折。
return sum((2**grade - 1) / math.log2(rank + 1) for rank, grade in enumerate(grades, 1))
actual = dcg([relevance.get(doc, 0) for doc in top])
ideal = dcg(sorted(relevance.values(), reverse=True)[:k])
print({"Hit@3": hit_rate, "Recall@3": recall, "RR@3": rr,
"nDCG@3": actual / ideal if ideal else None})
# 无答案问题的 IDCG 为 0,应单独评价拒答,不强行纳入这个归一化分数。
完整脚本见 retrieval_metrics.py下载。这些指标只能检查给定标注下的检索,不知道文档解析是否把原文数字弄错。源文件核对依旧是前置条件。
最终答案至少分三个方向看
答案正确性关注结论是否符合任务和实际依据;忠实度关注它是否增加了证据不支持的主张;引用检查关注标识、定位与支持关系。模型碰巧凭参数知识答对,却引用了一段无关文字,正确性可能过关,引用仍然失败。
引用精确性可观察已给出的引用中多少支持对应主张;引用覆盖率可观察需要依据的主张中多少拥有有效引用。两者需要先定义主张切分与支持标准,不能只数方括号。一个回答只有一条容易支持的结论,也可能通过引用精确性,却漏掉用户真正关心的其他问题。
拒答也要双向评价:无资料时是否硬答,有资料时是否过度拒绝。全拒答能降低某些幻觉指标,却让任务成功率归零。部分回答应检查它是否准确指出缺口,而不只匹配“资料不足”四个字。
沿着中间记录定位问题
保留原始问题、改写查询、过滤条件、召回 ID 与分数、重排结果、最终上下文、模型响应和引用检查。若正确原文根本没入库,先修解析;若精确搜索能找到而 ANN 找不到,调索引;若已召回却被最终裁掉,改排序和预算;若完整证据都在但条件丢失,才重点看生成。
这类诊断避免把所有错误归到 Embedding 或生成模型。比如 P-205 误答 P-204,可能来自查询实体提取,也可能来自产品过滤没实施;两种问题都能表现为最终型号错,却需要不同修复。
记录中还要区分无结果、超时、拒绝访问与模型拒答。否则一次网络异常会被误算成检索召回失败,或者被写进知识库“没有相关政策”的结论。
一次改变一个主要因素
先固定语料快照、问题集、模型与生成参数,比较切分;再在相同切分上加入混合检索;再比较是否重排。每次保存旧错新对与旧对新错的样本,避免总分掩盖关键退化。
开发集用于调参,保留集用于最终评估。仅改变订单号的近重复问题应按模板或会话分组,避免泄漏。自动从文档生成问题可以扩大覆盖,但生成器可能只问最容易回答的句子,仍需人工加入现实中的省略、否定和跨资料需求。
模型评审要与人工样本对照,隐藏版本名、交换候选位置并给出明确标准,检查是否偏爱长答案。重复运行可以观察稳定性,但同一问题重复十次不是十条独立业务样本。
质量之外,计算一次请求的代价
一轮查询可能包括问题编码、两路检索、二十条重排、压缩和生成。总延迟中有串行依赖也有可并行部分,不能把每阶段平均值随意相加当成尾延迟。记录单请求耗时与 p50、p95,同时报告数据规模、并发和缓存条件。
成本也要包括建库编码、更新、重排与多次生成。把 Top-k 从 20 增到 100,可能提高召回,也可能让重排成本显著增长。比较在同一质量门槛下所需资源,比只选最高总分更贴近应用。
本篇讲评价方法,本轮不执行评测或模型调用。下一篇讨论Agentic RAG 与 GraphRAG,当路径变得灵活,评价还要进一步覆盖动作轨迹与停止行为。