RAG 评估与问题诊断RAG 评估与问题诊断
分层评估召回、排序、上下文、忠实度与安全,掌握 Recall@K、MRR、nDCG 和 LLM-as-judge 的工程边界。分层评估召回、排序、上下文、忠实度与安全,掌握 Recall@K、MRR、nDCG 和 LLM-as-judge 的工程边界。
专题导读
RAG 的“回答不好”至少可能来自五类故障:证据未入库、检索未召回、排序或装配丢失、模型没有忠实使用证据、最终答案不满足用户任务。只给最终答案打一个总分,既无法定位,也容易把生成模型的语言流畅度误当成检索质量。
评估应建立可重放的分层协议:固定问题、相关证据、期望答案或断言、权限上下文和完整版本快照;分别度量初始召回、融合、重排、最终上下文、生成结果与安全边界。线上点击和点赞可补充真实行为信号,但不能替代事实、权限与忠实度审计。
知识地图
评测样本:问题 + 权限 + 时间点 + 相关证据 + 答案/断言 + 期望行为
└─ 索引快照:parser / chunker / embedding / indexVersion
└─ 初始召回:Hit@K / Recall@K / Precision@K
└─ 融合与重排:MRR / MAP / nDCG@K
└─ 最终上下文:证据覆盖 / context precision / 引用映射
└─ 生成:faithfulness / relevance / completeness / correctness
└─ 安全与产品:ACL 泄漏 / 注入 / 拒答 / 延迟 / 成本
└─ 门禁:切片、成对比较、置信区间、人工复核
评估不是一次性跑分,而是版本管理、失败归因和发布决策系统。
面试题
Q1:为什么 RAG 必须分层评估?
核心回答: 最终回答是索引、检索、装配和生成共同作用的结果。一个总分不能说明正确证据在哪一步丢失,也不能给出下一步优化方向。
推荐至少保存四个候选快照:
- 初始召回:BM25、向量等各路 top-K;
- 融合后:RRF 或分数融合结果;
- 重排后:reranker 的候选与名次;
- 最终上下文:真正进入 prompt 的 chunk。
若初始 Recall@K 低,应检查解析、chunking、query、embedding 或过滤;初始高而重排后低,应检查 reranker;重排高而最终上下文缺证据,应检查去重、parent 扩展和 token 预算;上下文完整而答案出现无依据断言,才主要是生成忠实度问题。
此外,安全和成本是独立维度。回答看似正确但使用无权文档,属于严重失败;质量提高 0.2% 却使 P99 和成本翻倍,也未必可上线。
面试追问:可以只测端到端正确率吗?
可以作为最终产品指标,但不能作为唯一指标。端到端指标负责验收,分层指标负责归因,两者都需要。
常见错误回答: “让另一个大模型给答案打分即可。”这既没有检索诊断,也没有权限、版本和裁判可靠性保证。
Q2:如何构建可靠的 RAG 评测集?
核心回答: 每条样本至少包含问题、时间点、身份/租户、相关证据集合或判定规则、期望答案要点、可接受变体、是否应拒答,以及数据与系统版本。样本分布要贴近真实流量,同时覆盖风险边界和长尾。
来源可包括脱敏线上问题、客服/运维案例、领域专家编写、日志失败回放和受控合成。合成样本适合扩充格式和边界,但会继承生成模型偏差,不能完全替代真实问题与专家标注。相关性标注应说明粒度:相关的是文档、段落还是 chunk;是任一权威证据足够,还是多条证据都必须命中。
评测集应按高频、长尾、无答案、歧义、多跳、最新内容、精确标识符、多语言、权限边界和提示注入切片。开发集用于调参,隔离 holdout 用于最终报告;长期反复观察 holdout 也会产生过拟合,应定期轮换或增加盲测。
面试追问:文档更新后金标怎么办?
金标必须带有效时间和源修订。更新后重标、迁移到新证据 ID,或将旧样本标为失效;不能用过期答案惩罚正确的新系统。
常见错误回答: “从文档随机生成问题就代表真实用户。”随机生成通常偏向文档措辞、单跳问题和易检索片段,无法代表真实表达与失败分布。
Q3:Recall@K 与 Hit@K 有什么区别?
核心回答: 对问题 q,设所有相关证据集合为 Rel(q),前 K 个结果集合为 TopK(q):
Recall@K(q) = |Rel(q) ∩ TopK(q)| / |Rel(q)|
它衡量相关证据被覆盖的比例。若只判断前 K 是否至少命中一条相关证据,则是 Hit@K(也常称 Success@K):
Hit@K(q) = 1,若 Rel(q) ∩ TopK(q) ≠ ∅;否则为 0
例如一个问题有 4 条必要相关证据,top-5 命中 1 条:Recall@5 是 1/4=0.25,Hit@5 是 1。把“至少命中一条”叫 Recall@K 会掩盖多证据问题的覆盖不足。
当问题只需要任一等价权威证据时,Hit@K 更贴合成功定义;当回答必须组合多个事实时,应使用 Recall@K 或额外定义 all-required coverage。前提是 Rel(q) 尽可能完整,否则未标注但实际相关的结果会被误判。
面试追问:Recall@K 高是否表示排序好?
不表示。所有相关证据都可能挤在 K 的尾部。需要结合 MRR、nDCG 或其他排序指标。
常见错误回答: “Recall@K 就是 top-K 里有没有正确答案。”这实际描述的是 Hit@K。
Q4:Precision@K、Context Precision 与召回如何权衡?
核心回答: Precision@K = |Rel ∩ TopK| / K,表示前 K 中相关结果的比例。Recall 关注“别漏”,Precision 关注“别带太多噪声”。扩大 K 往往提高或不降低 Recall,却可能降低 Precision、增加 rerank 与生成成本。
在 RAG 中应区分检索候选的 Precision@K 与最终上下文的 context precision。候选阶段允许较宽,以给 reranker 留空间;最终上下文预算有限,应更强调必要证据密度。若相关性是分级的,简单二元 Precision 会损失“高度相关”和“略有帮助”的差异,可配合 nDCG。
Precision@K 的分母通常固定为 K,但当系统实际返回少于 K 条时,协议必须说明按 K 还是实际返回数计算。选择不同会影响无结果查询的解释,团队应固定实现并在报告中注明。
面试追问:为什么不能只追求 Recall@100?
Recall@100 只说明证据在大候选池中,可能完全进不了 rerank 或 prompt;还会掩盖排序差与高成本。
常见错误回答: “上下文越多,模型越容易找到答案。”噪声、冲突和长上下文会降低利用率并增加延迟。
Q5:MRR 衡量什么?它不适合哪些场景?
核心回答: MRR(Mean Reciprocal Rank)关注每个问题第一个相关结果的位置。对 N 个问题:
MRR = (1/N) × Σ 1/rankᵢ
若某问题没有相关结果,倒数排名记为 0。第一个相关结果在第 1、2、10 位时,贡献分别为 1、0.5、0.1。
MRR 适合“找到第一条正确答案就够”的导航、FAQ 或单答案检索。它完全忽略第一个相关结果之后的其他相关证据:对需要多个证据的比较、多跳或综述问题,MRR 不能反映覆盖和整体排序,应结合 Recall@K、MAP 或 nDCG。
MRR 还依赖二元相关性。如果第 1 位“略相关”、第 2 位“高度相关”,MRR 可能只看第 1 位;此时分级相关性的 nDCG 更合适。
面试追问:MRR 从 0.5 提升到 0.55 是否显著?
不能只看均值。应做逐问题成对比较,用 bootstrap 置信区间或合适的配对检验,并查看关键切片和回归样本。
常见错误回答: “MRR 会评估所有相关文档的平均排名。”它只使用第一个相关结果的排名。
Q6:nDCG@K 如何计算?为什么需要分级相关性?
核心回答: nDCG 用位置折损衡量前 K 个结果的分级相关性,并除以理想排序归一化。常见定义为:
DCG@K = Σᵢ₌₁ᴷ (2^relᵢ - 1) / log₂(i + 1)
nDCG@K = DCG@K / IDCG@K
relᵢ 是第 i 个结果的相关等级,IDCG@K 是把同一批相关等级按最优顺序排列得到的 DCG。若 IDCG@K=0,该问题没有已标注相关结果;应在协议中明确记 0、排除或单独归类,不能静默选择。
指数增益让高相关等级更有价值,对数折损让头部错误代价更大。例如相关等级 [3,0,2] 与 [0,3,2] 命中集合相同、Recall 相同,但前者把高度相关证据放在首位,nDCG 更高。
相关等级必须有标注规范,例如 3=直接完整支持,2=支持关键部分,1=背景相关,0=无关。若标注员对等级理解不一致,精密公式也不会产生可靠结论。
面试追问:为什么 nDCG 通常在 0 到 1?
在非负相关等级、按同一候选相关性构造理想排序的标准设定下,DCG 不超过 IDCG。自定义负增益或不一致 IDCG 会破坏该性质。
常见错误回答: “nDCG 就是给排名靠前的结果更大权重。”这只说了折损,漏掉分级增益和理想排序归一化。
Q7:如何判断问题出在召回、融合、重排还是上下文装配?
核心回答: 对同一金标证据,在每个阶段计算位置和留存状态,并建立淘汰漏斗。至少记录:retrievedBy、原始 rank/score、融合 rank、rerank rank、是否扩展/去重、是否进入 prompt、丢弃原因。
典型诊断矩阵:
| 现象 | 优先检查 |
|---|---|
| 各路 Recall@K 都低 | 解析、chunking、query、embedding、analyzer、ACL 误过滤 |
| 单路高、融合后低 | RRF 身份映射、每路 K、权重/常数、重复候选 |
| 融合高、rerank 后低 | reranker 输入截断、模型版本、文本拼接、超时降级 |
| rerank 高、最终上下文低 | parent 扩展、去重误杀、来源配额、token 预算 |
| 上下文证据完整、答案错 | 生成指令、faithfulness、推理、答案格式或模型能力 |
评估日志必须版本化,否则候选模型和基线可能读取不同索引或 ACL 快照,比较失效。还要区分“相关证据被安全过滤”与“ACL 规则错误”:前者是正确行为,后者才是召回缺陷。
面试追问:只存最终 prompt 能诊断吗?
只能判断模型看到了什么,无法知道正确 chunk 在上游何时、为何丢失。
常见错误回答: “Recall 低就调大 top-K。”这可能掩盖解析缺失、版本错误或 ACL 误配置,并增加成本。
Q8:Faithfulness(忠实度)准确衡量什么,不衡量什么?
核心回答: Faithfulness 衡量回答中的原子断言是否能由给定上下文支持。它不证明上下文本身是真实或权威的,也不等于答案相关、完整或符合用户任务。
一种可解释方法是先把回答拆成可验证原子断言,再逐条标注为 supported、contradicted、not-enough-information,最后计算支持断言比例。协议必须说明不同断言是否加权,以及引用只支持部分复合句时如何处理。
边界示例:上下文错误地写“Java 21 移除了 G1”,回答照抄该句,faithfulness 可能很高,但事实正确性很低;上下文正确介绍 G1,回答只复述无关背景,faithfulness 高但 answer relevance 低;回答遗漏关键限制,忠实但不完整。
因此至少分开报告:
- Faithfulness/groundedness:断言是否有上下文依据;
- Correctness:是否符合可信金标或现实事实;
- Relevance:是否回答用户问题;
- Completeness:必要要点是否覆盖;
- Citation correctness:引用是否真的支持对应断言。
面试追问:模型没有引用的常识该算不忠实吗?
取决于产品契约。严格闭卷 RAG 应要求所有外部可验证断言有证据;允许常识的产品需明确豁免范围,不能在评分时临时决定。
常见错误回答: “Faithfulness 高就说明答案正确。”它只说明答案与给定上下文一致。
Q9:引用质量应该如何评估?
核心回答: 既评估引用与断言的对应关系,也评估应引用断言是否被覆盖。可以定义 citation precision 为“给出的引用中,真正支持相邻断言的比例”,citation recall 为“需要证据的断言中,有有效支持引用的比例”。
引用 URL 可打开不代表支持答案。评估应落到实际 sourceId/chunkId/sourceRevision,核对引用文本是否进入最终上下文、是否支持对应断言、是否来自用户有权访问且在该时间点有效的版本。多个引用堆在段尾会产生归属歧义,最好让服务端保存 assertion-to-source 映射。
引用评估还要识别:来源存在但内容不支持、只支持复合断言的一部分、引用过期版本、模型编造 sourceId、以及引用了召回但未进入 prompt 的文档。
面试追问:点击率能代表引用正确吗?
不能。点击受位置、标题和用户习惯影响,最多是行为信号;正确性仍需内容核验或人工抽样。
常见错误回答: “回答末尾列出三个真实链接就算有引用。”真实链接不等于与具体断言存在支持关系。
Q10:LLM-as-judge 适合做什么?边界和偏差是什么?
核心回答: LLM-as-judge 适合大规模初筛、开放文本 rubric 评分和候选间成对比较,但它是有误差、会漂移的测量工具,不是客观事实裁判。高风险结论必须用人工金标、规则或可执行验证校准。
主要风险包括:
- 位置偏差:成对比较偏好先出现或后出现的候选;
- 冗长/风格偏差:把长、流畅、自信误判为更正确;
- 自家模型偏差:偏好与裁判相似的表述或模型族;
- 提示敏感:rubric、示例与字段顺序改变结果;
- 非确定性:同一样本重复评分不一致;
- 知识与泄漏:裁判可能用自身先验而不是给定上下文,或评测样本进入训练数据;
- 注入风险:候选答案或检索文档中的文本可能试图操纵裁判。
工程协议应固定 judgeModel/judgePrompt/rubric 版本,候选匿名化并随机交换顺序,要求结构化理由与逐项评分,重复抽样估计一致性,定期与人工标注计算一致率、混淆矩阵或相关性。分歧、高风险和边界样本升级给人审。
面试追问:裁判模型比被测模型更大就可靠吗?
不足以保证。更大模型仍有偏差和知识盲区;可靠性来自明确 rubric、校准集、盲测、一致性监控与人工复核。
常见错误回答: “temperature 设为 0 就完全可复现、无偏。”供应商实现和模型版本仍可能变化,确定性也不消除系统偏差。
Q11:离线发布门禁怎样避免被平均分误导?
核心回答: 对同一问题做基线与候选的成对比较,报告总体指标、关键切片、差值分布、回归案例和不确定性。平均值通过不代表每个高风险切片通过。
门禁可包括:核心 Recall@K/nDCG 不退化、无答案与拒答达到阈值、ACL 泄漏为零容忍、faithfulness 和引用指标达标、P95/P99 与单位请求成本不超预算。具体阈值来自业务风险与历史基线,不能照搬通用数字。
Bootstrap 可从问题集合有放回采样,多次计算“候选减基线”的指标差,得到经验置信区间。它不修复有偏样本,也不能把不显著解释为两者等价;样本独立性、分层流量和多重比较都需考虑。小样本或高风险发布应结合专家审查和线上渐进实验。
每次运行固定并保存:数据快照、索引/embedding/retriever/reranker/prompt/生成模型/judge 版本与参数。否则差异无法归因。
面试追问:候选总体 nDCG 提升,但无答案幻觉率上升怎么办?
若无答案是硬门禁,则拒绝发布或仅对安全切片灰度。不能用平均排序收益抵消关键安全回归。
常见错误回答: “新版本总分更高就全量上线。”聚合分会隐藏权限、语言、租户和长尾回归。
Q12:线上应该监控什么?如何把线上信号反馈到离线评测?
核心回答: 线上同时监控质量代理、系统指标、安全事件和版本分布,并把失败样本经脱敏和审核后沉淀为新的评测切片。
系统指标包括各阶段 P50/P95/P99、超时、降级率、候选数量、token、embedding/rerank/生成成本、缓存命中和拒答率。质量代理可包括来源点击、追问/改写、人工纠错、会话放弃和用户反馈,但这些都可能有选择偏差,不能直接当正确率。
安全指标包括无权候选、跨租户缓存命中、提示注入检测与工具越权尝试;关键是验证无权正文不出现在召回结果、rerank 输入、trace、缓存、最终 prompt 和引用任一层。日志与 trace 本身也要最小化敏感信息。
发现线上失败后保存可重放包:脱敏 query、身份类别、时间、各阶段候选 ID/名次、版本快照、最终证据和输出。经领域专家标注后加入开发集;有代表性的重大事故样本进入长期回归集,避免只修单个提示词。
面试追问:A/B 测试能替代离线评测吗?
不能。A/B 适合产品行为差异,但低频安全事故和事实错误可能难以从点击观察;上线前仍需离线门禁,上线后渐进放量和审计。
常见错误回答: “用户点赞率高就说明 RAG 正确。”用户可能偏好流畅错误答案,且只有部分用户会反馈。
Java 21 完整示例:实现 Recall@K、MRR 与 nDCG@K
下面示例使用 Java 21 标准库,实现单问题指标与宏平均。相关等级 grade > 0 视为 Recall/MRR 的相关;nDCG 保留等级差异。生产评测还应校验重复结果、样本版本和标注完整性。
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.ToDoubleFunction;
public final class RagMetricsExample {
public record EvaluationCase(
String queryId,
List<String> rankedChunkIds,
Map<String, Integer> relevanceGrades) {
public EvaluationCase {
Objects.requireNonNull(queryId);
rankedChunkIds = List.copyOf(rankedChunkIds);
relevanceGrades = Map.copyOf(relevanceGrades);
if (relevanceGrades.values().stream().anyMatch(grade -> grade < 0)) {
throw new IllegalArgumentException("Relevance grade must be >= 0");
}
Set<String> unique = new HashSet<>(rankedChunkIds);
if (unique.size() != rankedChunkIds.size()) {
throw new IllegalArgumentException("Ranked results contain duplicate IDs");
}
}
}
public record Metrics(double hitAtK, double recallAtK,
double reciprocalRank, double ndcgAtK) {
}
public static Metrics evaluate(EvaluationCase c, int k) {
if (k <= 0) throw new IllegalArgumentException("k must be positive");
Set<String> relevant = new HashSet<>();
c.relevanceGrades().forEach((id, grade) -> {
if (grade > 0) relevant.add(id);
});
double reciprocalRank = 0.0;
for (int i = 0; i < c.rankedChunkIds().size(); i++) {
int grade = c.relevanceGrades().getOrDefault(
c.rankedChunkIds().get(i), 0);
if (grade > 0) {
reciprocalRank = 1.0 / (i + 1); // 未截断 MRR 的单题贡献
break;
}
}
int limit = Math.min(k, c.rankedChunkIds().size());
int relevantRetrieved = 0;
double dcg = 0.0;
for (int i = 0; i < limit; i++) {
String id = c.rankedChunkIds().get(i);
int grade = c.relevanceGrades().getOrDefault(id, 0);
if (grade > 0) {
relevantRetrieved++;
}
dcg += gain(grade) / log2(i + 2.0);
}
double hitAtK = relevantRetrieved > 0 ? 1.0 : 0.0;
double recallAtK = relevant.isEmpty()
? 0.0
: (double) relevantRetrieved / relevant.size();
List<Integer> idealGrades = c.relevanceGrades().values().stream()
.filter(grade -> grade > 0)
.sorted((left, right) -> Integer.compare(right, left))
.limit(k)
.toList();
double idcg = 0.0;
for (int i = 0; i < idealGrades.size(); i++) {
idcg += gain(idealGrades.get(i)) / log2(i + 2.0);
}
double ndcgAtK = idcg == 0.0 ? 0.0 : dcg / idcg;
return new Metrics(hitAtK, recallAtK, reciprocalRank, ndcgAtK);
}
public static Metrics macroAverage(List<EvaluationCase> cases, int k) {
if (cases.isEmpty()) throw new IllegalArgumentException("cases must not be empty");
List<Metrics> metrics = cases.stream().map(c -> evaluate(c, k)).toList();
return new Metrics(
average(metrics, Metrics::hitAtK),
average(metrics, Metrics::recallAtK),
average(metrics, Metrics::reciprocalRank),
average(metrics, Metrics::ndcgAtK));
}
private static double average(List<Metrics> values,
ToDoubleFunction<Metrics> extractor) {
return values.stream().mapToDouble(extractor).average().orElseThrow();
}
private static double gain(int grade) {
return Math.pow(2.0, grade) - 1.0;
}
private static double log2(double value) {
return Math.log(value) / Math.log(2.0);
}
public static void main(String[] args) {
EvaluationCase first = new EvaluationCase(
"q-1",
List.of("c-noise", "c-g1", "c-zgc", "c-other"),
Map.of("c-g1", 3, "c-zgc", 2, "c-jdk21", 1));
EvaluationCase second = new EvaluationCase(
"q-2",
List.of("c-virtual-thread", "c-noise"),
Map.of("c-virtual-thread", 3));
Metrics metrics = macroAverage(List.of(first, second), 3);
System.out.printf(
"Hit@3=%.3f Recall@3=%.3f MRR=%.3f nDCG@3=%.3f%n",
metrics.hitAtK(), metrics.recallAtK(),
metrics.reciprocalRank(), metrics.ndcgAtK());
}
}设问题数为 Q,每题完整排名长度为 Mᵢ、截断深度为 K,相关标注数为 Rᵢ:
- 示例计算未截断 MRR,因此单题最坏扫描完整排名
O(Mᵢ);Hit/Recall/DCG 只扫描 top-K,为O(K); - 构造理想排序采用排序,单题为
O(Rᵢ log Rᵢ),可在评测集加载时预计算 IDCG; - 所有问题总时间约为
O(Σ(Mᵢ + K + Rᵢ log Rᵢ));单题临时空间为O(Rᵢ),当前macroAverage物化全部结果,另占O(Q)空间; - 宏平均让每个问题权重相同;若按请求频率加权会更贴近流量,但可能掩盖长尾与高风险问题,应同时报告切片;
- 示例把“无相关标注”的 Recall/nDCG 记为 0。实际协议也可将无答案样本从检索相关性指标中分离,单独评估拒答;关键是预先固定且透明报告。
场景设计题
场景:为企业知识库 RAG 建立从离线到线上的发布评估体系
背景: 系统支持 50 个租户,包含 Java 运维手册、内部 API、事故复盘和权限敏感文档。团队准备升级 chunking、embedding 和 reranker,希望判断是否上线。
一个完整设计应包括:
- 样本与标注:按真实流量分层抽样,补充错误码、比较、多跳、时效、无答案、ACL 和注入样本;每条保存身份类别、时间点、相关 chunk 等级、必要答案断言和期望拒答。
- 冻结版本:基线与候选分别保存数据快照、parser/chunker/embedding/index、检索参数、reranker、prompt、生成模型和 judge 版本;尽量在相同源数据与身份条件下成对运行。
- 分阶段指标:BM25/向量独立 Recall@K,融合后 Recall/MRR/nDCG,rerank 后 nDCG,最终上下文证据覆盖与 context precision,生成的 faithfulness/relevance/completeness/citation 指标。
- 安全门禁:无权文档不得出现在候选返回、rerank 输入、trace、缓存、prompt 和引用;注入文本不能改变工具权限;无答案样本评估误答与误拒。
- 统计与审查:报告逐题差值和 bootstrap 区间;按租户、语言、问题类型、文档新旧切片;人工复核所有严重回归、judge 分歧和高风险任务。
- 性能成本:比较构建时间、索引体积、P50/P95/P99、各模型调用次数、输入/输出 token 和每千请求成本;质量与成本用 Pareto 曲线讨论,而不是合成一个任意总分。
- 上线策略:先影子流量检查版本和安全,再小流量 A/B;设置自动回滚阈值和人工值班;线上失败脱敏后回流评测集。
可以提出一个示例门禁框架,但不能编造通用阈值:ACL 泄漏和跨租户缓存命中属于零容忍事件;质量、延迟和成本阈值根据当前基线、样本量与业务风险共同确定。
排障路径
遇到“候选版本最终回答指标下降”时按层定位:
- 先验证实验有效性:基线与候选是否使用同一问题、身份、数据时间点和评分协议;是否有缓存污染或 judge 版本变化?
- 查看索引覆盖:金标 chunk 是否仍存在,ID 是否因重切分失效,标注是否过期?
- 比较独立召回:BM25、向量各自 Recall@K 是否变化。仅向量下降时检查 embedding、归一化、ANN 与 query 模板;两路都下降时看 chunking、过滤和数据版本。
- 比较融合与重排:正确证据的 rank 在 RRF 与 rerank 前后如何变化;去重身份、候选 K、reranker 截断和超时是否改变?
- 检查最终上下文:证据是否因 parent 扩展、来源配额或 token 预算被丢弃;最终 prompt 与记录是否一致?
- 拆生成指标:是 faithfulness、relevance、completeness、correctness 还是 citation 下降;不要把所有失败归为“幻觉”。
- 审计裁判:抽取下降样本做人评,交换候选顺序并重复 judge;检查裁判是否偏爱长答案或受文档注入影响。
- 查看切片:总体下降可能集中在一个文档类型或语言;总体持平也可能掩盖权限和无答案回归。
- 核对性能降级:线上是否因超时跳过向量或 rerank;离线全功能结果不能代表线上 deadline 下的真实路径。
排障输出应是一条可验证结论,例如“正确证据在向量 top-50 中,但因 chunkId 版本变化未与 BM25 去重,RRF 后降到 43,超出 rerank top-40”,而不是“模型效果波动”。
速记总结
- 端到端指标负责验收,分层指标负责定位;至少保存召回、融合、重排和最终上下文快照。
Recall@K = 命中的相关证据数 / 全部相关证据数;至少命中一个是 Hit@K,不要混淆。- MRR 只看第一个相关结果,适合单答案;多证据问题结合 Recall 与 nDCG。
- nDCG 用分级增益、位置折损和理想排序归一化,适合判断头部排序质量。
- Faithfulness 只回答“断言是否受给定上下文支持”,不证明来源真实,也不等于相关、完整或正确。
- 引用要验证 assertion-to-source 支持关系,并绑定实际进入 prompt 的版本化 chunk。
- LLM-as-judge 是需校准的测量工具:固定版本、匿名与随机顺序、重复评分、对齐人评、分歧升级。
- 平均分会隐藏租户、语言、无答案和权限回归;发布门禁必须看关键切片。
- 置信区间表达抽样不确定性,不修复有偏数据,也不证明“不显著即等价”。
- 线上点击是行为代理,不是事实正确性;失败 trace 应脱敏、版本化并回流评测集。
参考资料
- NIST:Text REtrieval Conference(TREC)
- NIST TREC:Evaluation measures overview
- Järvelin 与 Kekäläinen:Cumulated Gain-Based Evaluation of IR Techniques
- Voorhees:The Philosophy of Information Retrieval Evaluation
- Es 等:RAGAS: Automated Evaluation of Retrieval Augmented Generation
- Zheng 等:Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena
- NIST:AI Risk Management Framework
- OWASP:LLM Prompt Injection Prevention Cheat Sheet
- Lewis 等:Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks