很多搞大模型应用的朋友,最后都会撞上同一堵墙:RAG系统上线了,demo演示也挺顺,但一问“效果到底怎么样”,只能甩出几个截图和“看着还行”这种没法量化的说辞。我做过的RAG项目稍微多一点,最深的体会是——RAG评测从来不是一个“跑个分”的动作,而是一整套把业务需求翻译成可量化指标、用指标反推链路瓶颈、再拿错误案例驱动迭代的方法论。这篇文章不聊玄的,就把我实际用的那套评测体系完整拆给你看。
1. 先想清楚:RAG评测到底在测什么
很多团队一开始就想找“评测标准”,但评测的第一件事不是挑指标,而是回答一个更前置的问题:这套系统跑在什么场景里,答错了会付什么代价。
1.1 评测思路可以从系统链路倒推回来
RAG的全称是检索增强生成,它天生就是一条流水线:用户问题先被转成向量去知识库检索,然后把召回片段拼进提示词,交给大模型生成答案。既然是流水线,每个环节都可能掉链子,评测就必须把这两个环节拆开看:
- 检索环节:有没有把真正有用的知识片段从知识库里捞上来
- 生成环节:捞到的片段有没有被正确用于生成,有没有遗漏关键信息,有没有添油加醋
如果你的评测只盯着最终回答的对错,就会陷入“既说不清是哪里坏了,也不知道该优化谁”的困局。我自己做项目的固定打法,是先把链路拆成两个独立评测单元,再用一套系统性指标做端到端验证。
另外还要补一个很多人忽略的维度:评测是服务于决策的。你不能只测“平均分”,你还得知道“哪类问题在烂”。评测结果如果只产出一个总分数,对项目决策是远远不够的——它至少要能回答三个问题:检索到底拉偏了多少?生成到底忠实不忠实?上线前能不能用?
1.2 评测结果受场景影响极大
同样一个RAG系统,放在不同场景里,评测关注点完全是两回事。
我拿两个极端场景对比一下:
- 知识库问答/企业制度问答:答案错了是要背锅的。这类场景最看重忠实性,宁可不答也不要瞎编,用户问到的每一条制度条款都必须能找到对应出处。
- 开放式创意生成辅助:比如品牌营销文案辅助,答案的约束没那么死,用户更在意答案相关性和覆盖度,容忍系统适当发挥。
于是评测之前,必须先想清楚三件事:
- 答案错了,最坏结果是什么?
- 系统遇到不知道的内容,可以明确说“不知道”吗?
- “正确答案”这个标准,到底由谁定义——是知识库原文,还是业务专家的人工判断?
这些问题有了答案,评测指标才能谈得上“有意义”。不加判断直接套指标,等于看着评分卡做体检,但根本不知道体检是给谁做的。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心评测维度
明确了评测服务于决策,就可以搭指标体系了。我惯用的评测框架分三大块:检索质量、生成质量、性能成本。前两者是效果指标,最后一个是工程指标。
2.1 检索质量维度
检索是RAG的地基,检索结果的质量直接决定生成的上限。评测检索时不需要看生成,把知识库“文档-切块-向量化-召回”这条链路单独拉出来测就行。
我自己固定追踪的检索指标是这四项:
| 指标 | 计算方式 | 作用 |
|---|---|---|
| Hit Rate 命中率 | 前k个召回结果里,只要包含至少一个标准答案片段,就算命中,命中问题数/总问题数 | 看检索“有没有捞到对的”,是底线指标 |
| Recall@k 召回率 | 前k个结果中命中的相关片段数 / 标准答案相关的片段总数 | 看“该捞的全捞上来了没有”,对多片段答案的问题尤其重要 |
| MRR 平均倒数排名 | 第一个相关片段在前k结果中的排名倒数,取所有问题的平均 | 看“最相关的片段排得够不够靠前”,对生成效果影响很大 |
| NDCG 归一化折损累计增益 | 按相关等级计算排序质量,等级越高的排越前,分数越高 | 当知识片段存在“部分相关”时,NDCG比单纯的命中更细腻 |
这些指标用文字讲容易绕,我举个具体例子。假设一个内部制度问答知识库,用户问“报销流程是什么”,标准答案是三段内容,分布在三个知识片段里。
- 系统检索返回5个片段,其中有2个是标准答案里的片段,而且第一个相关片段排在第2位
- 那这个样本的Hit Rate就是1(因为命中了)
- Recall@5 = 2/3(标准答案3个片段,捞上来2个)
- MRR = 1/2(第一个相关片段排在第2位,取倒数)
Hit Rate是一种“及格线”思维,它只关心你有没有碰对;Recall@k关心的是把答案信息找全没有;MRR关心的是好内容有没有被顶到前几名。我一般至少同时盯住Hit Rate和MRR,因为只要相关片段排在召回结果的最后面,生成长上下文时大概率会被截断扔掉,检索等于白干。
2.2 生成质量维度:RAG三元组
检索环节看数据,生成环节看内容。生成质量评测最出名的框架是RAGAS的“三元组”概念,也就是把生成结果拆成三个可独立打分的维度:
| 维度 | 评估问题 | 对应的问题类型 |
|---|---|---|
| 上下文相关性 Context Relevance | 检索出来的上下文,有多少是真正跟用户问题相关的? | 如果我问“报销流程”,检索结果却塞进一堆“差旅标准”,这个维度就会扣分 |
| 忠实性 Faithfulness | 生成的回答,是否严格基于检索到的上下文?有没有编造上下文里没有的内容? | 会直接检测“幻觉”,这是RAG最核心的竞争力指标 |
| 答案相关性 Answer Relevance | 最终回答,是否真正回答了用户提出的问题? | 就算回答写得很通顺,但如果问答对不上,就是答非所问 |
三个指标是环环相扣的:上下文相关性低了,生成端拿到的材料就有杂质,忠实性再好也是忠实于一堆错的东西;忠实性低了,就算答案相关,也是在一条看似通顺但实质编造的路上狂奔。所以三者要一起看,不能单独拿一个说事。
关于这三个指标的打分方式,现在主流是“LLM即评判者”。但用LLM打分有注明要求的坑,我后面第4章详细展开,这里先记一个结论:每个维度都设计成带“判断依据说明”的评分格式,不要让模型只吐一个数字。例如忠实性评分的提示词,要强制模型先列出“回答中每个句子分别对应上下文中哪句话”,再给整条回答打分。这样一来,分数才有追溯性,一个2分你还能回去查为什么是2分。
2.3 还需要关注的性能和成本维度
效果指标之外,工程侧还必须关注三个量:
- 端到端延迟:用户从输入问题到看到回答的总耗时。主要开销在向量检索和LLM生成上。一般做在线服务的目标是首token延迟不超过1-2秒,具体看业务耐性。
- 单次请求成本:LLM按token计费,RAG如果召回片段多、提示词模板臃肿,单次成本会被明显推高,批量上线前必须有预算模型。
- 可用性(高可用):在线链路中检索服务和生成服务任一挂掉,整个对话就不可用。线上评测要专门盯这个,推荐用“历史问题定时回放”的方式做可用性巡检。
性能维度和效果维度往往存在冲突。比如为了Recall@k把召回的TopK从3调到8,效果指标涨了,但生成提示词里的内容大幅膨胀,token开销变高,LLM反而可能被无关内容干扰,出现忠实性下降。这类“效果-成本”的取舍,评测报告里必须标注清楚,别让团队只看单边收益。
3. 评测数据集怎么构建
指标定好了,接下来面对一个更实际的问题:评测数据从哪来?这一步做不好,后面全白搭。
3.1 全人工标注 vs 半自动生成
业内主流方法是构建“用户问题-标准答案片段-相关金标片段”的三元组评测集。构建方式有两类:
- 全人工标注:业务专家读知识库文档,手工写出典型问题,标注标准答案对应的知识片段。质量最高、周期最长,一个百题级数据集,两三个人可能要忙一到两周。
- 半自动生成:基于知识库切片,让LLM批量生成候选问题,再用人工抽检和修正。速度快得多,但会引入“模型自问自答”的偏差,生成的问题往往偏简单、偏直白,缺少真实用户那种口语化、带歧义、指代不清的提问。
我的做法是混合式:先让LLM基于每个知识块生成一批候选问题,做一轮人工筛选修订,再请业务专家补写20%-30%“真实用户会问但模型不见得能生成出来”的刁钻问题。这个比例根据自己的业务决定,但有一条底线——评测集里必须有真实业务的场景,不能完全靠LLM自high。
3.2 问题类型覆盖
评测集绝不是简单地问一百个问题就完事,它的价值全在覆盖面。我整理问题列表时,会强制分类别构造:
- 单跳事实类:直接能在某个知识块里找到答案的问题。例如“公司年假标准是几天”。
- 多跳推理类:需要把多个知识块的信息拼起来才能回答的问题。例如“去年新入职的员工,年假怎么折算”——可能需要综合入职制度、年假制度、折算规则多个文档。
- 否定类与条件判断类:答案是“不满足某个条件”“不符合资格”。这类问题最容易暴露RAG的幻觉,因为模型默认知识库里“有答案”,容易强行编造。
- 近义绕弯类:用户的问题表述跟知识库原文完全不一致。例如知识库写“报销需附带发票原件”,用户却问“票据不拿原件能报吗”。
一个大原则:要让评测集贴近真实用户提问,而不是贴近知识库写法。我见过太多评测集是从文档里复制标题改个问句拼出来的,测出来的分数非常漂亮,一上真实流量全露馅。
3.3 数据规模怎么定
评测集规模不是一个拍脑袋的数字,它应该取决于你对“评估置信度”的要求。一个很粗略的经验法则:
- 20-50个问题:只能做冒烟测试,验证流程通不通
- 100-200个问题:能看出主要趋势,检索和生成的宏观问题能暴露出来
- 300个以上:可以支持按业务分类对比、按问题类型分层分析,也足以支撑前后迭代的差异对比
如果团队管线是每次代码更新都跑全量评测,那么控制在200-300题是性价比最高的区间。再多,一是标注成本吃不住,二是每次全量跑评测消耗的LLM调用费会让人肉疼。
4. 实操过程与核心环节实现
这个环节把理论落成工程。我用的评测流程从静态数据集出发,分三步走:先跑检索评测,再跑生成评测,最后输出带可定位信息的综合报告。
4.1 快速搭一套评测流程
为了确保你能直接上手复现,我给出一个简化但完整的Python示例。我假设你已经把索引准备好了,评测集文件是一个JSON列表,每个样本长这样:
python复制{
"question": "报销发票丢了还能走报销流程吗",
"reference_contexts": ["doc_123_chunk_1", "doc_125_chunk_2"],
"reference_answer": "发票丢失后不能直接走报销流程,需要先补开证明..."
}
先跑检索评测。这一步不管生成,只看向量检索能不能从库里把 reference_contexts 对应的片段捞上来:
python复制import json
import numpy as np
# 假设你封装好了这几个函数:embed_query, search_index
# search_index 返回 [(chunk_id, score), ...] 按相关性降序
from my_rag_engine import embed_query, search_index
def evaluate_retrieval(eval_set, k=5):
hit_count = 0
reciprocal_ranks = []
recall_records = []
for sample in eval_set:
q_vec = embed_query(sample["question"])
results = search_index(q_vec, top_k=k)
retrieved_ids = [chunk_id for chunk_id, _ in results]
gold_set = set(sample["reference_contexts"])
hits = [cid for cid in retrieved_ids if cid in gold_set]
hit = 1 if hits else 0
hit_count += hit
# MRR: 第一个命中的排名倒数
for rank, cid in enumerate(retrieved_ids, start=1):
if cid in gold_set:
reciprocal_ranks.append(1 / rank)
break
else: # 没命中时MRR记0
reciprocal_ranks.append(0)
# Recall@k
recalled = len(hits) / len(gold_set)
recall_records.append(recalled)
metrics = {
"hit_rate": hit_count / len(eval_set),
"mrr": float(np.mean(reciprocal_ranks)),
"recall_at_k": float(np.mean(recall_records)),
}
return metrics
if __name__ == "__main__":
with open("eval_set.json", "r", encoding="utf-8") as f:
dataset = json.load(f)
result = evaluate_retrieval(dataset, k=5)
print(json.dumps(result, ensure_ascii=False, indent=2))
这段代码看起来不复杂,但注意几个实操细节:
reference_contexts一定要是“知识块ID”而不是原始文本。评测完你才能精确定位是哪一块文档没被召回。- MRR循环里
break的位置非常关键,它只记录第一个相关片段的排名,别把所有命中都算进去。 - 如果你的检索结果里没有命中任何金标片段,MRR记0,不要跳过,否则高估系统能力。
4.2 生成质量评测实操
生成质量评测的核心是调LLM作为裁判。你可以用开源的RAGAS框架,也可以自己写一个可解释的评分提示词。我推荐自己维护评分逻辑,因为评价标准会随业务变化,封装在框架里反而不灵活。
下面是“忠实性”评分器的一个极简实现。它的思路是:先抽取回答中的陈述句,再把每个陈述句与检索上下文比对,计算有多少陈述能被上下文支撑:
python复制from openai import OpenAI
import json
client = OpenAI() # 配置你自己的API
FAITHFULNESS_PROMPT = """
你是一个严谨的事实核查员。用户问题、检索上下文、模型回答如下。
【用户问题】
{question}
【检索上下文】
{context}
【模型回答】
{answer}
请按步骤执行:
1. 从“模型回答”中拆分出所有独立的陈述句,每行一句,编号列出。
2. 逐句判断:该陈述句是否能被“检索上下文”中的内容直接支撑。
能支撑 -> 标注 SUPPORTED
不能支撑 -> 标注 UNSUPPORTED
上下文未提及该信息,但无法判断对错 -> 标注 UNVERIFIABLE
3. 计算忠实性分数 = SUPPORTED语句数 / (总陈述句数 - UNVERIFIABLE语句数)。
如果分母为0,则分数为0。
4. 只输出JSON:
{{"statements": [{{"text": "...", "verdict": "SUPPORTED|UNSUPPORTED|UNVERIFIABLE"}}], "faithfulness_score": 0.75}}
不要输出任何其他内容。
"""
def score_faithfulness(question, context, answer, model="gpt-4o-mini"):
resp = client.chat.completions.create(
model=model,
messages=[{
"role": "user",
"content": FAITHFULNESS_PROMPT.format(
question=question, context=context, answer=answer
),
}],
temperature=0,
response_format={"type": "json_object"},
)
data = json.loads(resp.choices[0].message.content)
return data["faithfulness_score"], data["statements"]
几个关键点,都是实际操作中踩过之后总结出来的:
- temperature必须固定为0。评测最怕波动,同一个样本跑两遍得分差0.3以上,整个评测结果就失去可信度了。
- 让评审模型输出“逐句判定”而不是一个总分。这样分数可追溯,任何一次低分都能立刻看到是哪句话出了幻觉。
- 分类里加上UNVERIFIABLE。真实场景中,模型回答“根据上下文,这里没有提到……”是完全合理的。如果把这种都算不支持,会发现分数永远上不去。
同理,上下文相关性评分可以设计成“先概括上下文每句话的主题,再判断与问题的关联度”,答案相关性评分可以设计成“基于问题逐要点核对答案是否覆盖”。总体原则是:每个维度的评分都要引入中间推理过程,而不是让模型当黑盒打分器。
4.3 用评测定位问题:先查检索,再查生成
评测的意义不全在“打分数”,更在于定位瓶颈。我每次拿到评测结果,都是按固定的排查顺序走:
- 先看检索指标。如果Hit Rate都不到0.7,肯定是检索侧出了问题——chunk切法不合理、embedding模型选型不对、query理解差等。此时不要去看任何生成分数,因为生成侧拿到的材料本来就不对。
- 再看上下文相关性。如果Hit Rate还行但上下文相关性低,说明检索出了“相关的块”,但也混进了大量“无关的块”,这个时候优先调TopK数量、重排序策略或相似度阈值。
- 最后看忠实性和答案相关性。检索都正常,但忠实性低,问题在生成侧:可能是提示词对模型约束不够,也可能是上下文太长导致模型“看不过来”。若忠实性高但答案相关性低,那就是生成出来了但没回答用户问题,多半要调提示词结构和模型选择。
上面这个顺序我屡试不爽。很多人一上来就看最终答案错没错,然后去调生成提示词,改了半天发现是召回根本没有正确答案,纯属南辕北辙。
4.4 评测报告里一定要附带错误案例
一份合格的评测报告,除了表格里的各种指标,还必须包括一批典型错误样本。我通常每个维度都会挑分数最低的5-10个样本,逐条标注错误类型:
- 检索遗漏关键片段
- 检索前片段被无关内容挤占
- 生成幻觉,编造了不存在的条款
- 生成遗漏关键条件(答了表面,漏了例外)
- 上下文过长,关键信息被截断
这些错误案例的价值远大于分数本身,因为团队拿着案例,才能直接知道下一步改什么。
5. 常见问题与排查技巧实录
最后分享一些实操中高频踩的坑,按频率排个序。每条都是我或我认识的人在项目里真实遇到过的。
5.1 LLM打分稳定性和幻觉问题
LLM评测最经典的坑:今天跑91分,明天跑85分,内容没改,只是模型版本悄悄变了。应对手段有几条:
- 锁定评审模型版本,每次跑评测用同一个模型ID。
- 评测批次之间不做对比时,别随便切模型。
- 对关键数据集,可以跑多轮取平均,或者用多个模型评分取交叉验证。
- 定期抽取一部分样本做人工复核,确认LLM评审没有系统性偏好。
还有一种情况是评审模型自身产生幻觉——把“没有的条款”脑补成“上下文里有”。这个问题目前没有完全根治的办法,只能靠“强制引用原文片段”来缓解。评分提示词里加上一句“判断该陈述是否被支撑时,必须引用上下文中原文片段作为证据”,能显著降低误判。
5.2 数据分布偏差问题
有一个现象很常见:评测集是知识库文档自动生成的,所以问题风格跟知识库写作风格高度一致,导致真实用户一进来分数暴跌。
解决办法是构建“双评测集”:一个是内部自动生成的冒烟集,用于快速回归;另一个是从真实日志或业务专家处沉淀的混淆集,用于定期验收。以真实用户问题为准,不要拿自动评测集的分数当对外汇报的数据。
5.3 指标“全绿”但业务上还是不行的坑
这是最伤的一种情况:评测分数都90+,业务方还是说不好用。排查后发现问题往往在评测设计和业务需求的错位:
- 评测关注“平均回答质量”,但业务关心“高危问题不出错”。
- 评测里都是完整清晰的提问,真实用户却有很多指代模糊、问一半、甚至讲错名词的口语问题。
- 业务要求答不出时主动说“不知道”,但评测指标把这种合理的拒答判成了低分。
解决这个问题的核心还是回到第1章——评测前必须跟业务方对齐“错误代价”。高危场景要把权重倾斜到忠实性上,而不是整体平均数。
5.4 评测脚本的性能与成本控制
评测集200题跑全量,如果用GPT-4级别模型评分,一次全量测评可能烧掉几十甚至上百美元。我常用的降本方法:
- 日常开发迭代用轻量模型(如gpt-4o-mini或本地部署的中小模型)初筛,只有上线前验收才上强模型。
- 复用检索评测结果。跑过检索评测后把召回片段缓存下来,生成评测直接读缓存,不要每跑一遍生成就重做一次检索。
- 按错误率分层抽样,先全量跑一遍,分数异常低的类别才拉大样本细看,避免所有类别都堆相同规模的样本。
5.5 回归机制必须搭起来
评测不是一次性工作。RAG项目最大的特性是“牵一发动全身”:你以为只是改了一个prompt,结果检索评分也跟着变了;你以为只是换了embedding模型,结果忠实性全面下降。所以每次改动至少要跑一遍全量评测,并把历史分数记录成表格做趋势对比。团队里至少要有一个人对评测数据的分布和变化负责任,否则这个项目的效果就是一团迷雾。
一点个人的体会
做评测这几年,我越来越觉得它像一个翻译器,把业务方模糊的“好不好用”翻译成工程团队听得懂的“哪一环出问题”。我自己反复踩过的一个坑,是拿到总分后急着调参数,却忘了先看错误案例分布——这就像体检完只看了总胆固醇,却不看低密度和高密度的拆分。如果你现在正为RAG效果不稳定发愁,我的建议很简单:先把检索指标和生成指标拆开跑一轮,把所有低分样本翻一遍,你会比自己想象的更快找到病灶。
