Skip to content

RAG 评测从入门到实践:一份手把手教学文档

适用对象:刚接触 RAG(检索增强生成)系统、需要搭建评测体系的算法/后端工程师 前置知识:了解 RAG 基本流程(检索 + 生成)即可,不要求有评测经验 学习目标:读完后能独立设计一套评测集、写出一个简单的裁判打分器,并看懂企业级评测系统的整体架构


image

一、先讲一个场景,搞懂"为什么需要评测"

假设你花了两周做了一个企业内部的客服 RAG 系统,接入了知识库,能回答问题了。你把 demo 演示给老板看,老板问了三个问题:

  1. "这系统准确率多少?"
  2. "上周你改了检索逻辑,效果是变好了还是变差了?"
  3. "如果它编造事实误导了客户,怎么保证不会发生?"

你发现自己答不上来——因为你只是"感觉好像还行",测试方式是自己在前端随便问几个问题看看回答对不对。这种方式有三个致命问题:

  • 不可复现:今天你测了10个问题觉得不错,明天换个人测10个不同的问题可能觉得很差,没有统一标准。
  • 改了这里、坏了那里,发现不了:优化了A类问题的回答,可能悄悄把B类问题的准确率搞低了,靠人工肉眼根本发现不了。
  • 无法量化,没法向上汇报,也没法当作上线的门槛

这就是评测体系存在的意义:把"感觉还行"变成"客观数字",把"随便测测"变成"可重复、可对比、可追溯"的工程流程。


二、基本概念:先搞清楚几个术语

在动手之前,先把几个反复会用到的概念讲清楚。

1. RAG 的两层结构

RAG 系统本质上分两层,评测也要按这两层来做,这是最重要的一个认知:

用户提问


【检索层 Retrieval】:从知识库里找出相关的文档片段(chunk)


【生成层 Generation】:大模型基于检索到的片段,生成最终回答


返回给用户

如果最终回答错了,可能是两种原因造成的:检索层没找对材料(巧妙做饭但食材不对),或者生成层没用好材料(食材是对的但做糊了)。评测体系必须能区分这两种情况,否则你永远不知道该优化检索还是优化生成。

2. 什么是 Golden Dataset(金标准测试集)

一份提前准备好的"标准答案集",每条包含:问题、标准答案、这个答案应该来自哪个文档。它是评测的"考卷",没有这个,后面所有指标都无从算起。最简单的一条大概长这样:

json
{
  "question": "退货政策是几天内可以无理由退货?",
  "ground_truth": "7天内无理由退货,需保持商品完好",
  "source_file": "退换货政策.md"
}

3. 什么是 LLM-as-a-Judge(大模型当裁判)

人工一条条看回答对不对,效率太低;用传统的字符串匹配(比如判断两句话像不像)又理解不了语义。所以业内的通行做法是:再找一个大模型,给它一套打分标准(Rubric),让它扮演"裁判",去比对系统的回答和标准答案,输出一个分数和理由。

打个比方:这就像批改作文,与其你自己一篇篇精读打分,不如请一位经验丰富的语文老师(裁判模型),按照统一的评分标准去批改,速度快很多,而且标准统一。

4. 什么是 Rubric(打分标尺)

Rubric 就是给裁判模型的"评分说明书"。没有它,裁判打分会很随意。一个好的 Rubric 长这样(以"忠实度"这个指标为例):

1.0 分:回答中的所有事实都能在参考资料里找到依据,没有编造
0.5 分:回答基本忠实,但补充了一些参考资料没提到的常识性内容
0.0 分:回答中包含参考资料没有依据的关键事实,属于编造/幻觉

记住一句话:Rubric 越具体,裁判打分越稳定;Rubric 越模糊(比如只说"根据忠实度打0-1分"),裁判打分的随机性越大。


三、动手第一步:设计你的评测指标

不要一上来就想着做6个指标、8个指标,先理解每个指标"测的是什么问题",再决定要不要加。

检索层指标(客观计算,不需要大模型)

Hit@K:前K个召回的片段里,有没有包含标准答案所在的文档?有就是1,没有就是0。这是最基础的指标,理解起来像"抽奖有没有中"。

python
# 伪代码:判断 Hit@3
def hit_at_k(retrieved_docs, ground_truth_source, k=3):
    top_k = retrieved_docs[:k]
    return 1.0 if ground_truth_source in top_k else 0.0

