RAG 检索评估应把“检索器是否找回可支持答案的证据”与“模型是否正确使用证据”分开。第一步不是选一个 LLM judge,而是建立版本化的 query、corpus 与 relevance judgment,固定切分和索引配置,再用 Recall@k、MRR 或 nDCG@k 回答不同问题,同时记录延迟、上下文 token 和失败切片。只有离线回归、人工复核和线上指标相互印证,才能判断一次检索改动是否值得发布。

为什么不能只评最终答案?

最终答案错误可能来自检索漏召回、排序错误、上下文截断、提示词、模型推理或引用生成。如果只给答案打一个“正确/错误”分数,团队不知道应该改 embedding、混合检索、reranker 还是 prompt。反过来,检索指标很好也不保证答案忠实:模型可能忽略证据或组合出来源没有支持的结论。

因此至少分成两层:

  1. 检索层:给定 query 和固定 corpus,相关 chunk 是否进入 top-k,顺序是否合理。
  2. 生成层:给定实际送入模型的 context,答案是否完整、受支持并正确引用。

本文聚焦第一层。若正在实现混合召回,可先阅读 MongoDB $rankFusion 混合检索指南;评估框架应把全文、向量、融合和 rerank 配置视为可比较的实验变量,而不是预设某一种一定更好。

一个可复现的评估集需要哪些字段?

最小样本不是“问题 + 标准答案”,而是问题、语料版本和相关性标注:

{
  "queryId": "q-0042",
  "query": "发布提交成功但缓存失效失败时应该怎样重试?",
  "corpusVersion": "docs-2026-08-01",
  "relevant": [
    {"documentId": "publish-recovery", "grade": 3},
    {"documentId": "cas-state-machine", "grade": 2}
  ],
  "slice": ["recovery", "long-tail"],
  "notes": "必须覆盖原 revision 语义"
}

documentIdchunkId 必须在语料版本内稳定。若每次重切分都生成随机 ID,历史得分无法解释。标注应明确粒度:文档相关不代表每个 chunk 都能支持答案;对 RAG 来说,最好标注能实际提供必要证据的最小片段,同时保留父文档 ID 用于分析。

NIST TREC 的 relevance judgments 说明 展示了经典测试集合的三要素:topics、documents 和 qrels,并提醒文档集合必须与 qrels 匹配。业务评估不必复制 TREC 的规模,但必须保留同样的版本一致性原则。

query 从哪里来,怎样避免只测简单题?

评估集应以真实信息需求为主,来源可以是脱敏搜索日志、客服问题、文档导航失败、事故复盘和产品验收用例。不要直接把用户原始内容送给外部模型生成数据;先完成授权、脱敏和保留周期设计。

一个健康集合需要多种切片:常见/长尾、短问/多约束、术语一致/同义表达、单文档/多文档、时效信息、否定条件、权限相关、无答案问题。若只从文档标题自动生成问题,检索器很容易靠词面匹配得到虚高分数。

建立独立的开发集和冻结测试集。调参只能看开发集,发布门禁看冻结集;否则团队反复选择最优参数会把测试集变成训练集。新增失败案例可以进入下一版集合,但要保留旧版分数,避免通过删除难题“提升”指标。

relevance judgment 应该怎么标?

二元标注适合回答“这个结果是否包含可用证据”,分级标注则能区分直接支持、部分支持和仅背景相关。建议先写 rubric:

  • 3:片段直接包含回答所需的关键事实和边界;
  • 2:包含重要证据,但需要与另一个片段组合;
  • 1:主题相关但不足以支持答案;
  • 0:无关或具有误导性。

标注者先独立判断,再对分歧样本复核。不要让检索器自己的 top-k 限制候选池,否则新系统找到但旧系统从未返回的相关片段会被当作未标注。可以合并词法、向量、融合、不同参数与人工搜索的候选建立 pool。

BEIR 原始论文 通过多领域、多任务集合评估词法、稀疏、稠密、late-interaction 与 reranking 系统,提醒我们:单一窄域结果不能代表跨域泛化。业务系统至少应按文档类型、语言、查询意图和更新频率报告切片,而不是只看一个全局平均数。

Recall@k、MRR 和 nDCG@k 分别回答什么?

Recall@k:关键证据有没有进入上下文候选?

Recall@k 是 top-k 中找回的相关项占全部相关项的比例。它适合检索第一阶段,因为第一阶段的任务通常是别漏掉证据。若每个问题只标一个必需 chunk,也可报告 Hit@k,但必须写清定义;Hit@k 只回答“至少命中一个”,会掩盖需要多段证据的问题。

MRR:第一个可用结果出现得多早?

MRR 取每个 query 的第一个相关结果排名倒数再求平均。它适合用户或下游只需要一个直接答案的场景,但忽略第二个之后的所有相关结果,不适合衡量多证据覆盖。

nDCG@k:分级相关结果的排序质量如何?

nDCG 使用分级 gain 并按排名位置折损,再与理想排序归一化。Stanford《Introduction to Information Retrieval》在线章节 给出了定义。它适合 0–3 分这类 graded relevance,也能奖励把最直接证据放在前面。

