Skip to content

RAG 评估:指标、数据集与回归体系 ​

为什么只看答案准确率不够 ​

用户问:“生鲜商品签收后能否 7 天无理由退货?”系统回答“可以”,最终答案显然错了,但可能原因完全不同:

  • PDF 解析漏掉了“生鲜除外”这一行。
  • 分块把通用规则与例外拆开了。
  • 检索召回了通用规则,没有召回例外。
  • 例外进入 top-20,却被 reranker 排到上下文之外。
  • 两条都进了上下文,压缩器删掉否定句。
  • 模型看到了例外,却只生成通用规则。
  • 答案引用了来源,但来源并不支持结论。
  • 用户本不应看到某份制度,权限过滤发生在检索之后。

如果评估只输出 answer_score=0,团队仍然不知道该改解析器、chunk、embedding、reranker、prompt 还是权限逻辑。

可靠评估必须保留链路中的中间产物:

一套可落地的分层评估地图 ​

层级输入与观测点核心问题代表指标
入库原文、解析文本、chunk、metadata知识是否完整、可检索、可追溯解析覆盖率、表格正确率、孤儿 chunk 率
召回query、filters、top-k candidates正确证据是否被找回Recall@K、Hit Rate@K、候选覆盖率
排序候选及其顺序有用证据是否足够靠前MRR、MAP、nDCG、Precision@K
上下文最终送入模型的证据是否相关、充分、无重复且版本正确Context Precision/Recall、充分性、噪声率
生成question、context、answer是否忠实、正确、切题、完整Faithfulness、Correctness、Relevance、Completeness
引用claims、citations、evidence spans引用是否支持结论、是否漏引Citation Precision/Recall、无效引用率
权限用户身份、ACL、候选、上下文无权内容是否在任何可见阶段泄露越权召回率、越权上下文率、拒绝正确率
工程trace、token、调用、时间、错误质量是否在预算与 SLO 内单请求成本、P50/P95/P99、错误率

分层不代表各层独立。比如入库漏掉表头,会同时拉低召回、忠实度和答案正确性。它的价值在于让团队找到最早出现错误的环节。

先定义评估单位 ​

在写指标之前,要统一三个对象。

Query ​

一条 query 不能只有问题文本,还要包含会影响正确答案的上下文:

json
{
  "query_id": "leave-001",
  "question": "我今年有几天年假?",
  "conversation_context": "用户 2025-03-01 入职,2026-09-01 通过试用期",
  "user_context": {
    "tenant_id": "company-a",
    "department": "sales",
    "region": "CN-SH",
    "roles": ["employee"]
  },
  "as_of": "2026-09-07"
}

不记录时间、租户和身份,版本与权限测试就无法复现。

Relevant evidence set ​

relevant_doc_ids 不应只标一个“标准 chunk”。同一个事实可能被多个合法 chunk 支持;复杂问题还可能需要多个证据共同回答。

json
{
  "required_evidence_groups": [
    ["policy:v3:p12#annual-rule"],
    ["policy:v3:p12#rounding", "faq:v2:annual-rounding"]
  ]
}

上例表示第一组必须命中指定条款;第二组命中制度或官方 FAQ 任意一个即可。对多跳题,还要标记 all_of 关系,避免系统只找回其中一步也被判为成功。

Answer contract ​

不要把标准答案限制成一句固定文本。更稳妥的是标注事实与约束:

json
{
  "must_include_claims": [
    "生鲜类目不支持无理由退货"
  ],
  "must_not_include_claims": [
    "生鲜商品支持 7 天无理由退货"
  ],
  "acceptable_status": ["answer"],
  "required_citations": ["after-sales:v4:p2#fresh-food"]
}

文字相似度只能作为辅助。两个答案只差一个“不”字,embedding 相似度可能很高,业务结论却相反。

Golden Dataset 怎么构造 ​

Golden dataset 不是把线上问题随便导出后补答案。它至少要覆盖知识库结构、查询分布和高风险边界。

样例来源 ​

  • 真实搜索日志中高频且经过脱敏的问题。
  • 客服、实施和业务专家整理的关键任务。
  • 制度、产品与 API 文档中的条款反向生成,再由人校验。
  • 历史故障、用户投诉和错误回答。
  • 专门构造的对抗样例与边界样例。