MRR(平均倒数排位):不只关心"有没有中",还关心"排第几"。排第1名给1分,第2名给0.5分,第3名给0.33分——排名越靠后,分数衰减越快。

python
# 伪代码:计算 MRR
def mrr(retrieved_docs, ground_truth_source):
    for rank, doc in enumerate(retrieved_docs, start=1):
        if doc == ground_truth_source:
            return 1.0 / rank
    return 0.0

新手常踩的坑:知识库里存的文件名和检索返回的引用格式经常对不上(比如一个叫 01_退换货政策.md,检索返回的却是 退换货政策),直接用 == 判断相等会导致大量"假阴性"(明明中了却被判定没中)。解决办法是先把文件名统一做标准化处理(去掉编号前缀、去掉扩展名)再比较。

生成层指标(需要 LLM 裁判)

忠实度(Faithfulness):回答是不是"照着材料说话",有没有编造。这是最重要的一个指标,因为幻觉是 RAG 系统最大的风险。

准确度(Correctness):回答和标准答案对不对得上。

安全合规(Safety):有没有说出不该说的话(比如误导性的操作建议)。

初学者建议:一开始不要贪多,先把 Hit@K、忠实度、准确度这三个指标跑通,能看懂数字变化是怎么回事之后,再逐步加安全性、相关性等更多维度。


四、动手第二步:写一个最简单的裁判打分器

理解了 Rubric 之后,我们来写一个最简化版的忠实度裁判,帮助你理解整个链路是怎么跑起来的。

python
FAITHFULNESS_PROMPT = """
你是一个严格的质检员。请根据下面的【参考资料】判断【系统回答】是否忠实(没有编造事实)。

【参考资料】
{context}

【系统回答】
{answer}

打分规则:
1.0 分:回答完全基于参考资料,没有编造
0.5 分:基本忠实,但有轻微的常识性补充
0.0 分:包含参考资料中没有依据的关键事实

请只输出如下 JSON 格式,不要输出其他任何内容:
{{"score": 分数, "reasoning": "打分理由"}}
"""

def judge_faithfulness(context, answer, llm_client):
    prompt = FAITHFULNESS_PROMPT.format(context=context, answer=answer)
    response = llm_client.chat(prompt)
    return parse_json(response)  # 解析出 score 和 reasoning

