1. 为什么 RAG 检索效果必须做量化测评做 RAG 项目最怕的一种情况是演示的时候效果惊艳上线之后用户一问稍微偏一点的问题就开始胡说八道而你完全不知道问题出在检索环节还是生成环节。我见过太多团队把精力全砸在换模型、调 prompt 上结果折腾了两周检索召回率其实只有 40% 出头生成模型再强也救不回来。RAG 的本质是先检索、后生成检索环节决定了生成模型能看到什么上下文。检索没做好后面全是空中楼阁。所以量化测评的核心目的只有一个把感觉效果还行变成我知道每个环节的具体数字并且知道改哪里能提升多少。这套测评体系能解决三个具体问题。第一定位瓶颈。到底是切分粒度不对、embedding 模型选错了还是召回策略太单一量化指标能直接告诉你。第二验证迭代。每次调整参数比如 chunk size 从 512 改成 256你需要一个客观数字来判断是变好了还是变差了而不是靠抽几个 case 拍脑袋。第三设定上线门槛。检索召回率低于某个阈值就不该上线这个阈值必须靠测评数据来定。适合读这篇的人有三类正在做 RAG 项目但效果不稳定的开发者、准备搭建 RAG 测评体系的技术负责人、以及想搞清楚检索到底该怎么评估的算法同学。不需要你是测评专家但至少要跑通过一个基础的 RAG 流程知道 embedding、向量库、召回这些概念大概是怎么回事。我下面讲的这套流程是我在几个实际项目里反复打磨出来的从构建测评集到指标计算到问题排查每一步都有可复现的操作。指标实现部分我会给可直接跑的代码踩坑部分全是我自己踩过的能帮你省掉至少一周的试错时间。2. 测评体系整体设计与核心思路拆解2.1 测评到底测什么三层指标框架很多人一上来就问RAG 准确率多少这个问题本身就问错了。RAG 是一个流水线准确率是端到端的结果但你要定位问题就必须拆开看。我习惯把测评分成三层第一层是检索层指标衡量该找的文档有没有被找出来。核心指标是 RecallK召回率和 MRR平均倒数排名。RecallK 回答的是前 K 个结果里有没有包含正确答案MRR 回答的是正确答案排在第几位。这两个指标直接反映检索质量。第二层是生成层指标衡量拿到正确上下文后模型答得对不对。常用的是 Faithfulness忠实度答案是否完全基于检索到的内容和 Answer Relevancy答案相关性。这一层用 LLM 做裁判LLM-as-a-Judge比较常见。第三层是端到端指标衡量用户最终拿到的答案对不对。可以用 Exact Match精确匹配或者人工评分。这一层最贴近真实体验但成本也最高。为什么要分三层因为不同层的问题对应不同的修复手段。检索层差你去调切分和 embedding生成层差你去调 prompt 和模型端到端差但前两层都好那可能是测评集本身有问题。不分层你就是在盲人摸象。2.2 方案选型为什么我不用现成的测评框架市面上有 RAGAS、TruLens 这类现成的测评框架功能很全。但我在实际项目里最终选择了自建轻量测评流程原因有三个。第一可控性。现成框架的指标计算逻辑是黑盒当你的业务场景比较特殊比如法律文档检索对召回顺序极度敏感你需要能改指标定义。自建的话每个公式都是你自己写的出了问题能查。第二依赖成本。RAGAS 这类框架依赖较多版本更新也快有时候一个小版本升级就导致指标计算方式变了历史数据没法对比。自建的核心逻辑就几百行代码稳定可控。第三测评集适配。现成框架通常假设你有标准格式的 QA 对但实际项目里你的测评集可能是从业务日志里挖出来的格式五花八门。自建流程可以灵活适配。当然如果你只是想快速跑个 baseline用 RAGAS 也没问题。但一旦要长期迭代我建议还是自建一套哪怕简陋一点至少你完全掌控。2.3 测评集构建这是最容易被低估的环节我见过最离谱的情况是团队花了两周调模型最后发现测评集里有一半的标注是错的。测评集的质量直接决定了测评结果的可信度这一步绝对不能糊弄。测评集的核心结构是三元组问题query、标准答案ground truth answer、相关文档 ID 列表relevant doc ids。注意第三个元素很多人只标答案不标文档导致检索层指标根本没法算。相关文档 ID 是检索测评的基石。构建方式有三种我按推荐程度排序从真实业务日志挖掘最贴近实际分布但需要你有日志积累。做法是把用户真实问过的问题抽样然后人工标注对应的相关文档。基于现有文档反向生成用 LLM 从文档片段生成问题再人工校验。效率高但要注意生成的问题可能过于教科书化和真实用户问法有差距。人工从零编写质量最高但成本最大适合核心场景的小规模精标。我的经验是一个可用的测评集至少需要 100 到 200 条 QA 对覆盖你的主要业务场景。低于 50 条指标波动会很大没有统计意义。而且测评集要定期更新因为业务在变用户问法也在变。注意测评集一定要划分开发集和测试集。你在开发集上调参数在测试集上验证最终效果。如果混在一起你就是在过拟合测评集上线必翻车。3. 核心指标实现与代码落地3.1 检索层指标RecallK 和 MRR 的手写实现先说 RecallK。定义很直白对于每个问题看前 K 个检索结果里是否命中了至少一个相关文档。命中记 1否则记 0最后求平均。def recall_at_k(retrieved_ids, relevant_ids, k): retrieved_ids: 检索返回的文档ID列表按相关性排序 relevant_ids: 标注的相关文档ID集合 k: 截断位置 top_k retrieved_ids[:k] hit any(doc_id in relevant_ids for doc_id in top_k) return 1.0 if hit else 0.0 def evaluate_recall(dataset, retriever, k_list[1, 3, 5, 10]): results {k: [] for k in k_list} for item in dataset: retrieved retriever(item[query]) for k in k_list: results[k].append(recall_at_k(retrieved, item[relevant_ids], k)) return {k: sum(v) / len(v) for k, v in results.items()}这里有个细节要注意relevant_ids应该是一个集合而不是单个 ID。因为一个问题可能对应多个相关文档只标一个会导致召回率被低估。我踩过这个坑早期测评集每个问题只标了一个文档结果 Recall5 死活上不去后来发现是标注太严格了。再说 MRR。它衡量的是第一个相关文档出现的位置。公式是 1/rankrank 是第一个相关文档的排名从 1 开始。如果前 K 个都没命中记 0。def mrr_at_k(retrieved_ids, relevant_ids, k): top_k retrieved_ids[:k] for rank, doc_id in enumerate(top_k, start1): if doc_id in relevant_ids: return 1.0 / rank return 0.0MRR 的价值在于它区分了排第一和排第五。Recall5 都是 1但 MRR 会告诉你哪个检索器更好。实际项目里如果生成模型对上下文顺序敏感比如只取 top 3 喂给模型MRR 就比 Recall 更重要。3.2 生成层指标用 LLM 做裁判的正确姿势生成层指标我主要用两个Faithfulness 和 Answer Relevancy。这两个都可以用 LLM 来打分但 prompt 设计很关键。Faithfulness 的核心问题是答案里的每一句话是否都能在检索到的上下文里找到依据。我的做法是把答案拆成句子逐句判断是否有上下文支撑。FAITHFULNESS_PROMPT 你是一个严格的评审。给定以下上下文和答案判断答案中的每个陈述是否都能在上下文中找到依据。 上下文 {context} 答案 {answer} 请输出一个 0 到 1 之间的分数1 表示完全有依据0 表示完全无依据。只输出数字。Answer Relevancy 则是判断答案是否切题。这个相对简单直接让 LLM 打分即可。但要注意LLM 打分有波动同一个样本多次打分可能不一致。我的做法是每个样本打 3 次取平均成本增加不多但稳定性提升明显。提示用 LLM 做裁判时一定要用比被测评模型更强的模型。用 7B 模型去评判 70B 模型的输出结果基本不可信。我一般用 GPT-4 级别或同等的强模型做裁判。3.3 端到端指标与人工抽检的结合端到端指标我主要看两个Exact Match 和人工抽检通过率。Exact Match 适合有标准答案的场景但 RAG 的答案往往是开放式的精确匹配意义有限。所以我更依赖人工抽检。人工抽检的做法是从测评集里随机抽 30 到 50 条让标注人员判断答案是否正确。这个比例不用太高但必须定期做。因为 LLM 裁判再强也可能有系统性偏差人工抽检是最后的校准。我一般把人工抽检结果和 LLM 裁判结果做对比如果两者差异超过 15%说明 LLM 裁判的 prompt 需要调整。这个校准过程很重要能让你的自动化测评真正可信。3.4 指标汇总表与阈值设定把所有指标汇总成一张表方便对比不同版本的效果。下面是我常用的模板指标定义目标阈值当前值Recall5前5个结果命中相关文档的比例≥ 0.850.78MRR10第一个相关文档排名的倒数均值≥ 0.700.62Faithfulness答案有上下文依据的比例≥ 0.900.88Answer Relevancy答案切题程度≥ 0.850.83人工通过率人工判断答案正确的比例≥ 0.800.75阈值怎么定没有绝对标准取决于你的业务容忍度。法律、医疗这类场景Recall5 至少要 0.95 以上一般客服场景0.85 就够用。我的建议是先跑一个 baseline然后根据业务反馈逐步提高阈值不要一上来就定一个够不着的目标。4. 完整实操流程与关键环节实现4.1 环境准备与依赖安装先把环境搭起来。我用的是 Python 3.10核心依赖就几个向量库用 Chroma轻量、易上手embedding 用 sentence-transformersLLM 调用用 openai 兼容接口。pip install chromadb sentence-transformers openai pandas tqdm如果你用的是本地模型把 openai 换成对应的客户端即可。我建议测评阶段先用 API 模型因为稳定性和速度都更好等流程跑通了再考虑本地化。4.2 构建测评集的实操步骤第一步从你的文档库里随机抽 200 个 chunk。第二步用 LLM 为每个 chunk 生成 2 到 3 个问题。第三步人工校验问题质量删掉那些答案就在问题里的废话问题。第四步标注每个问题对应的相关文档 ID。这里有个提效技巧生成问题时让 LLM 同时输出这个问题需要哪些 chunk 才能回答这样相关文档 ID 就自动标好了人工只需要校验。我用这个方法把标注效率提升了大概 3 倍。GEN_QA_PROMPT 基于以下文档片段生成2个用户可能会问的问题并列出回答该问题需要哪些文档片段用ID表示。 文档片段 {chunk_text} 文档ID{chunk_id} 请以JSON格式输出[{{question: ..., relevant_ids: [...]}}]4.3 跑通测评流程的完整代码把前面的模块串起来形成一个完整的测评脚本。核心逻辑是加载测评集对每个问题执行检索计算检索指标再调用生成模型计算生成指标。import json from tqdm import tqdm def run_full_evaluation(dataset_path, retriever, generator, judge_llm): with open(dataset_path, r, encodingutf-8) as f: dataset json.load(f) retrieval_scores {recall5: [], mrr10: []} generation_scores {faithfulness: [], relevancy: []} for item in tqdm(dataset): query item[query] relevant_ids set(item[relevant_ids]) # 检索 retrieved retriever(query, top_k10) retrieved_ids [r[id] for r in retrieved] # 检索指标 retrieval_scores[recall5].append( recall_at_k(retrieved_ids, relevant_ids, 5)) retrieval_scores[mrr10].append( mrr_at_k(retrieved_ids, relevant_ids, 10)) # 生成 context \n.join([r[text] for r in retrieved[:5]]) answer generator(query, context) # 生成指标 faith judge_faithfulness(judge_llm, context, answer) relev judge_relevancy(judge_llm, query, answer) generation_scores[faithfulness].append(faith) generation_scores[relevancy].append(relev) # 汇总 summary {} for k, v in {**retrieval_scores, **generation_scores}.items(): summary[k] round(sum(v) / len(v), 4) return summary跑完之后你会得到一张汇总表。我第一次跑的时候Recall5 只有 0.61Faithfulness 却有 0.92。这说明生成模型很老实有依据才答但检索根本没找对文档。问题定位就很清晰了去优化检索而不是动生成。4.4 参数调优的实操记录定位到检索问题后我做了几组对比实验。下面是我实际记录的调优过程实验chunk_sizeoverlapembedding模型Recall5MRR10baseline51250text-embedding-ada-0020.610.48实验125630text-embedding-ada-0020.720.55实验225630bge-large-zh0.810.66实验325630bge-large-zh 混合检索0.880.74可以看到chunk_size 从 512 降到 256 带来了明显提升因为小块更容易精确匹配。换中文优化的 embedding 模型又提升了一截。最后加上混合检索向量关键词Recall5 到了 0.88基本达标。这个调优过程的关键是每次只改一个变量。我见过有人一次改三个参数结果效果变好了也不知道是哪个起的作用下次遇到问题还是不会调。5. 常见问题与排查技巧实录5.1 检索指标虚高或虚低的排查最常见的问题是 Recall5 异常高比如 0.98但你明显感觉实际效果没那么好。这种情况八成是测评集泄露了。什么叫泄露就是你的测评集问题和文档库里的某些 chunk 高度重合检索器几乎是抄答案。排查方法检查测评集里的问题是否直接包含了文档里的原句。如果是说明生成问题时 LLM 偷懒了直接把文档句子改成了问句。解决办法是让 LLM 生成问题时做语义改写而不是简单换标点。反过来Recall5 异常低比如 0.3先别急着调模型检查两件事一是相关文档 ID 标注是否正确二是检索器返回的 ID 格式是否和标注一致。我踩过一次坑检索器返回的是字符串 ID标注是整数 ID导致所有匹配都失败Recall 直接归零。这种低级错误排查起来最费时间但一旦发现就很好解决。5.2 LLM 裁判打分离谱的应对LLM 裁判有时候会给出很离谱的分数比如答案明显是胡编的Faithfulness 却给了 0.9。这种情况通常是 prompt 不够严格。我的经验是在 prompt 里加几个 few-shot 例子特别是反例能显著提升裁判的准确性。另一个技巧是让 LLM 先输出推理过程再输出分数。虽然会增加 token 消耗但分数稳定性提升明显。我实测下来加了推理步骤后同一批样本三次打分的方差从 0.08 降到了 0.03。注意LLM 裁判对长答案的打分普遍偏低因为它更容易找到没有依据的句子。如果你的答案普遍较长考虑按句子粒度打分再平均而不是整体打分。5.3 测评结果波动的归因方法测评结果波动大先看测评集大小。100 条以下的测评集指标波动 5 个百分点很正常。其次是看检索器是否有随机性比如某些向量库的近似搜索有随机成分。最后看 LLM 裁判的温度参数温度不为 0 会导致打分波动。我的做法是固定所有随机种子LLM 裁判温度设为 0测评集至少 150 条。这样跑出来的指标同一版本重复跑三次波动能控制在 1 个百分点以内。5.4 常见问题速查表现象可能原因排查方向Recall5 异常高测评集泄露检查问题是否含文档原句Recall5 异常低ID 格式不匹配对比检索返回和标注格式Faithfulness 虚高裁判 prompt 太宽松加反例 few-shot指标波动大测评集太小/有随机性扩大测评集固定种子检索好但生成差prompt 或模型问题检查上下文是否被截断6. 踩坑总结与经验沉淀6.1 测评集构建的三个坑第一个坑是问题过于书面化。用 LLM 生成的问题往往很规范但真实用户问法是口语化的、有错别字的、甚至是不完整的。我后来在生成问题时特意加了一步口语化改写让问题更接近真实分布。这一步让测评结果和线上表现的差距缩小了很多。第二个坑是相关文档标注过严。早期我只标完全能回答问题的文档结果 Recall 一直上不去。后来放宽到包含部分相关信息的文档也标上指标才合理。因为实际检索中部分相关的文档也有价值不该被完全否定。第三个坑是测评集长期不更新。业务变了用户问法变了测评集还是老的测出来的分数再高也没意义。我现在固定每季度更新一次测评集替换掉 20% 的旧样本。6.2 指标计算的细节陷阱RecallK 的 K 值选择很关键。K 太小比如 3指标会偏低因为很多相关文档排在 4 到 10 位。K 太大比如 50指标会虚高因为几乎什么都能命中。我的经验是K 应该等于你实际喂给生成模型的文档数量。如果你只取 top 5 喂给模型那就看 Recall5。MRR 的坑在于它只关心第一个相关文档。如果一个问题有多个相关文档且它们分散在不同位置MRR 无法反映整体排序质量。这种情况下可以补充 NDCG 指标但 NDCG 计算复杂小项目不一定需要。6.3 从测评到优化的闭环测评的最终目的是指导优化。我习惯把测评结果和优化动作对应起来Recall 低 → 调 chunk_size、换 embedding、加混合检索MRR 低 → 加 rerank 模型、调整相似度阈值Faithfulness 低 → 收紧生成 prompt、要求模型只基于上下文回答Relevancy 低 → 优化 query 改写、加意图识别这个对应关系不是绝对的但能给你一个明确的优化方向。最怕的是测评做完了数字往那一放不知道该干嘛。测评必须形成测-改-再测的闭环才有价值。6.4 一些个人体会做 RAG 测评这两年我最大的体会是测评体系的价值不在于数字本身而在于它逼你把模糊的效果拆解成可操作的环节。当你能量化每个环节的表现时优化就从玄学变成了工程。另外不要追求一步到位的完美测评体系。我第一版测评脚本只有 50 行代码只算了一个 Recall5但它已经帮我发现了 chunk_size 的问题。先跑起来再逐步完善比一开始就设计一个庞大框架要务实得多。最后分享一个小技巧把每次测评的结果存成 JSON带上时间戳和参数配置。这样你随时可以回溯三个月前那个版本为什么效果好而不是靠记忆。这个习惯帮我省了无数次重复实验的时间。
阅读完成 · 觉得有帮助?