ByteFisher AI 工程化深度实战(五):RAG 优化(下)——评测与迭代

第 04 篇我们把检索链路从单路 BM25 升级成了混合检索。但有一个残酷的事实:凡是”感觉更好”的优化,都可能在两周后被打回原形。因为”感觉”不留下数据,而数据才是不被推翻的依据。

本篇解决这个问题的工程手段:离线评测体系。包括三部分——评估集怎么来、指标怎么算、指标怎么驱动迭代。

一、RAG 评测的两个层次

RAG 系统要评测两层,缺一不可:

1
2
3
4
5
6
7
┌─────────────────────────────────────┐
│ L1 检索层(Recall / MRR / Precision)│ ← 只测"找没找对"
│ 上下文里的证据是否真的相关 │
├─────────────────────────────────────┤
│ L2 生成层(Faithfulness / Relevancy)│ ← 测"答得好不好"
│ 回答是否忠于证据、是否回答到点上 │
└─────────────────────────────────────┘

先测 L1 再测 L2。如果检索层 Recall@K 是 0.3,生成层指标再高都是幻觉侥幸——模型在用自己的知识回答,而不是用你的知识库。测试顺序就是问题定位顺序。

二、评估集:没有评估集,一切指标都是摆设

2.1 从博客真实读者收集

ByteBot 最珍贵的资产是真实问题。ai-assistant.js 的 trackEvent 只统计 opens/sends/errors,没有记录问题文本。改造第一步:后端把每次对话的 user 消息(脱敏后)落库。

评估集的问题来源分级:

来源 价值 数量目标
真实读者问题(线上埋点) 最高,反映真实需求 ≥ 30
博客评论区 / 留言板 高 ≥ 10
从文章标题反推(”每个系列至少 1 个导航问题”) 中,覆盖边界 ≥ 20
手工构造的刁钻问题(跨文章、模糊意图) 高,测鲁棒性 ≥ 10

2.2 评估集的格式

1
2
3
4
5
6
7
8
9
{
"id": "q_001",
"question": "Unity 项目里怎么实现对象池?",
"expected_answer_articles": ["/2025/05/15/Unity3D教程-16-对象池技术/"],
"expected_keywords": ["对象池", "ObjectPool", "实例化", "回收"],
"category": "unity",
"difficulty": "normal",
"source": "online"
}

关键字段:

  • expected_answer_articles:L1 检索层的”标准答案”——这个问题应该召回哪些文章。这是人工标注成本最高的字段,但对 Recall 计算必不可少。
  • expected_keywords:生成层的弱标注,自动粗略校验”回答是否提到关键概念”。
  • source:区分线上/手工/反推,跑评测时可以分层看指标(比如”线上问题集”的分数才是用户真实体验)。

2.3 用 RAGAS 自动生成评估集

纯人工标注 70 个问题太累。RAGAS 提供 TestsetGenerator,从文档自动生成”问题 + 答案”对——生成的答案是文档里的原文,可以直接当 ground truth。流程:

1
2
260 篇文章 → 抽样 30 篇 → RAGAS 生成 60~90 个 QA 对
→ 人工抽检 20% 修正 → 合并真实问题 → 形成评估集 v1

注意 RAGAS 生成的问题偏”文档内容复述型”,覆盖不了”导航型”问题(”有哪些 Unity 教程”),所以必须和真实问题混合。

三、核心指标:怎么算、怎么解读

3.1 检索层指标

Recall@K:Top-K 结果里包含多少标准答案。

1
2
Recall@5 = 标准答案文章在 Top5 召回中的比例
例:5 个评估问题,每个期望 2 篇文章,Top5 中正确召回 6 篇 → Recall@5 = 6/10 = 0.6

MRR(Mean Reciprocal Rank):第一个正确答案出现在第几位。

1
2
问题期望文章在 Top5 的第 2 位 → RR = 1/2
MRR = 所有问题 RR 的平均,衡量"第一个正确答案够不够靠前"

Context Precision:注入 prompt 的上下文里,相关 chunk 的占比。第 04 篇做了 Rerank 后,这个指标应该显著上升。

3.2 生成层指标(RAGAS 三件套)

Faithfulness(忠实度):回答里的每条陈述是否都能在检索到的上下文中找到依据。这是防幻觉的核心指标。

1
2
评估方式:LLM-as-Judge,把回答拆成陈述句,逐句问"上下文里有没有依据"
faithfulness = 有依据的陈述数 / 总陈述数

Answer Relevancy(回答相关性):回答是否回答了用户的问题,还是答非所问。

Context Relevancy(上下文相关性):注入的上下文整体上是否切题。

3.3 LLM-as-Judge 的可信度

RAGAS 这些生成层指标依赖一个”评委 LLM”。评委要可信,必须遵守:

  • 盲审:评委不知道哪个是改造前、哪个是改造后,避免顺序偏好。
  • 多评委取平均:至少两个模型(如 DeepSeek + 本地 Qwen)打分,分歧大时人工复核。
  • 分数而非”好/坏”:让评委输出 1~5 分或百分比,比二分类更有区分度。
  • 评委 prompt 里写清楚评分标准,避免主观漂移。

四、指标驱动迭代的完整闭环

1
2
3
4
5
6
7
① 收集问题 → ② 标注/生成评估集 → ③ 跑基线评测
▲ │
│ ▼
⑥ 更新评估集(新增线上问题) ④ 定位短板(检索 or 生成)
│ │
└────────── ⑤ 针对性优化 ←─────────┘
(改检索/改chunk/改prompt) 再回③

关键规则:一次只改一个变量。想验证”加 Rerank 有没有用”,就只加 Rerank 跑一次;想验证”换 chunk 大小”,就只改 chunk 大小。同时改三个东西,指标变了你都不知道是哪个的功劳。

4.1 诊断决策树

1
2
3
4
5
6
7
看 L2 指标(Faithfulness 低)?
├─ 是 → 是"没依据硬说"还是"依据不够"?
│ ├─ 没依据硬说 → 增强 prompt 约束 + 检查 Rerank 是否混入不相关 chunk
│ └─ 依据不够 → 看 L1 Recall:上下文里根本没有证据 → 回检索引擎
└─ 看 L2 的 Relevancy 低?
├─ 是 → 上下文注入过量(第03篇的预算问题)或查询改写失效
└─ 否 → 检索已经够好,问题在生成 prompt 本身

五、ByteBot 的实测流程(附示例数据)

设计一个可复现的评测脚本 tools/rag-eval/eval.py:

1
2
3
4
5
python tools/rag-eval/eval.py \
--index api/posts-index.json \
--eval-set tools/rag-eval/eval-set-v1.json \
--recall-topk 5 \
--judge-model deepseek-chat

输出示例(改造前后对比):

1
2
3
4
5
6
7
8
9
10
11
12
=== 检索层 ===
Recall@5 : BM25单路 0.52 → 混合检索 0.71 (+36.5%)
MRR : BM25单路 0.44 → 混合检索 0.58 (+31.8%)
Context Precision : 0.63 → 0.74 (+17.5%) ← Rerank 的效果

=== 生成层(RAGAS, LLM-as-Judge, 2评委平均) ===
Faithfulness : 0.78 → 0.86 (+10.3%)
Answer Relevancy: 0.72 → 0.81 (+12.5%)
Context Relevancy: 0.75 → 0.83 (+10.7%)

=== 分项(线上问题集 subset) ===
Recall@5 on online-set : 0.60 → 0.78

注:以上数字为示例,实际改造后应跑出真实数据回填。这个脚本会作为系列工具留存,CI 里加一步”评测不劣化”的回归检查(第 11 篇细说)。

5.1 评测的工程化细节

  • 每次评测记录 git 环境:评估集版本 + 代码 commit + 检索配置,否则跑完不知道测的是哪一版。
  • 评测集要”滚雪球”:每次线上发现回答翻车,就把这个问题追加进评估集。评估集从 70 → 100 → 150,会越来越贴近真实。
  • 成本:一次全量评测用 150 个问题 × 2 评委 ≈ 300 次 judge 调用,DeepSeek 下成本约 1 块钱以内,完全可以每天跑。

六、落地清单与小结

优先级 改造 收益
P0 线上问题埋点落库(脱敏) 评估集有活水
P0 建评估集 v1(真实+生成混合) 一切指标的前提
P1 写 eval 脚本 + 跑基线 把”感觉”变成数字
P1 一次只改一个变量,逐项验证第 04 篇改动 每个优化都有归因
P2 CI 回归检查(评测不劣化) 防止后续改坏
P2 翻车问题进评估集 评估集滚动进化

核心结论:

  • 先测检索层再测生成层;检索 Recall 低时,生成层指标再高都是幻觉。
  • 评估集 = 真实问题(主体)+ RAGAS 生成(扩充)+ 手工刁钻题(边界),且要带 expected_answer_articles 标注。
  • RAGAS 的 Faithfulness / Relevancy 是标准生成层指标,但评委必须盲审、多模型、打分制。
  • 一次只改一个变量,用指标归因每步优化;”感觉更好”不算数。
  • 评测不是一次性的,评估集要滚雪球、CI 要回归——这是工程化的护城河。

下一篇转向模型部署:本地模型量化与推理优化。把第 01 篇诊断的”降级路径 3”落地——用 Ollama/vLLM 跑量化模型做 ByteBot 的兜底,并给出量化级别实测对比。


本系列持续更新中,每周一篇。

ByteFisher
分享编程技术 · 记录钓鱼乐趣
扫码关注
▸ 扫码关注 ◂
分享: