ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

RAG评估方法实战:从RAGAS指标到零标注LLM评委的完整指南

RAG评估方法实战:从RAGAS指标到零标注LLM评委的完整指南 简介面向AI应用开发者与算法工程师聚焦RAG检索增强生成系统质量评测围绕索引、检索、生成三个核心环节系统梳理准确率、忠实度、召回率等关键指标的定义与考量方式并区分人工评估和自动评估两类方法的适用场景为构建完整评估闭环提供方法论参考。压缩包共3个文件index.html为可离线打开的说明主页.inscode为项目运行提供配置入口.gitignore管理版本忽略规则整体仅5KB结构精简适合快速查看。目前已有193人学习下载适合刚接触RAG评估的开发者或需要搭建评测流程的团队。通过该源码可直观了解RAG评估的完整实施路径包括评估前准备、评估中操作与评估后数据分析同时能借助LangSmith、Langfuse、RAGAS等工具快速定位检索质量短板理解召回率对生成效果的直接影响进而依据示例调整索引策略与生成参数优化系统表现。1. RAG评估方法为什么是知识库项目的第一个黑匣子从“看着还行”到“知道哪坏了”做RAG知识库问答的人大概都经历过这个阶段本地跑几个测试问题回答“看着还行”一上线用户就说答非所问。问题出在哪可能是检索回了无关文档可能是大模型没按检索内容回答也可能评估手段本身就把问题带偏了。RAG评估方法要解决的就是这件事——把“回答好不好”拆成检索质量和生成质量两个环节用可复现的指标给每个环节打分让优化不再靠猜。这篇文章给你一套能直接落地的评估方案从开源框架RAGAS的四个核心指标和最小可运行的评估代码到零标注场景下用大模型当评委再到知识库场景的分层评估和五个高频踩坑记录每一步都有能照抄的源码。适合正在做知识库问答、智能客服或者想给RAG项目补上评估体系的工程师。2. 用RAGAS跑通第一版RAG评估四个核心指标与最小源码复现2.1 RAGAS四个指标在算什么两两对应的评估逻辑RAGAS是目前用得最多的开源RAG评估框架之一代码仓库结构清晰通过LangChain接口统一接入模型和向量库改造起来不费劲。它的默认指标族里有四个最常用faithfulness忠实性、answer_relevancy答案相关性、context_precision上下文精确率、context_recall上下文召回率。这四个指标不是随意凑出来的它们恰好两两对应RAG链路的两个环节。faithfulness管生成端衡量回答里的每个陈述能不能在检索回来的上下文里找到证据。实现思路是让LLM把回答拆成若干陈述句再逐句判断该陈述是否被context支持最后算出被支持的比例。低分说明模型在自由发挥没按检索内容答这在幻觉问题里是最典型的信号。answer_relevancy也管生成端但关注的是“回答到底在不在回答这个问题”。实现方式是让LLM基于回答反向生成若干问题再计算生成问题与原始问题的embedding相似度。如果回答写成一堆正确的废话与问题不相关这个指标会明显偏低。在rag实战里我一般拿它当“跑题检测器”用。context_precision和context_recall管检索端。precision衡量检索结果里有用片段是否排在前面recall衡量检索回来的内容覆盖了标准答案里多少要点。需要注意的是context_recall必须依赖ground_truth参考答案而后三个指标不需要。所以在评估集设计时至少要保证有一部分样本带标准答案否则检索端永远缺一条腿。这四件事搞清楚后面看分数才知道该调检索还是调生成。2.2 最小可运行评估脚本从数据集构造到指标输出先准备环境。RAGAS版本差异很大0.1.x和0.2.x的API不兼容我这边建议直接锁版本免得装上最新版后发现函数签名全变了。# 建议 Python 3.10 pip install ragas0.1.7 langchain-openai0.1.6 datasets2.19.0下面是完整的评估脚本。这里用OpenAI的模型当评委和embedding模型因为RAGAS默认prompt质量对模型指令跟随能力有一定要求OpenAI模型开箱即用。如果你的数据不能出内网可以把llm和embeddings换成本地部署的模型接口不变后面避坑章节会专门说这件事。import os from datasets import Dataset from ragas import evaluate from ragas.metrics import ( faithfulness, answer_relevancy, context_precision, context_recall, ) from ragas.llms import LangchainLLM from ragas.embeddings import LangchainEmbeddings from langchain_openai import ChatOpenAI, OpenAIEmbeddings os.environ[OPENAI_API_KEY] sk-你的key # 评估集每个样本是一个dict字段 # question用户问题 # answerRAG系统实际给出的回答 # contexts检索回来的文档片段列表 # ground_truth标准答案context_recall 必须要这个字段 eval_data { question: [ 员工入职满一年后享有多少天年假, 公司对远程办公有什么规定, ], answer: [ 根据公司考勤制度员工入职满一年后每年享有5天带薪年假。, 公司允许每周最多两天远程办公需要提前一天在OA系统提交申请。, ], contexts: [ [ 公司考勤制度规定员工入职满一年后每年享有5天带薪年假满三年增至10天。, 年假需在自然年度内使用逾期不累计。, ], [ 远程办公管理办法明确员工每周可申请最多两天远程办公。, 远程办公申请需提前一天通过OA系统提交经直属主管审批后生效。, ], ], ground_truth: [ 入职满一年后每年享有5天带薪年假满三年为10天。, 每周最多两天远程办公提前一天在OA提交申请主管审批。, ], } dataset Dataset.from_dict(eval_data) # 指定评估用的LLM和embedding模型 gpt4o LangchainLLM(llmChatOpenAI(modelgpt-4o, temperature0)) embeddings LangchainEmbeddings( embeddingsOpenAIEmbeddings(modeltext-embedding-3-small) ) result evaluate( dataset, metrics[faithfulness, answer_relevancy, context_precision, context_recall], llmgpt4o, embeddingsembeddings, ) print(result) print(faithfulness:, result[faithfulness]) print(answer_relevancy:, result[answer_relevancy]) print(context_precision:, result[context_precision]) print(context_recall:, result[context_recall])这段代码的核心逻辑就是把你的数据整理成Dataset格式然后一次性丢给evaluate函数。数据集里的contexts字段必须是列表的列表每一个内层列表代表一个样本检索回来的所有文档片段。ground_truth字段只在计算context_recall时用到其他三个指标不需要它但建议统一填上省得后面补跑。参数上需要注意两个地方。第一个是temperature必须设成0LLM评委如果带随机性同一批评估集每次跑出来的分数都会不一样你就没法判断改动前后的差异是真实优化还是随机波动。第二个是OpenAIEmbeddings默认模型是text-embedding-ada-002但如果你是新账号建议显式指定text-embedding-3-small效果和性价比都更好。2.3 指标分数怎么读一份对照表与两种误读方式跑完评估拿到四个浮点数接下来最容易出错的就是解读。RAGAS官方没给一套放之四海皆准的合格线因为它依赖你的数据难度和领域复杂度。我根据自己的落地经验整理了一份参考阈值供你起步用。指标关注环节评分范围建议合格线低分说明faithfulness生成端0-1≥0.8模型在自由发挥没有严格按检索内容回答answer_relevancy生成端0-1≥0.7答非所问或回答过于泛化context_precision检索端0-1≥0.6有用文档排在后面被无关片段挤占了位置context_recall检索端0-1≥0.8相关文档没被检索出来召回列表不完整这里有两个误读方式要警惕。第一种是拿单次分数直接下结论。LLM评委指标在不同样本之间的方差不小一次跑出0.75和0.78可能没有统计意义上的差别。正确的做法是固定评估集分别跑改动前后的两个版本看相对变化。第二种是只看总分忽略环节指标。RAGAS默认会算四个指标的平均值一个环节的低分会被其他指标拉平比如context_recall只有0.4但其他三个是0.9总分看起来还行实际检索端已经崩了。所以在报告里我一般把四个指标分开列不合并。3. 零标注数据怎么做RAG评估LLM即评委的最小实现与可信度检验3.1 为什么评估可以没有人工标注LLM评委的适用范围很多团队在开始做RAG评估时遇到的第一道坎是没有标注数据每条都找人写标准答案又太慢。RAG评估方法里有一支路线专门解决这个问题——让LLM来当评委。它的逻辑不复杂给定问题、检索上下文和系统回答让一个指令跟随能力足够强的模型按照评分标准打分数再给出理由。它的适用范围要比很多人以为的窄。LLM评委适合打分维度相对明确的任务比如“回答是否被上下文支持”“是否针对问题作答”。这类判断用自然语言定义清楚之后大模型能做得相当稳定。但它不适合需要领域专家知识的场景比如医疗诊断结论是否准确、法律条文引用是否恰当。这类任务里LLM很容易被一段看起来权威但实际错误的上下文带偏。另外要注意的是LLM评委评估的是“回答质量”不是“事实正确性”。它只能判断回答和给定的检索上下文是否一致不能验证context里的内容本身对不对。如果你的知识库源头就有错误LLM评委不但不会发现还会把基于错误内容的回答打成高分。这一点在引入评估体系之前就要讲给团队听免得后续对评估结果产生错误信任。3.2 最小LLM评委实现打分函数与一致性验证这里我给出一个可以直接跑的脚本。它不依赖RAGAS只用LangChain调用一个模型你可以在任何RAG系统上套用。评分维度我选了正确性、完整性、相关性三个每个维度0到5分外加一个总评和理由。import json from langchain_openai import ChatOpenAI def llm_judge(question, answer, contexts, modelgpt-4o-mini): 让LLM当评委给一条RAG问答结果打质量分。 contexts: 检索回来的文档片段列表 llm ChatOpenAI(modelmodel, temperature0) context_text \n.join([f[{i1}] {c} for i, c in enumerate(contexts)]) prompt f你是RAG系统评估员。请基于检索上下文评估回答质量。 问题{question} 检索上下文 {context_text} 回答{answer} 评估要求 1. 正确性0-5回答中的关键信息是否在检索上下文中找到依据。 2. 完整性0-5是否完整回答了问题有没有遗漏检索上下文已覆盖的要点。 3. 相关性0-5回答是否切题有没有答非所问。 只输出JSON格式不要输出其他内容 {{correctness: 0, completeness: 0, relevance: 0, overall: 0, reason: 一句话理由}} resp llm.invoke(prompt) # 兼容代码块包裹的返回 content resp.content.strip() if content.startswith(): content content.split(\n, 1)[1].rsplit(, 1)[0] return json.loads(content) # 示例调用 if __name__ __main__: result llm_judge( question员工入职满一年后享有多少天年假, answer每年享有5天带薪年假。, contexts[公司考勤制度规定员工入职满一年后每年享有5天带薪年假。], ) print(result)这个函数的prompt设计是核心。正确性、完整性、相关性三个维度分别对应RAG的忠实度和相关性问题但比RAGAS的四个指标更轻量适合快速批量打点。输出强制JSON格式是为了方便后续做统计和一致性验证reason字段用来定位低分样本非常有用——你可以把它直接打印到日志里人工复核。一致性验证是LLM评委方案里绝对不能省的一步。做法很简单同一批数据跑两次计算两次评分的秩相关性。如果你换不同的模型当评委也可以用来对比两个评委的一致性。from scipy.stats import spearmanr eval_pairs [ (问题1, 回答1, [上下文1]), (问题2, 回答2, [上下文2]), ] scores_1 [llm_judge(q, a, c)[overall] for q, a, c in eval_pairs] scores_2 [llm_judge(q, a, c)[overall] for q, a, c in eval_pairs] corr, p_value spearmanr(scores_1, scores_2) print(f两次评分一致性 spearman{corr:.3f}, p{p_value:.4f})如果两次评分的spearman相关系数低于0.7说明评委模型的判断不稳定这时候不要急着拿评估结果去指导优化先换模型或者调整prompt。还有一种情况是p值不显著那说明评估集太小样本量不够支撑相关分析需要先扩充数据集。3.3 评委可信度的边界何时该怀疑分数LLM评委最大的问题不是它“准不准”而是你很难知道它什么时候准、什么时候不准。这本身就是个黑匣子。我的经验是设置几条硬性怀疑规则领域术语密集的样本、包含否定和比较关系的样本、以及回答非常短少于20个字的样本分数都需要额外小心。领域术语密集的样本比如法律或医疗内容LLM判断“上下文是否支持回答”时容易被术语的存在感带偏把不相关但含有相同术语的内容当作证据。否定关系是另一个高频失分点模型对“不适用”“除外”这类表达的理解不够稳定可能把一个正确排除的场景判定为矛盾。解决办法是做分层抽检。按分数区间把样本分成高分组和低分组的分布每组随机抽10%到20%人工复核。如果人工复核的通过率低于80%说明评委尺度和你的预期不一致需要回头调整prompt里对评分标准的描述或者换成能力更强的模型。这一步不能省它决定了评估结果有没有说服力。4. 知识库RAG分层评估检索质量与生成质量分开测4.1 端到端分数为什么定位不了问题先拆链路很多团队做完第一轮RAG评估后发现分数很低但不知道改哪里。原因在于端到端的分数是把检索和生成揉在一起看它只能告诉你“现在的系统不行”不能告诉你“为什么不行的”。举个常见的例子用户问“报销流程”系统检索回来的contexts里全是差旅标准模型在无米下锅的情况下硬生成了一段流程说明。这时候faithfulness会低但问题根源在检索端。所以评估方案在知识库场景下要分两层检索侧评估和生成侧评估。检索侧回答“有没有检索到对的文档”生成侧回答“有没有把对的文档用好”。分开测的好处是你拿到低分时能立刻定位到链路环节在rag实战中这直接决定了你的排查效率——是先换embedding模型还是先改prompt完全由分层指标决定。具体做法是把一次评估拆成两段流水线。第一段只测检索器把用户问题丢进去拿回top-k文档列表然后跟标准答案做对比。第二段把检索结果喂给生成模型得到最终回答再做生成质量评估。这两段的评估集可以共用但指标和计算逻辑完全独立。4.2 检索侧三个指标命中率、MRR与NDCG的源码实现检索侧指标全部基于一个核心数据正确答案在检索结果中的排序。所以在搭建评估集时你要为每个问题标注“哪个文档片段是能回答这个问题的”。有了这个标注命中率、MRR、NDCG就都能算出来。import math def hit_rate(rankings, k5): rankings: 每个query的正确答案在检索结果中的排名从1开始未命中记0 返回 Hitk代表前k个结果里包含正确答案的样本比例 hits [1 for r in rankings if 0 r k] return sum(hits) / len(rankings) def mrr(rankings): MRR第一个正确答案排名的倒数未命中计0取所有query平均 关注的是“第一个对的排在多前面” return sum(1.0 / r for r in rankings if r 0) / len(rankings) def ndcg_at_k(relevances, k5): relevances: 单个query的检索结果相关性列表0或1按检索顺序排列 返回 NDCGk衡量排序质量考虑多个相关文档的位置 dcg sum(rel / math.log2(idx 2) for idx, rel in enumerate(relevances[:k]) if rel) ideal sorted(relevances, reverseTrue)[:k] idcg sum(rel / math.log2(idx 2) for idx, rel in enumerate(ideal) if rel) return dcg / idcg if idcg 0 else 0.0 # 示例5个query的正确答案排名 rankings [1, 3, 7, 0, 2] print(fHit5: {hit_rate(rankings, k5):.4f}) print(fMRR: {mrr(rankings):.4f}) # 示例单个query的检索结果相关性[1,0,1,0,0]表示第1和第3个文档相关 print(fNDCG5: {ndcg_at_k([1,0,1,0,0], k5):.4f})这三个指标放在一起用能看出检索器的真实表现。命中率只看有没有召回MRR看第一个正确答案的位置NDCG则进一步考虑了多个相关文档的排序质量。如果你的知识库每个问题往往对应多个相关片段NDCG的参考价值比MRR更高因为它不会因为第一个相关文档排第1就忽略后面漏掉的第3个相关文档。实际使用里我一般对embedding检索器要求Hit5不低于0.7MRR不低于0.5达不到这个水平就先不要动生成环节的配置。4.3 生成侧两个指标引用准确率与幻觉率的统计方法生成侧评估在知识库场景里要抓住两个点引用准确率和幻觉率。前者衡量回答里的引用是否真的能对应到检索回来的文档后者衡量回答内容有多少在检索上下文里找不到依据。这两个指标比单纯的BLEU或ROUGE更适合RAG因为RAG的生成是开放式的n-gram重叠统计不敏感。引用准确率的统计依赖RAG系统在生成时输出引用标记。如果系统没有做引用可以退而求其次用回答中的关键实体和上下文做匹配测试。幻觉率的统计相对直接把回答逐句拆开检查每句话能否在contexts里找到依据找不到依据的句子占比就是幻觉率。这个判断可以复用前面写的llm_judge函数把prompt换成逐句判断。from langchain_openai import ChatOpenAI def hallucination_rate(question, answer, contexts, modelgpt-4o-mini): 逐句判断回答是否在检索上下文中有依据返回幻觉句比例 llm ChatOpenAI(modelmodel, temperature0) sentences [s.strip() for s in answer.split(。) if s.strip()] checked [] for sent in sentences: prompt f判断下面的回答句子是否能被检索上下文支持。 检索上下文 {chr(10).join(contexts)} 回答句子{sent} 如果能找到支持证据输出SUPPORTED否则输出NOT_SUPPORTED。只输出一个词。 resp llm.invoke(prompt).content.strip() checked.append((sent, resp)) not_supported sum(1 for _, label in checked if label NOT_SUPPORTED) return not_supported / len(checked), checked rate, details hallucination_rate( question公司年假几天, answer员工入职满一年后享有5天年假。公司还提供额外3天福利假。, contexts[公司考勤制度规定员工入职满一年后每年享有5天带薪年假。], ) print(f幻觉率: {rate:.2f}) print(details)幻觉率指标在落地时有个坑中文按句号切分会把顿号和分号割裂的句子片段当作独立句子导致判断失真。所以切分逻辑要根据你数据里的标点习惯调整至少要处理句号、分号、感叹号三种边界。另外LLM对“SUPPORTED”和“NOT_SUPPORTED”的判断在边缘样本上可能不自信建议对断言标签的置信度要求放宽只要模型输出格式不一致的样本就计入人工复核清单而不是直接当作SUPPORTED或NOT_SUPPORTED处理。5. RAG评估避坑指南五个让分数失真或翻车的配置错误5.1 评估集只有30条分数波动到不敢用现象评估集只有30条左右跑两次RAGAS分数差出0.1甚至更多完全不知道改动到底有没有效果。原因LLM评委指标本身有随机性评估集小的时候个别样本的剧烈波动会直接拉偏平均值。30条样本连统计意义都很勉强更别提做分维度分析了。解决评估集至少扩充到50到80条并且按场景分层——简单事实类、多跳推理类、否定条件类各占一部分。这样分数才稳定也才能看出不同场景下的能力短板。如果确实凑不到这么多条就接受分数只能用来粗筛不能用来做精确对比。5.2 上下文截断让context_precision虚高现象RAG链路里给LLM的contexts做了截断只保留前3段但评估时喂给RAGAS的是完整的检索结果。结果context_precision得分很高实际产品中用户看到的内容并不是那段完整结果。原因评估数据里的contexts必须和线上实际传给生成模型的上下文一致。评估时用了未截断的版本等于给模型泄题了。解决把RAG日志里真正传给LLM的那份contexts记录下来评估时直接用这份数据。如果logs里没有完整记录上下文就重新跑一遍检索再截断确保评估数据链路和生产一致。这件事在避坑优先级里排第一因为它会让所有下游指标失真。5.3 中文数据跑RAGAS默认prompt是英文的现象中文数据跑RAGAS报一些奇怪的解析错误或者跑出的answer_relevancy非常低明显不合理。原因RAGAS 0.1.x的默认prompt是英文写的中文数据喂进去虽然能跑但embedding模型和LLM之间的交互可能不稳定尤其在生成反向问题时容易出格式问题。另外如果embedding模型本身不支持中文answer_relevancy计算出来的相似度就会偏离真实语义。解决embedding模型必须换成中文效果好的比如BGE系列或text-embedding-3-small。RAGAS的prompt可以通过修改metric的属性来替换成中文模板。还有一个临时做法是先把问题翻译成英文跑评估但实际工程里不建议这么做因为翻译本身会引入误差而且没法持续维护。5.4 本地小模型当评委分数系统性偏低现象用7B或13B的本地模型当评委所有指标普遍比GPT系列低0.2到0.3放到同一批测试问题里看起来系统“变差了”。原因小模型的指令跟随和推理能力有限对“是否支持”“是否相关”这类判断不够稳定更容易输出负面的判断。这不是系统变差了是尺子不准。解决如果数据不能出内网非要本地模型当评委有两个方向。第一选更大参数量的模型至少30B以上或者用专门微调过的评估模型。第二在prompt里加few-shot示例把典型场景的评分结果展示给模型看让它在有限的推理能力下至少保持判断格式的统一。本地模型当评委的结果必须抽人工复核这个流程比用API模型更严格。5.5 ground_truth和question对不上指标直接失真现象context_recall分数很低但人工看检索结果觉得质量还行检查发现是ground_truth写的内容和question对不上。原因评估集在构造时copy错了标准答案或者标准答案是按另一个问法写的。context_recall计算时拿这条不匹配的ground_truth去衡量context的覆盖度分数自然失真。解决在建评估集时做一次批量一致性检查。简单做法是把question和ground_truth拼接丢给LLM让它判断是否匹配返回不匹配的样本全部打回重写。别小看这一步知识库评估集的脏数据率往往比想象中高而这种错误在分数上几乎没有明显信号。6. 评估结果反哺RAG链路一个可以直接抄的调参优先级6.1 先看短板指标再动对应环节拿到一份分层评估报告之后直接按下面的优先级去调不需要从零开始折腾整个链路。context_recall低于0.7先改检索侧。常见做法是给知识库分段加上更细的chunk策略或者换embedding模型。我在一个合同问答项目里只把chunk从固定512字符改成按条款边界切分context_recall就从0.55提到了0.78效果比换模型更明显。优先查chunk边界再查embedding选型最后才考虑要不要加reranker。context_precision低而recall正常问题出在排序。先看是不是检索结果里混杂了太多无关片段加一个轻量reranker通常能解决。如果不想引入新组件也可以调整检索参数里相似的候选数量让相关性阈值过滤掉尾部噪声。faithfulness低问题在生成端。最直接的做法是改prompt明确要求“只根据提供的上下文回答不要补充额外信息”并约束答案格式。如果还压不住幻觉可以考虑缩小检索结果的输入量减少模型发挥的空间。我见过一个案例把top-k从5调到3faithfulness从0.72涨到0.85代价是recall小幅下降但整体可接受。answer_relevancy低除了检查生成侧是否跑题还要看问题本身是否需要改写。用户问题表述模糊时先加一个query改写模块把口语化问题改写成检索友好的提问很多跑题问题会自然消失。6.2 把评估集变成回归测试集救了一次大版本升级我踩过一次印象很深的坑。当时升级了知识库里的embedding模型人工测了二十几个问题感觉效果变好了就打算直接上线。刚好有一份现成的50条评估集顺手跑了一遍发现context_recall从0.76降到了0.63。后来查原因是新模型在合同条款这类长文本上表现不佳而人工测试的样本恰好没覆盖这类内容。那一次如果没有评估集兜底线上知识库就翻车了。从那以后我的习惯是每份评估集除了用来改参数还当作回归测试集固定存下来。每次改检索配置、换模型、改prompt都跑一遍全套指标和上一次的结果做对比只接受短板指标不变差、目标指标有提升的改动。这个过程不需要自动化平台一个脚本加一个CSV文件就够用。评估集本身也要定期更新每月往里面加最近真实用户问过的问题尤其是那些曾经失分的样本。RAG评估方法的落地难点从来不在指标计算而在能不能持续维护一套可信的评估数据。把这份数据当资产对待你的RAG系统每一次改动就都有迹可循不再是靠感觉上线。希望这些经验和源码能帮你迈过评估这道坎。本文还有配套的精品资源点击获取
返回列表