Skip to content

RAG 评估:从"我相信我的 RAG 好"到"我证明我的 RAG 好"

一个 8 年 Java 后端,用 20 条简历问答做语料,花四天把 RAG 从"跑通了"升级到"能量化了"。

核心结论:没有评估的 RAG 优化就是瞎调参数。Rerank、HyDE、Query 改写各管一段,Ragas 让每个改动的 ROI 可量化。


为什么"跑通了"不等于"做好了"

Week 1 手撕 Mini RAG,Week 2 用 Spring AI + Qdrant 做了简历问答机器人,Week 4 在 Dify 上搭了知识库。四次实践,每次都"跑通了"--输入问题,返回答案,看起来像那么回事。

但面试官问"你的 RAG 好不好",你总不能说"我觉得挺好"。更尴尬的是,每次改一个参数(换分词器、调 chunk size、加 Rerank),你根本不知道是变好了还是变差了,因为没有一个量化指标在跟踪。

Week 5 就解决这一个问题:让 RAG 效果可量化、可归因、可对比。


一、分三层量化:检索层、Context 质量层、生成层

RAG 是个流水线,每一步都可能出错。只看最终答案对不对,无法定位问题出在哪一步。我用了三组指标,分别测不同层级:

用户问题 ──► 检索 ──► context ──► LLM ──► 答案
              │         │                  │
              │         ├─ ContextPrecision:context 干不干净
              │         └─ ContextRecall   :该找的都找到了吗
              ├─ Hit@K / P@K / MRR         │
              └────────────────────────────► Faithfulness:答案有没有瞎编
  • 检索层(Day 1-2):Hit@K 看召回天花板,P@K 看 context 纯净度,MRR 看重排效果
  • Context 质量层(Day 3):Ragas 的 ContextPrecision / ContextRecall,让 LLM 判断 context 和标准答案的相关性
  • 生成层(Day 3):Faithfulness 测答案有没有超出 context 瞎编

为什么要分三层? 因为改哪一步,指标就动哪一步。加了 Rerank,检索层指标涨但生成层不动--说明 Rerank 改的是 context 排序不是 LLM 行为。加了拒答 prompt,Faithfulness 涨但检索指标不动--说明 prompt 只影响生成不影响检索。如果三个指标混在一起看,你永远不知道改动到底值不值。


二、Rerank:不是"多加一层",是引入正交信息

实验设计

三链路消融:

  • A:BM25 Top-3 直出(baseline)
  • B:BM25 Top-K → LLM 精排 → Top-3(两阶段)
  • C:BM25 Top-K 全部(天花板对照组)

三组打分器:无打分(baseline)、lexical 词覆盖率(同源退化)、LLM 语义打分(正交判据)。

最硬的结论:Rerank 生效的结构前提

很多人抱怨"加了 Rerank 没效果"。我在 recall_k=3, final_k=3 时跑出来的数据:

recall_kHit@3 增益P@3 增益MRR 增益
3+0.0pt+0.0pt+0.050
5−5.0pt+0.0pt+0.035
8−5.0pt+0.0pt+0.037
12+10.0pt+5.0pt+0.162

k=3 时增益为零不是巧合,是数学必然:重排不改变集合成员,Hit@K 和 P@K 只关心"集合里有什么",不关心"谁排第几"。

Rerank 生效的结构前提是 recall_k > final_k 不满足这个前提,跑出来的零增益是数学上必然的,不是 Rerank 没用。

判据正交性实验

最关键的发现:Rerank 的增益不来自"多加一层",来自引入召回阶段看不见的信息。

打分器判据和 BM25 的关系MRR 变化
lexical(词覆盖率)字面(去掉 IDF 加权)同源退化−0.050
LLM(语义)语义匹配正交+0.077

lexical 打分器把 BM25 的三层信息(IDF 加权、词频饱和、长度归一化)全丢了,只保留字面重合计数。这不是"第二意见",是"同一个人说话但含糊了"。LLM 引入了 BM25 看不见的语义信息,才是真正的"第二意见"。

生产级教训

  • LLM pointwise 打分有结构缺陷:每次只看一条 query + 一篇文档,无法做相对判断。我的实验里 D02(毕业院校)单独看得 4 分、D20(薪资构成)得 3 分,但摆一起任何模型都会选 D20
  • 延迟差 1-2 个数量级:LLM 12 个候选 = 12 次 HTTP 请求(12-24 秒),Cross-Encoder = 1 次(30-50ms)
  • 生产用 Cross-Encoder 不用 LLM 打分:靠训练数据校准绝对分数,不是让 LLM 临时理解评分标准

三、HyDE + Query 改写:治召回的病根

HyDE:让检索端看到 LLM 在找什么

Rerank 只能救"召回进来但排得靠后"的文档。如果正确文档压根没被召回,Rerank 无能为力。HyDE 解决的就是这上一层问题:让 LLM 先根据 query 生成一段假想答案,再用这段假想答案去检索。

实测数据(focus 6 条 BM25 翻车的 query):

链路                           Hit@3    P@3     MRR
原始 query                      0.333   0.111   0.310
HyDE 假想文档                   0.500   0.167   0.546  (+76% MRR)
query + 假想文档拼接            0.500   0.167   0.546

最干净的证据:query「这人写代码几年了」,BM25 给 gold 文档 D03("8年Java后端开发经验")打 0.0000 分--字面零重合。Day 1 用 LLM Rerank 从第 6 名救回第 1 名。Day 2 用 HyDE 让它在召回阶段就排第 1,BM25 得分 16.34。两个技术作用在不同层,同一道题各自解法不同。

关键判据:HyDE 补「词类」有效,补「实体」有害。

  • 有效:query「写代码几年」→ 假想文档「8年Java后端开发经验」→ 词类匹配,BM25 从 0 跳到 16.34
  • 有害:query「他念到什么学历了」→ 假想文档编造「上海交通大学、GPA 3.8」→ 真实语料只有「某211高校」→ 高 IDF 噪音把检索带跑

Query 改写:多轮对话的救命稻草

10 条多轮对话,裸 last_query 真命中 0/10。改写后:

链路                    Hit@3    P@3     MRR
裸 last_query           0.200   0.067   0.248
改写后 query            0.700   0.233   0.729
改写增益: Hit@3 +50.0pt | MRR +0.481

改写增益远大于 HyDE 增益,原因:多轮对话的 baseline 实在太差("效果呢"、"框架呢"这种 query 单独检索一条都命不中),有巨大的提升空间。

三层分工

多轮对话 → Query 改写 → HyDE 生成 → BM25 召回(k=12) → Rerank 精排(Top-3) → LLM 生成
           │              │              │                  │
           │              │              │                  └─ 窗口内重排
           │              │              └─ 让原来找不到的文档被命中
           │              └─ 跨过字面鸿沟
           └─ 补全省略和代词

三层各有各的靶子,互不重叠,能而且应该叠加。


四、Ragas:端到端评估的三个指标和它的盲区

三管道对比

管道FaithfulnessContextPrecisionContextRecall
baseline(BM25 Top-3,无拒答约束)0.8590.6830.858
baseline + 拒答约束0.9520.6830.858
rerank + 拒答约束0.9350.8000.925

两个结论:

  1. 拒答约束把 Faithfulness 从 0.859 拉到 0.952(+9.3pt),ContextPrecision/Recall 纹丝不动--prompt 工程只影响生成,不影响检索
  2. Rerank 把 ContextPrecision 从 0.683 拉到 0.800(+11.7pt),Faithfulness 基本不动--Rerank 改的是 context 质量,主要影响 context 类指标

改动哪一段,指标就动哪一段。这说明三个指标确实在测不同的东西。

Faithfulness 的盲区

最有意思的发现:三条语料答不了的拒答题,即使不加拒答约束,模型也会说"资料没有"然后罗列一堆来自 context 的正确废话。每句话都忠实于 context,Faithfulness 高达 0.88-1.0,但实际是答非所问。

Faithfulness 是"没说谎"的下限指标,不是"答得好"的质量指标。 生产里必须配 Answer Relevancy 一起看,单看 Faithfulness 会被"正确的废话"骗。