这里有个新手容易忽略的细节:大模型的输出不总是干净的 JSON,有时候会带 ```json 代码块标记,有时候会在前面加一句解释。所以实际生产代码里,parse_json 这一步通常要做"多级兜底":

python
import re, json

def parse_json(text):
    # 第一步:尝试直接解析
    try:
        return json.loads(text)
    except Exception:
        pass
    # 第二步:提取 ```json ... ``` 代码块
    match = re.search(r"```(?:json)?\s*(\{.*?\})\s*```", text, re.DOTALL)
    if match:
        try:
            return json.loads(match.group(1))
        except Exception:
            pass
    # 第三步:提取第一个 { 到最后一个 } 之间的内容
    start, end = text.find("{"), text.rfind("}")
    if start != -1 and end > start:
        try:
            return json.loads(text[start:end + 1])
        except Exception:
            pass
    return None  # 都失败了,标记为解析失败,人工兜底

这段代码看似不起眼,但在实际项目里极其重要——如果没有兜底逻辑,可能有5%~10%的裁判打分因为格式问题直接解析失败,白白浪费调用成本。


五、动手第三步:把单题评测串成完整流程

有了 Hit@K/MRR 的计算函数和裁判函数,接下来把它们串起来,对一整份测试集跑一遍:

python
async def evaluate_one_case(case, retriever, generator, judge):
    # 1. 检索
    contexts = retriever.search(case["question"], top_k=3)
    hit = hit_at_k(contexts, case["source_file"])
    rr = mrr(contexts, case["source_file"])

    # 2. 生成
    answer = generator.generate(case["question"], contexts)

    # 3. 裁判打分(忠实度 + 准确度可以并行调用,节省时间)
    faith_score = await judge.faithfulness(contexts, answer)
    correct_score = await judge.correctness(case["ground_truth"], answer)

    return {
        "id": case["id"],
        "hit@3": hit,
        "mrr": rr,
        "faithfulness": faith_score,
        "correctness": correct_score,
    }

async def run_evaluation(dataset, retriever, generator, judge):
    results = []
    for case in dataset:
        result = await evaluate_one_case(case, retriever, generator, judge)
        results.append(result)
    return results

新手常见的下一个问题:测试集有几十上百道题,一道道跑很慢。生产环境通常会用并发(比如 asyncio.gather + 信号量限流)来提速,但一开始学习阶段,建议先用最简单的串行循环把流程跑通,再考虑并发优化——先追求"跑得对",再追求"跑得快"。


六、看懂结果之后:怎么用评测结果指导优化

跑完一轮评测,你会拿到一堆分数。新手常犯的错误是:看到某个指标低,就直接开始瞎改(比如看到准确度低就疯狂加长 Prompt)。正确的做法是先做"归因",再动手:

归因的基本思路

拿到一条低分 case,按下面的顺序排查:

  1. 先看 Hit@K:是不是等于0?如果是,说明问题出在检索层,生成层再怎么优化都没用,得先去查检索逻辑(是不是切片切得不好、向量库配置有问题)。
  2. Hit@K 是1,但 MRR 很低:说明检索到了但排序靠后,可以考虑加 rerank 模型,或者调整检索的 top_k。
  3. Hit@K 和 MRR 都正常,但忠实度低:说明生成层出了问题,可能是 Prompt 没有明确要求"只依据参考资料回答"。
  4. 忠实度正常,但准确度低:可能是回答遗漏了关键细节(比如漏了一个数值、一个步骤),这种情况通常是 Prompt 里没有强调"完整保留关键信息"。

一个真实的调优例子(帮助理解这个排查过程)

假设有道题问"CAN总线故障如何排查",标准答案里包含"电阻60Ω,如果测得120Ω说明一端开路"这个关键判断依据。系统的回答只说了"电阻正常应为60Ω",漏掉了"120Ω代表一端开路"这个判断技巧。

  • 排查:Hit@3=1(检索对了材料),忠实度=1.0(没有编造),但准确度裁判打了0.5分,理由是"遗漏关键判定依据"。
  • 归因:属于上面第4种情况——生成层遗漏了细节,不是检索问题。
  • 优化动作:在 Prompt 里加一条明确要求——"参考资料中的判定依据和具体数值必须完整保留,不能概括省略"。
  • 复测:这道题的准确度分数从0.5涨到1.0。

这个例子想说明的核心道理是:优化动作永远要能对应到具体的归因结论,而不是凭感觉去改 Prompt。归因做得越细,优化就越有针对性,避免"改了这里、坏了那里"。


七、进阶一步:企业级场景要多想的几件事

当你把上面的流程跑通之后,如果要把这套评测体系用到真实企业项目里,还需要多考虑几点(这里先给概念,不展开细节,作为你后续学习的路标):

  • 测试集不能只有一份,要分层:固定不变的"回归测试集"(每次改动必跑)、专门收集难题的"难例集"、专测安全底线的"红线集",以及定期从真实用户提问里抽样的"生产采样集"。
  • 裁判模型自己也要被检验:定期抽样对比裁判打分和人工打分是否一致,防止裁判模型本身"看走眼"。
  • 版本要能追溯:每次跑评测,要记录清楚当时用的是哪个版本的代码、Prompt、知识库、模型,分数变化了才知道是谁的锅。
  • 要能接入上线流程:设定一个明确的门槛(比如"忠实度≥0.95 且安全违规=0 才允许上线"),让评测系统从"事后分析工具"变成"自动化质量关卡"。

八、动手实操清单(学完就做)

按顺序完成,每一步都能验证上一步的理解:

  • [ ] 准备10道测试题,包含 question / ground_truth / source_file 三个字段
  • [ ] 写一个 hit_at_k 函数并手动验证3个 case
  • [ ] 写一个 mrr 函数并手动验证3个 case
  • [ ] 写一个忠实度裁判 Prompt,用你的大模型 API 跑通一次调用
  • [ ] 给裁判打分器加上 JSON 解析兜底逻辑,故意构造一个"脏输出"测试兜底是否生效
  • [ ] 把上面所有函数串成一个完整的 run_evaluation 循环,跑完10道题拿到一份分数表
  • [ ] 挑一道低分题,按"检索→排序→忠实度→准确度"的顺序做一次归因,写出你认为的问题原因
  • [ ] 针对归因结论修改一次 Prompt,重新跑这道题,验证分数是否变化

完成这份清单之后,你已经掌握了 RAG 评测体系里最核心的思维方式:先分层定位问题,再针对性优化,用可重复的数字代替"感觉"。这也是后续学习企业级评测平台架构(并发调度、可观测性、版本管理)的基础。