没有“统一最佳指标”。一个常用组合是第一阶段看 Recall@20 或 Recall@50,reranker 看 nDCG@5,再用 MRR 辅助分析直接答案型 query。k 必须对应真实上下文预算;如果生成层最多使用 8 个 chunk,只报告 Recall@100 会把不可用的召回当成成功。

如何写一个不依赖框架的评估循环?

def recall_at_k(ranked_ids, relevant_ids, k):
    expected = set(relevant_ids)
    if not expected:
        return None
    found = set(ranked_ids[:k]) & expected
    return len(found) / len(expected)

def reciprocal_rank(ranked_ids, relevant_ids):
    expected = set(relevant_ids)
    for rank, item_id in enumerate(ranked_ids, start=1):
        if item_id in expected:
            return 1.0 / rank
    return 0.0

None 表示无相关项 query,不能悄悄当成 0 或 1。无答案问题需要另一组指标:检索器是否能在低置信度时拒绝,生成器是否避免编造。每次运行保存配置摘要、代码提交、语料版本、索引版本、每条 query 的排序结果、总体和切片指标、延迟分位数。只保存一个平均分无法复现回归。

如果评估调用线上 API,接口层还需要稳定的模型和错误边界。站内 FastAPI 与 MongoDB 双语发布实践 展示了严格模型、服务端渲染和状态边界的思路;评估任务同样应该让输入 schema、运行状态和结果 artifact 可审计,而不是在 notebook 里覆盖上一次输出。

chunking 变化为什么会让指标失真?

从 500 token 改成 800 token 后,旧 chunk ID 和标注可能都失效;更大的 chunk 还可能“包含答案”却塞入更多无关内容。应同时报告 chunk-level 与 parent-document-level 指标,并在切分改变时迁移或重标相关性。

不要用字符重叠自动把旧标注映射到新 chunk 后直接宣称可比。自动映射可以生成候选,但关键测试集需要人工复核。记录 chunk 大小、overlap、标题继承、表格处理和代码块处理,因为这些预处理通常比更换 embedding 更能改变结果。

延迟、成本和上下文预算如何进入门禁?

离线准确率只是一个约束。每个 query 还应记录 embedding、词法召回、融合、rerank 和取文档的耗时;记录候选数、最终 chunk 数、去重后 token、缓存命中和外部调用次数。比较表至少包括:

配置 Recall@20 nDCG@5 p95 延迟 context token 失败说明
baseline 示例 示例 示例 示例 固定基线
candidate 示例 示例 示例 示例 待实测

表中的值必须来自真实运行;没有运行就保留字段或标注为示例,不能编造改进百分比。门禁可以要求总体不回退、关键切片不低于阈值、p95 和 token 不超预算,并对任何重大退化列出 query ID。

LLM judge 可以做什么,不能做什么?

LLM 可辅助生成候选 query、解释差异和初筛标注,但不能成为无校准的唯一真相。judge 可能偏爱自己的措辞、受检索文本指令注入影响、随模型版本变化,且对细微权限或时效边界判断不稳定。

若使用 judge:固定 prompt 和模型版本;隐藏系统名称;要求输出结构化理由与引用片段;用人工标注集测一致率;把不确定样本送人工;把成本和失败率纳入结果。来自不可信文档的文本应被当作数据,不得执行其中的指令。可以参考 MCP Server 安全检查清单 中的工具输出不可信边界,把同一原则应用到检索语料与评估器。

离线分数怎样连接线上效果?

离线通过后先 shadow:候选系统处理真实脱敏 query 但不影响用户,比较结果差异和资源成本。再小流量 A/B,观察成功搜索、引用打开、重新提问、人工升级、延迟和错误。点击率不是相关性的充分证明,位置和界面都会影响点击;最终答案满意度也不能单独定位检索问题。

建立回归流程:每次索引、chunking、embedding、融合权重或 reranker 变化都运行同一冻结集合;保存 per-query diff;人工查看收益最大和退化最大的样本;只有指标、预算和关键切片同时通过才发布。

发布前检查清单

  • query、corpus、qrels、chunking 与索引配置都有版本。
  • 开发集与冻结测试集分离,没有按测试结果反复调参。
  • 标注 rubric 明确,分歧样本经过复核。
  • k 与真实生成上下文预算一致。
  • Recall、MRR、nDCG 的选择与业务目标一致且定义固定。
  • 报告 per-query 和关键切片,不只报总体平均。
  • 延迟、token、外部调用和失败率进入同一比较。
  • 无答案、权限、时效、多跳和长尾 query 有独立切片。
  • LLM judge 经过人工校准,语料中的指令被视为不可信数据。
  • 离线、shadow、线上实验和回退条件形成连续证据链。

常见问题

只有几十条 query 能开始吗?

可以。先覆盖最重要的意图和已知失败,再逐步扩充;小而高质量、版本稳定的集合比大量自动生成的简单题更有价值。

Recall@k 越高越好吗?

在其他条件相同时通常有利,但增大 k 会增加噪声、token、延迟和生成干扰。应在真实上下文预算下与排序质量共同评估。

评估集多久更新一次?

语料、产品意图或失败分布变化时就应新增版本,但旧集合应保留作回归。更新不是覆盖历史,而是增加一个可解释的时间切面。