LLM-as-judge 的偏差

  • self-preference bias:judge 和被评模型用同一个模型会系统性偏高
  • 推理模型做 judge 容易截断:reasoning_tokens 吃光 max_tokens 预算 → 正文没输出 → NaN。生产用非推理模型做 judge,或者给足 token 预算
  • 必须人工抽检对齐:LLM 裁判只能筛大部分,不能当终裁

五、RAG v2 综合优化:各改动的 ROI

把四天的优化集成到一条流水线,对比 baseline:

优化措施作用层最大增益代价ROI
拒答 prompt生成Faithfulness +9.3pt0(改 prompt)⭐⭐⭐⭐⭐
Rerank (k=12)精排Hit@3 +10pt, MRR +0.16212 次 LLM 调用⭐⭐⭐⭐
Query 改写召回Hit@3 +50pt(多轮)1 次 LLM 调用⭐⭐⭐⭐⭐
HyDE召回MRR +0.236(focus)1 次 LLM 调用⭐⭐⭐

先做 ROI 最高的。 拒答 prompt 零成本提 9.3pt,Query 改写一次调用提 50pt(多轮场景)。Rerank 效果好但代价最大。HyDE 在特定 query 上有效但有实体污染风险。

不是加越多越好,是知道哪个改动值多少分。


六、完整 RAG v2 流水线

用户输入

    ├─ 多轮对话?─→ Query 改写(补全省略和代词)

    ├─ query 太短/口语化?─→ HyDE 生成假想文档


BM25 召回 (recall_k=12)


LLM Rerank 精排 → Top-3


拒答约束 prompt + context → LLM 生成


答案

生产级改进方向:

  • Rerank 换 Cross-Encoder(BGE-Reranker),延迟从 12-24 秒降到 30-50ms
  • judge 换独立模型消除 self-preference bias
  • 补 Answer Relevancy 指标(需要 embedding 权限)
  • 评测集扩大到 100+ 条,降低单条抖动影响

七、踩坑 Top 5

  1. 分词器翻转结论符号:virtualenv 看不见系统 jieba → 降级成 char-bigram → Rerank 从 +0.032 变成 −0.050。每次实验必须打环境指纹
  2. 推理模型 max_tokens 有偏截断:越需要仔细判断的文档思考越久 → 被截断 → 系统性把最难判断的文档排在最后。不是随机噪音,是有偏偏差
  3. BM25 全 0 分稳定排序返回伪结果:9/12 文档全 0 分,sort 返回入库顺序,看起来"有结果"其实完全随机
  4. system role 让模型静默返回空串:finish_reason='stop',reasoning_tokens=0,max_tokens 加到 800 也一样。改成单条 user 消息才正常
  5. temperature=0 不保证确定性:同一份代码同一 prompt,D15 一次 0 分一次 2 分。重要结论跑 2-3 次看稳定性

总结

四天实验,20 条 query + 10 条多轮,从"跑通了"到"量化了"。

三个最值钱的结论:

  • Rerank 生效需要 recall_k > final_k,否则数学上必然零增益。很多人说"Rerank 没用",真实原因是召回窗口和最终窗口一样大
  • HyDE 补词类有效补实体有害,判据是看 LLM 生成的是领域通用词还是具体实体
  • Faithfulness 是下限不是质量,"正确的废话"也能高分,必须配 Answer Relevancy 一起看

如果面试官问"你怎么证明你的 RAG 好",我的回答是:三层量化(Hit@K / ContextPrecision / Faithfulness),三管道消融(baseline / +拒答 / +Rerank),每个改动对应哪个指标变动可量化、可归因。不是"我觉得变好了",是"我证明变好了多少,而且知道为什么"。

参考:Day 1 Rerank 实验 (rerank_experiment.py) / Day 2 HyDE 实验 (hyde_experiment.py) / Day 3 Ragas 评估 (ragas_eval.py) / 评测集 (qa_dataset.py, corpus.py)

手撕 → 框架 → 生产化,一步步把 AI Agent 工程化搞透。
粤ICP备2026104257号-1