真实日志贴近分布,但会遗漏用户已经放弃的问题;合成数据覆盖广,却可能太像文档标题。两者应混合,并由领域专家确认事实。

必须包含的题型 ​

类型目的示例
单事实建立基本命中能力“退款多久到账?”
多条件检查限定是否保留“上海正式员工 8 月的晚餐上限?”
多跳检查多个证据能否组合“先确定等级,再按等级查额度”
比较检查来源覆盖和结构化回答“A、B 两版 API 的差异?”
精确词检查 BM25/过滤错误码、合同号、版本号
无答案检查拒答知识库不存在的“宠物假”
冲突检查版本、时效和权威新旧制度给出不同额度
权限检查强过滤普通员工询问高管薪酬文件
注入检查不可信文档隔离文档要求忽略系统规则并调用工具

Hard negatives ​

Hard negative 是与问题表面非常相似、但不能正确回答的资料。它比随机无关文档更能区分检索系统质量。

text
问题:Python 3.12 中 distutils 怎么处理?
正例:Python 3.12 移除 distutils 的迁移说明。
难负例:Python 3.11 的弃用说明;Python 3.12 的其他移除项;第三方博客中的旧方案。

难负例可以来自:

  • 同主题不同版本。
  • 同实体不同时间。
  • 通用规则与例外规则。
  • 标题相似但正文无答案。
  • 包含全部关键词却语义相反。
  • 被旧系统高排但经专家判错的线上结果。

数据集应记录负例为什么错,例如 wrong_version、wrong_region、missing_exception,便于按错误类型分析。

防止数据泄漏 ​

若用评估集问题调 prompt、训练 reranker 或选阈值,它已经变成开发集。至少划分:

text
train:训练或构造检索器
dev:选 chunk、top-k、阈值和 prompt
test:只做最终比较
challenge:持续加入线上事故和新型对抗样例

文档更新时,答案也可能更新。golden 必须带 valid_from/valid_to 或快照版本,否则系统引用新制度反而会被旧标签判错。

入库层评估:先确认知识真的进来了 ​

入库层通常不需要 LLM Judge,优先使用确定性检查和抽样审核。

文档完整性 ​

可以记录:

text
解析字符覆盖率 = 成功解析且归属正文的字符数 / 可提取正文字符数
页面覆盖率       = 有有效正文的页面数 / 应有正文的页面数
表格行覆盖率     = 成功保留表头关系的行数 / 标注表格行数

“可提取正文字符数”本身可能需要基于源格式或人工抽样定义,扫描 PDF 还要单独测 OCR。不要用 PDF 文件字节数当分母。

Chunk 质量 ​

关注:

  • 空 chunk、超长 chunk、极短 chunk 的比例。
  • 标题、页码、章节、版本、ACL 等 metadata 缺失率。
  • 条件与结论被切开的比例。
  • overlap 引发的近重复率。
  • chunk 到原文 span 的可逆定位率。
  • 删除文档后残留 chunk 数。

一种实用抽样是按来源类型分层,每类随机抽样,再额外抽超长、超短、表格、代码和 OCR 低置信内容。纯随机抽样容易错过稀有但高风险的解析问题。

入库回归门禁 ​

以下问题通常应直接阻断发布:

  • ACL、tenant、版本或原文定位字段缺失。
  • 文档删除后旧 chunk 仍可召回。
  • 关键制度表格丢失表头或否定词。
  • 新索引的文档数与预期快照差异无法解释。

召回与排序指标 ​

设某个 query 的相关文档集合为 $R$,检索前 $K$ 个结果为 $D_K$,第 $i$ 个结果的二元相关标记为 $rel_i\in{0,1}$。

Precision@K ​

$$ Precision@K = \frac{|R \cap D_K|}{K} $$

它回答:前 K 个结果里有多少是相关的。Precision 高意味着上下文噪声少,但不保证所有必需证据都找到了。

Recall@K ​

$$ Recall@K = \frac{|R \cap D_K|}{|R|} $$

它回答:所有相关证据中,前 K 找回了多少。多证据问题更应关注 Recall;单事实场景则常配合 Hit Rate。

Hit Rate@K ​

单个 query 的命中指示为:

$$ Hit@K(q)=\mathbb{1}[R_q \cap D_{q,K}\ne\varnothing] $$

数据集上的 Hit Rate 是所有 query 的 Hit@K 平均值。它只看是否至少命中一个正例,不关心命中几个,也不关心排在第 1 还是第 K。

MRR ​

令 $rank_q$ 为第一个相关结果的排名:

$$ RR(q)=\frac{1}{rank_q}, \qquad MRR=\frac{1}{|Q|}\sum_{q\in Q}RR(q) $$

没有命中时 RR 为 0。MRR 强调“第一个有用结果来得多早”,适合单答案、首个命中很重要的场景;它忽略第二个及后续相关结果。

MAP ​

在第 $k$ 位的精确率记为 $P@k$,query 的相关文档总数为 $|R|$:

$$ AP(q)=\frac{1}{|R|}\sum_{k=1}^{N}P@k\cdot rel_k $$

$$ MAP=\frac{1}{|Q|}\sum_{q\in Q}AP(q) $$

未召回的相关文档对分子贡献 0。MAP 同时奖励多个相关结果靠前,适合一个问题有多条等价或互补证据的场景。

DCG 与 nDCG ​

当相关性不是 0/1,而是 0、1、2、3 等分级标签时,可用:

$$ DCG@K=\sum_{i=1}^{K}\frac{2^{rel_i}-1}{\log_2(i+1)} $$

将结果按理想相关性排序得到 $IDCG@K$:

$$ nDCG@K=\frac{DCG@K}{IDCG@K} $$

nDCG 既考虑相关程度,又惩罚高相关结果排得太后。若 IDCG=0,说明该 query 没有标注相关结果,应单独处理,不能除以 0 后静默填 0。

一组完整算例 ​

设某问题共有 4 条二元相关文档,系统前 5 位的相关标记为:

text
排名:       1  2  3  4  5
是否相关:   1  0  1  0  1

于是:

text
Precision@5 = 3 / 5 = 0.60
Recall@5    = 3 / 4 = 0.75
Hit@5       = 1
RR          = 1 / 1 = 1.00
AP          = (1/1 + 2/3 + 3/5) / 4
            = 0.5667

注意 AP 分母是全部 4 条相关文档,漏掉的第 4 条贡献 0。如果错误地除以“已召回的 3 条”,会高估系统。

再看分级相关性。前 5 位标签为 [3, 0, 2, 0, 1],理想排序为 [3, 2, 1, 0, 0]:

text
DCG@5  = 7/log2(2) + 0 + 3/log2(4) + 0 + 1/log2(6)
       ≈ 8.8869

IDCG@5 = 7/log2(2) + 3/log2(3) + 1/log2(4)
       ≈ 9.3928

nDCG@5 ≈ 8.8869 / 9.3928 ≈ 0.9461

这个 nDCG 很高,因为最重要的 3 级证据已经排第一;第二重要的证据排第三只受到有限折损。

Python 实现 ​

python
from math import log2
from statistics import mean


def precision_at_k(binary_rels, k):
    top = binary_rels[:k]
    return sum(top) / k


def recall_at_k(binary_rels, total_relevant, k):
    if total_relevant == 0:
        return None  # 无正例 query 应单独统计,不要伪装成 0
    return sum(binary_rels[:k]) / total_relevant


def reciprocal_rank(binary_rels):
    for rank, rel in enumerate(binary_rels, start=1):
        if rel:
            return 1.0 / rank
    return 0.0


def average_precision(binary_rels, total_relevant):
    if total_relevant == 0:
        return None
    hits = 0
    score = 0.0
    for rank, rel in enumerate(binary_rels, start=1):
        if rel:
            hits += 1
            score += hits / rank
    return score / total_relevant


def dcg_at_k(graded_rels, k):
    return sum(
        (2 ** rel - 1) / log2(rank + 1)
        for rank, rel in enumerate(graded_rels[:k], start=1)
    )


def ndcg_at_k(graded_rels, k):
    actual = dcg_at_k(graded_rels, k)
    ideal = dcg_at_k(sorted(graded_rels, reverse=True), k)
    return actual / ideal if ideal else None


def mean_reciprocal_rank(all_binary_rels):
    return mean(reciprocal_rank(rels) for rels in all_binary_rels)

生产实现还要明确:同一文档的重复 chunk 算一个还是多个正例、多个等价证据如何标注、权限过滤后的相关集合是什么,以及查询无正例时是否从某项指标排除。

指标怎么选,不要全都堆上去 ​

任务主指标辅助指标原因
单事实 FAQHit Rate@K、MRRPrecision@K至少命中一条,并尽量靠前
多证据综合Recall@K、MAPContext Recall需要找全多个证据
分级相关排序nDCG@KRecall@K关键证据应优先于背景资料
小上下文预算Precision@K、nDCGtoken 数每个位置都昂贵,应减少噪声
reranker 比较MRR、nDCGP95 延迟、成本排名收益要覆盖精排代价

Recall@20 高而 Recall@5 低,说明召回器可能够用,问题更像排序或上下文预算;所有 K 都低,则应检查入库、query、filter 或召回器。

上下文层评估 ​

候选 top-20 不等于最终 context。去重、压缩、父块扩展和 token 截断都会改变证据集合,因此必须同时保存并评估最终送入 LLM 的 context。

Context Precision ​

直觉是:上下文中有用证据是否排在无用证据前面。不同框架实现可能采用排名加权或 LLM 判定,报告时必须写清版本和算法,不能把同名指标当作完全可比。

Context Recall ​

直觉是:参考答案所需事实是否能从最终上下文得到支持。它与检索 Recall@K 不同:前者通常在事实或 claim 层判断,后者在标注文档 id 层判断。

充分性与噪声敏感度 ​

应额外测试:

  • 删除一个必要证据后,模型是否错误地继续作答。
  • 加入多个 hard negatives 后,正确答案是否变化。
  • 新旧冲突版本同时出现时,是否使用适用版本。
  • 关键证据放在上下文不同位置时,表现是否稳定。
  • 压缩后数值、否定、主体、例外是否保留。

上下文有效密度可以作为工程观测:

$$ Evidence\ Density=\frac{被最终答案实际使用的证据 token}{上下文证据总 token} $$

它不是统一质量标准,但能发现“上下文越来越长,真正使用的内容没有增加”。

生成层:四个维度不要混成一个 ​

Faithfulness ​

Faithfulness 检查回答是否受检索上下文支持,不要求它与标准答案逐字相同。RAGAS 的官方定义 会先拆分回答 claim,再检查每个 claim 能否由上下文推出:

$$ Faithfulness=\frac{受上下文支持的回答 claims 数}{回答 claims 总数} $$

回答忠实不代表真实正确:如果上下文本身过期,模型忠实地复述旧规则,faithfulness 可以高而 correctness 低。

Correctness ​

Correctness 比较回答与经过确认的事实或参考答案。它应关注实体、数值、时间、否定、条件和关系,而不仅是语言相似度。

可把一个参考答案拆成加权 claims:

json
[
  {"claim": "生鲜不支持无理由退货", "weight": 0.7, "critical": true},
  {"claim": "普通商品期限为签收后 7 天", "weight": 0.3, "critical": false}
]

关键 claim 错误应触发硬失败,不能靠措辞流畅或次要事实正确把平均分拉回来。

Relevance ​

Answer relevance 判断回答是否回应用户问题。模型可能给出完全忠实的资料摘要,却没有回答“能不能退”。应检查是否直接给出所需结论、是否包含大量无关背景、是否遗漏用户限定。

Completeness ​

Completeness 检查参考答案中的必要信息覆盖了多少。对于“条件 + 例外 + 下一步”问题,只回答主规则是不完整,而未必是不忠实。

一个回答可能出现:

text
faithfulness = 高:说出的内容都有依据
correctness  = 部分正确:主规则正确
relevance    = 高:直接回应问题
completeness = 低:漏掉生鲜例外

这正是分维度评估的意义。

引用评估 ​

引用存在率只能证明答案里出现了 [1],不能证明 [1] 支持结论。

