云计算百科
云计算领域专业知识百科平台

如何科学地评测你的 RAG 系统?Golden Set 评测实践

在这里插入图片描述

快速阅读(30 秒速览)

  • 调了参数不知道效果变好还是变坏?你需要一套可重复的量化评测
  • EasyRAG 的 GoldenSetEvaluator:准备一份"黄金问答集",系统逐条跑,输出混淆矩阵 + 三大指标
  • 三大指标:幻觉率(该拒答时硬答的比例)、覆盖率(回答了多少)、忠实度(回答的正确率)
  • 黄金集里要故意放"应该拒答"的问题——护栏的价值只有在拒答题上才能被量化
  • 评测器通过 Queryable trait 解耦:可以评真实引擎,也可以评 mock

前情提要:上一篇讲完查询引擎,自然要回答下一个问题:这些设计到底有没有让效果变好?不能凭感觉,要拿数字。


"感觉变好了"不是评测

一个常见的调参循环:

改了分块参数 → 问了几个问题 → “好像更准了” → 上线 → 用户投诉变差了

问题出在哪?样本太小、没有基线、没有对照组。你记住的永远是那几个答对了的问题,答错的早就忘了。

科学的做法是把评测变成一段可以反复执行的代码:固定问题集、固定指标、一键出报告。


第一步:构建黄金问答集

黄金集(Golden Set)是一组人工标注的问答对,EasyRAG 的模型定义在 easyrag-core:

pub struct GoldenItem {
pub query: String,
pub expected_answer: Option<String>, // None = 这个问题应该拒答!
pub query_type: Option<String>, // 可选分类:single_hop / multi_hop…
}

注意 expected_answer 是 Option——这是整套设计里最关键的细节。黄金集分两类:

  • 应答题:知识库里有答案,期望系统正确回答;
  • 拒答题:知识库里没有答案,期望系统诚实地说"不知道"。
  • # testdata/golden/ 示例(示意)
    { "query": "产品保修期多久?", "expected_answer": "主要部件保修 2 年" }
    { "query": "公司食堂中午几点开饭?", "expected_answer": null }

    第二类问题就是幻觉检测器:如果系统对"食堂几点开饭"给出了具体时间的回答——幻觉实锤。

    构建建议:

    • 从真实用户问题里挑(别自己编,真实问题的表述方式有你想不到的多样性);
    • 应答题与拒答题比例约 2:1;
    • 每道题标注到"答案在哪份文档"——答错时能直接定位是检索问题还是生成问题。

    第二步:评测器的混淆矩阵思维

    easyrag-pipeline/src/evaluation.rs 的 GoldenSetEvaluator 把每道题归入四种情况:

    let (classification, correct) = match (should_abstain, abstained) {
    (false, false) => { // 应回答,回答了 → TP,再查答得对不对
    let correct = is_roughly_correct(&response, item.expected_answer.as_deref());
    matrix.true_positive += 1;
    ("tp".to_string(), correct)
    }
    (true, true) => { // 应拒答,拒答了 → TN,正确
    matrix.true_negative += 1; ("tn".to_string(), true)
    }
    (true, false) => { // 应拒答,却回答了 → FP,幻觉!
    matrix.false_positive += 1; ("fp".to_string(), false)
    }
    (false, true) => { // 应回答,却拒答了 → FN,漏答
    matrix.false_negative += 1; ("fn".to_string(), false)
    }
    };

    在这里插入图片描述

    "是否拒答"怎么判断?系统拒答时返回统一的 FAIL_RESPONSE 话术(第 5 篇讲过),评测器识别这个标记即可——护栏与评测共用同一套协议,这是系统设计上的呼应。

    第三步:三大指标

    // 覆盖率:系统愿意回答多少问题
    let coverage = answered as f32 / total as f32;

    // 幻觉率:回答了的问题里,多少是"本该拒答却硬答"
    let hallucination_rate = matrix.false_positive as f32 / answered as f32;

    // 忠实度:应答题里,实际答对的比例
    let faithfulness = correct_answers as f32 / matrix.true_positive as f32;

    三个指标必须一起看,因为它们互相牵制:

    指标越高越好?刷分陷阱
    覆盖率 全都回答(幻觉率爆炸)
    幻觉率(越低越好) 越低越好 全都拒答(覆盖率归零)
    忠实度 只回答最有把握的题

    单看任何一个指标都会被系统"应试"刷分。 护栏阈值往严调,幻觉率降、覆盖率降;往松调反之——评测报告就是帮你找到那个平衡点。

    此外报告还包含 risk-coverage 曲线:按置信度排序逐步放宽拒答阈值,画出"幻觉风险随覆盖率上升"的曲线,你可以直接读图选工作点。


    工程细节:评测器怎么不绑死实现

    #[async_trait]
    pub trait Queryable: Send + Sync {
    async fn eval_query(&self, query: &str, param: &QueryParam)
    -> easyrag_core::Result<String>;
    }

    pub struct GoldenSetEvaluator {
    queryable: Arc<dyn Queryable>, // 可以是真实引擎,也可以是 mock
    param: QueryParam,
    }

    Queryable trait 让评测器与查询实现解耦:

    • 生产评测:接真实 QueryEngine,通过 /api/eval/golden-set 路由在线跑(另有 /api/eval/risk-coverage 单独出曲线);
    • 单元测试:接 mock,验证指标计算本身(混淆矩阵逻辑的测试不需要真模型)。

    查询失败也算拒答——评测循环里 Err 被归入 FAIL_RESPONSE,一次网络抖动不会让整份报告作废。


    评测驱动开发:真实工作流

    1. 基线报告:默认配置跑一遍,存档
    2. 改一个变量(分块策略 / top_k / 护栏阈值 / 换 embedding 模型)
    3. 重跑,diff 三个指标
    4. 指标升 → 保留;降 → 回滚,换下一个变量

    一次只改一个变量——这是评测能给出因果结论的前提。同时改三个参数等于没评测。

    踩坑记录

    坑 1:黄金集全是应答题。 幻觉率永远是 0(分母里没有拒答题),护栏调了个寂寞。拒答题至少占 1/3。

    坑 2:期望答案写得太"标准"。 is_roughly_correct 做的是粗匹配,期望答案写成论文式长句会导致"答对了但判错"。期望答案写关键事实短语(“2 年”“3.2 亿元”),判对率立刻合理。

    坑 3:评测用流式模式。 流式输出拼字符串有开销且不稳定,评测统一走非流式接口,结果可复现。


    写在最后

    阶段没有评测有评测
    调参 凭感觉 看指标
    上线 赌运气 基线对比
    换模型 心里没底 一键回归
    向老板汇报 “应该更好了” “幻觉率从 12% 降到 3%”

    评测体系是 RAG 项目里投入产出比最高的基础设施——它不提升任何一次回答的质量,但它让每一次优化都可信。

    下一篇进入工程实战篇:同一个系统,本地用 JSON 文件、生产用 PostgreSQL+Neo4j,代码一行不改——Trait 可插拔存储是怎么做到的。

    你的团队怎么评测 RAG 效果?有黄金集吗?评论区聊聊。


    本文为《手撸一个生产级 RAG:EasyRAG 实战系列》第 7 篇。上一篇:7 种检索模式 | 下一篇:Trait 可插拔存储


    开源仓库:haibingzhao/easyrag —— 欢迎 Star、Fork、提 Issue 和 PR。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 如何科学地评测你的 RAG 系统?Golden Set 评测实践
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!