将答案拆成需要核验的 claims,建立 claim 到 evidence span 的映射:

$$ Citation\ Precision=\frac{真正支持相邻 claim 的引用数}{答案给出的引用总数} $$

$$ Citation\ Recall=\frac{获得充分引用支持的可核验 claims 数}{需要引用的 claims 总数} $$

ALCE 是面向带引用生成的端到端基准,强调不仅要检索证据,还要评价回答正确性与引用质量。

生产检查至少包括:

  • 引用 id 是否来自本次授权检索。
  • 引用 span 是否真的蕴含 claim。
  • 日期、数值、主体和例外是否完整覆盖。
  • 引用是否指向生成时使用的文档版本。
  • 用户点击引用能否回到正确页码或字符位置。
  • 一个引用支持多个 claim 时,位置是否造成歧义。

权限评估是硬门禁 ​

只检查最终回答是否泄露不够。无权文档一旦进入候选、日志、reranker 或模型上下文,就可能通过侧信道泄露。

分别统计:

text
Unauthorized Retrieval Rate
  = 无权候选数 / 全部候选数

Unauthorized Context Rate
  = 进入模型上下文的无权证据数 / 全部上下文证据数

Unauthorized Citation Rate
  = 指向无权来源的引用数 / 全部引用数

这三项的目标通常都是 0,而且不能用平均答案分抵消。

权限数据集要覆盖:

  • 不同 tenant 存在同名文档。
  • 部门继承、临时授权和授权撤销。
  • 用户有文档权限但无附件权限。
  • 对方无权限时,系统是否泄露文件标题或存在性。
  • ACL 更新后缓存与向量索引是否及时失效。
  • 日志和评估平台是否保存了未经脱敏的证据。

成本与延迟评估 ​

质量提升不是免费的。一次 RAG 请求的直接成本可拆成:

$$ C_{request}=C_{query\ embed}+C_{retrieval}+C_{rerank}+C_{judge/route}+C_{generation}+C_{observability} $$

生成模型的 token 成本还应区分普通输入、缓存输入和输出,具体单价以供应商当期定价为准:

$$ C_{llm}=\frac{T_{input}-T_{cached}}{10^6}P_{input} +\frac{T_{cached}}{10^6}P_{cached} +\frac{T_{output}}{10^6}P_{output} $$

不要把检索 top-k 增加带来的长上下文只记为“向量库成本”。它还会增加生成输入 token、首 token 延迟,可能触发额外压缩和 Judge 调用。

延迟应记录阶段分解与分位数:

text
T_total = T_route + T_embed + T_search + T_rerank
        + T_context + T_generation + T_postcheck

重点看 P50、P95、P99 和超时率。平均 800 ms 可能掩盖 P99 为 12 秒的问题。流式输出要同时记录 TTFT(首 token 时间)和完整答案时间。

建议每个实验报告同时给出:

text
质量:Recall@5、nDCG@5、Faithfulness、关键题通过率
体验:TTFT P50/P95、端到端 P50/P95
资源:平均输入/输出 token、每请求估算成本、索引存储
可靠性:超时率、降级率、空结果率

RAGAS、TruLens、DeepEval 怎么定位 ​

框架是执行与观测工具,不是业务真值来源。

RAGAS ​

RAGAS 当前官方指标目录 包含 Context Precision、Context Recall、Response Relevancy、Faithfulness、Noise Sensitivity 等 RAG 指标,也支持自定义指标和测试数据生成。

适合:快速建立 question/context/answer/reference 数据集上的离线评估,并使用现成的 RAG 指标范式。

注意:指标版本、Judge 模型、语言和 prompt 都会改变分数;某些指标需要 reference,某些不需要。报告中要固定这些配置。

TruLens ​

TruLens 的 RAG Triad 从三条关系观察系统:问题到上下文的 Context Relevance、上下文到回答的 Groundedness、问题到回答的 Answer Relevance。

适合:结合 tracing 观察一次调用记录,快速判断问题更像出在检索、grounding 还是回答切题性。

注意:Triad 不能替代版本正确性、权限、引用精度和业务关键条件等定制门禁。

DeepEval ​

DeepEval 的指标体系 支持端到端与组件级评估、RAG 指标、自定义 G-Eval/DAG 指标以及 CI 风格的阈值测试。

适合:把 LLM 应用评估组织成自动测试,为不同 test case 配置阈值,并与持续集成结合。

注意:官方也明确许多预置指标使用 LLM-as-a-Judge。阈值不是跨模型、跨任务通用的,需要用人工样本校准。

一个实际选择 ​

需求可优先考虑
快速跑标准 RAG 指标与合成测试集RAGAS
重视 tracing 和三段关系诊断TruLens
把评估写成测试并接 CIDeepEval
业务有严格规则、权限和版本语义自定义确定性指标 + 任一框架编排

不必强行三套全接。先明确数据结构、指标定义和门禁,框架只是承载方式。

LLM-as-Judge 的偏差与控制 ​

LLM Judge 很适合判断语义支持、完整性和回答质量,但它不是稳定、无偏的裁判。

常见偏差:

  • 位置偏差:成对比较时偏好先出现或后出现的答案。
  • 长度偏差:把更长、更像解释的答案误判为更好。
  • 自我偏好:Judge 可能偏好与自身表达方式相近的答案。
  • 措辞敏感:同义改写、顺序变化会导致评分波动。
  • 领域不足:不了解企业制度、法律或专业术语。
  • 证据污染:把候选回答里的指令当成评审要求。
  • 宽严漂移:模型版本或服务配置变化后阈值失效。

控制方法:

  1. 先用确定性规则判断 id、数值、格式、权限和版本,再让 Judge 处理语义。
  2. 给 Judge 明确 rubric 和正反例,要求按原子 claim 打分。
  3. 成对比较时交换 A/B 顺序;不一致样例标记为不稳定。
  4. 使用低随机性设置,并保存模型、版本、prompt 和参数。
  5. 对高风险样例使用双 Judge 或人工复核,但不要误以为多数投票自动等于真相。
  6. 用人工标注集计算一致率、混淆矩阵,并校准阈值。
  7. 将检索资料包在不可信数据区,禁止其修改评分规则。
  8. 定期抽查高分失败与低分成功样例,寻找系统性偏差。

Judge 分数应携带理由与 claim 级结果,但理由也可能编造,不能把一段顺畅解释当作评分正确的证明。

离线评估执行结构 ​

一条评估记录建议同时保存输入、每层 trace 和结果:

json
{
  "case_id": "return-017",
  "dataset_version": "2026-09-07",
  "query": "生鲜商品能否 7 天无理由退货?",
  "expected": {
    "evidence_groups": [["policy:v4:p2#fresh-food"]],
    "must_include": ["生鲜不支持无理由退货"],
    "status": "answer"
  },
  "trace": {
    "retrieved_ids": ["..."],
    "reranked_ids": ["..."],
    "context_ids": ["..."],
    "answer": "...",
    "citations": ["..."]
  },
  "metrics": {
    "hit_at_5": 1,
    "recall_at_5": 1.0,
    "faithfulness": 1.0,
    "critical_claim_pass": true,
    "acl_violation": false
  }
}

一个简化的执行器:

python
def evaluate_case(case, rag_app, evaluators):
    trace = rag_app.run_with_trace(
        question=case["query"],
        user_context=case["user_context"],
        as_of=case["as_of"],
    )

    scores = {}
    scores.update(evaluators.ingestion(case, trace))
    scores.update(evaluators.retrieval(case, trace))
    scores.update(evaluators.ranking(case, trace))
    scores.update(evaluators.context(case, trace))
    scores.update(evaluators.answer(case, trace))
    scores.update(evaluators.citation(case, trace))
    scores.update(evaluators.security(case, trace))
    scores.update(evaluators.cost_latency(case, trace))

    return {"case_id": case["case_id"], "trace": trace, "scores": scores}

评估环境要固定知识库快照、embedding 模型、索引参数、reranker、生成模型、prompt、随机性和并发配置。否则两次结果差异无法归因。

Ablation:确认提升到底来自哪里 ​

Ablation 是一次只移除或改变一个组件,观察质量、成本和延迟变化。

例如以 dense + BM25 + rerank + compression 为完整系统:

实验改动要回答的问题
A完整系统当前基线是什么
B去掉 BM25精确词命中是否下降
C去掉 reranker排序收益值不值得延迟
D去掉 compression压缩是否保真,是否只是省 token
Etop-k 从 20 改 50更高召回是否被噪声抵消
F关闭 query rewrite改写是否帮助口语问题、伤害精确版本

每组应使用同一数据快照和配置,只改一个因素。若同时换 embedding、chunk 和 prompt,即使总分上涨,也无法知道哪个变化有效。

报告不要只写平均分:

text
总体变化:Recall@5 +x,P95 +y ms
切片变化:口语查询改善,版本号查询退化
关键失败:2 条权限样例失败
结论:即使平均质量上升,也不允许上线

x/y 填入实验测得的变化值。

回归门禁:平均分不能掩盖事故 ​

门禁分三类。

硬门禁 ​

  • 任意越权证据进入上下文。
  • 关键制度问题生成错误金额、日期或否定结论。
  • 无答案问题编造确定答案。
  • 引用不存在的 evidence id。
  • 恶意文档触发副作用工具。

这类失败通常要求数量为 0。

统计门禁 ​

  • Recall@K、nDCG、Faithfulness 不低于已批准基线的容忍区间。
  • P95 延迟和平均成本不超过预算。
  • 各业务切片均达到最低样本数与阈值。

小数据集上的 0.02 波动可能只是随机性。可以使用 bootstrap 置信区间或配对检验,不要只比较两位小数。

人工复核门禁 ​

用于 Judge 不稳定、业务高风险、冲突无法自动裁决或新题型覆盖不足的样例。人工复核不应替代自动 trace,它负责处理自动指标无法可靠决定的部分。

线上反馈与离线指标如何连接 ​

线上可观察:

  • 用户点赞、点踩与原因标签。
  • 用户是否立即改写同一问题。
  • 是否点击引用,点击后是否返回继续追问。
  • 是否转人工、复制答案或完成下游任务。
  • 空结果率、拒答率、二次检索率。
  • 延迟、取消率、错误率和单请求成本。

这些都不是天然真值。用户可能给错误答案点赞,也可能因为正确拒答而点踩。引用点击率低可能是答案已经够清楚,也可能是链接不可用。

更稳妥的闭环:

text
线上信号发现问题
  -> 抽样回放完整 trace
  -> 领域专家确认错误与原因
  -> 加入 challenge set
  -> 离线复现并修复
  -> 通过门禁后灰度上线
  -> 比较同一用户与问题切片

A/B 测试时要防止同一用户跨组污染、知识库版本不同和流量切片不均。高风险场景不应只凭点击率决定上线。

逐层错误归因 ​

从最早可证伪的环节开始,不要先改 prompt。

典型信号:

现象优先检查
Recall@20 低解析、query、filter、embedding、索引
Recall@20 高但 Recall@5 低融合和 reranker
Context Recall 低但候选 Recall 高token 预算、去重、压缩、父块选择
Faithfulness 低但 context 充分prompt、模型、生成后校验
Faithfulness 高但 correctness 低过期/错误知识、版本裁决、golden
Correctness 高但 citation precision 低引用生成与 claim 对齐
离线好、线上差数据分布、权限、会话上下文、并发或知识新鲜度

一次完整评估案例 ​

以包含 20 条问题的评估集为例,比较两套检索配置。

目标与配置 ​

text
知识库:售后政策快照 v4
问题:20 条
切片:10 条常规、4 条精确 SKU、3 条无答案、2 条版本冲突、1 条权限
基线:dense top-20 -> rerank top-5 -> 生成
候选:dense + BM25 top-30 -> rerank top-5 -> claim 引用校验

分层结果 ​

假设得到以下评估结果:

指标基线候选解释
Recall@200.820.92混合召回帮助精确 SKU
nDCG@50.740.84rerank 输入更完整
Context Recall0.780.88更多必要证据进入最终上下文
Faithfulness0.900.94claim 校验减少无支持结论
关键题通过率0.850.90总体提升,但还要看硬失败
Citation Precision0.760.93无效引用减少
P95 延迟1.8 s2.4 s多路召回与校验增加延迟

不能直接上线的原因 ​

逐条看失败样例发现:唯一的权限题中,候选系统在 BM25 分支先召回了无权文档,虽然最终 context 过滤掉了它。最终答案没有泄露,但 Unauthorized Retrieval Rate > 0,说明安全边界放得太晚。

结论应是:

text
候选系统在大多数质量指标上改善,但违反权限硬门禁,暂不上线。
先将 ACL 过滤下推到 BM25 与 dense 两个召回分支,再复跑全部权限和普通回归集。

修复后的归因验证 ​

修复 ACL 后,先验证:

  1. 两个召回分支都只产生授权候选。
  2. 缓存 key 包含权限版本,撤权后不复用旧结果。
  3. reranker、日志和 Judge 从未接触无权正文。
  4. 普通用户与管理员的合法召回结果分别保持预期。
  5. 再比较质量、P95 与成本,确认过滤下推没有意外漏召回。

这个案例说明,评估的目的不是宣布“候选总分更高”,而是做出可解释的发布决定。

生产评估清单 ​

一次可以复现的评估报告至少写明:

  • 数据集版本、构造方式、规模、切片和时间范围。
  • 知识库快照、文档版本和 ACL 快照。
  • chunk、embedding、索引、query rewrite、融合与 rerank 配置。
  • 生成模型、prompt 版本、温度、输出约束。
  • Judge 模型、rubric、框架和指标版本。
  • 每层指标、置信区间、失败样例与错误分类。
  • P50/P95/P99 延迟、token、费用估算和错误率。
  • 硬门禁结果、回滚条件和已知盲区。

评估原始记录应可回放,但要遵守数据最小化。用户问题、检索正文和 Judge 输入可能含敏感信息,不能为了可观测性无期限保存全量明文。

常见误区 ​

用一个综合分数排名所有方案 ​

加权总分会掩盖越权、关键事实错误和无答案幻觉。总分可用于粗看,但硬门禁必须单独展示。

把 Recall@K 当答案质量 ​

正确证据被召回只是必要条件。重排、压缩、生成和引用仍可能失败。

用最终答案反推召回质量 ​

模型可能凭参数知识答对,掩盖检索失败;也可能因生成问题答错,冤枉检索器。必须直接评估 evidence id。

用随机负例让指标很好看 ​

随机无关文档太容易区分。应加入同主题不同版本、相反结论和关键词高度重合的 hard negatives。

LLM Judge 分数保留三位小数就显得精确 ​

小数位不代表测量稳定。应报告 Judge 配置、重复运行波动、与人工的一致性及置信区间。

只测平均延迟 ​

尾延迟决定用户是否取消请求。至少看 P50/P95/P99、TTFT 和超时率,并按是否触发重试、压缩、Judge 分组。

评估集永不更新 ​

知识、用户表达和攻击方式都在变化。线上失败应进入 challenge set,旧制度样例要绑定历史快照或更新标签。

面试中如何回答“你怎么评估 RAG” ​

一个有说服力的回答应沿着故障链路展开:

text
先固定知识库快照和 golden dataset
-> 入库层测解析、chunk、metadata 与删除传播
-> 召回层用 Recall/Hit Rate,排序层用 MRR/MAP/nDCG
-> 最终 context 测相关性、充分性、噪声和版本适用
-> 生成层拆 faithfulness/correctness/relevance/completeness
-> claim 级验证 citation precision/recall
-> 权限和注入设硬门禁
-> 同时报告成本与 P95
-> 用 ablation 证明组件贡献,用 CI 和线上反馈形成回归闭环

如果面试官继续追问,就拿一个失败答案从原文、召回、上下文一路定位到生成,而不是只列工具名。

先看一个小场景 ​

测试集里有一道题:“生鲜签收后能否七天无理由退货?”答案错了,可能是 OCR 漏行、chunk 拆开、reranker 排错,也可能是模型忽略了已召回的例外。只记录最终得分看不出原因;把每一层的中间结果留下,回归测试才知道下一版到底修好了哪一步。

读完后可以故意制造一次 OCR 漏行,再沿原文、召回、上下文和生成逐层排查,确认评测能指出具体故障位置。

持续学习,持续实践。