本文是《让大模型"开卷考试":RAG 全链路因果链》的姊妹篇。上一篇第十二层说"评估是整条链的导航系统",留下一个问题:导航系统本身怎么搭------数据集从哪来、四个指标各自怎么算分、分数低了怎么归因、怎么接进 CI 防回退。本文把这条路从"跑通"讲到"上线"。
一句话总览:RAGAS 的本质是用 LLM 当裁判替代人工阅卷------因为人工评慢、贵、不一致,传统字面指标又量不出语义对错,所以让模型拆 claim、反向出题、逐块判定;而四个指标各防一种病:忠实度防编、相关性防跑题、上下文精度防噪音占位、上下文召回防漏检;评估的终点不是分数,是按分数归因回链路旋钮,并接进 CI 让每次改动都过检。
〇、愿景:我们要的到底是什么
上一篇的 RAG 链路有十三层,每层都有旋钮:切分大小、embedding 模型、召回路数、融合权重、reranker 阈值......改任何一个,都要回答同一个问题:
"这次改动,效果是变好了还是变坏了?"
我们要的是一个能重复执行、结果可比、能定位到坏在哪一层的评估系统------不是验收时跑一次,而是像单元测试一样,每次改动都跑。
第一层:三条土路都走不通,所以请 LLM 当裁判
先排除三个直觉方案,它们的死因各不相同:
-
人工评估 :最准,但改一次参数就要人看几百条问答------慢、贵,而且两个评审对同一条答案经常打分不一(一致性问题)。评估的意义是高频回归,人工撑不起这个频率;
-
传统 NLP 指标(BLEU/ROUGE):算的是字面重叠率,而生成式答案的表达千变万化。举个对比例子:
- 参考答案:"D 级供应商将被暂停合作资格",模型答:"暂停与 D 级供应商的合作"------语义 100% 等价 ,但按字词重叠算(ROUGE),"将被""资格"等词缺失,得分只有 0.5 上下,冤杀;
- 参考答案同上,模型答:"D 级供应商将被暂停付款 资格"------只换了两个字,ROUGE 高达 0.9,但意思完全变了(不让你投标和冻结你货款是两回事),错放。
该低的不低、该高的不高------字面指标量不出语义对错;
-
拍脑袋看几条 :没有固定数据集,这次看的和上次看的不是同一批 case,分数无从比较。典型翻车:把 chunk_size 从 400 调到 600,随手试了 3 条 query 都觉得"变好了"------但这 3 条恰好都是制度解读类长问题(大块天然占优),没覆盖到编号查询类(小块才捞得准),上线后第二类 query 召回率悄悄塌了;更糟的是下周又凭另外几条的"感觉"换了 embedding,两次"感觉不错"叠加,系统实际是在进步还是退步,没有固定基线永远无从发现------等于没评。
三条路的共同死因:缺一个既懂语义、又能高频执行、还标准统一的阅卷人 。这个角色的天然人选就是 LLM 自己------这就是 RAGAS 的核心思路:LLM-as-a-Judge(用 LLM 当裁判)。
RAGAS 是一个开源 Python 评估框架(pip install ragas,Apache-2.0),把"裁判怎么阅卷"固化成了四个标准指标和一套数据集工具。
但请裁判是有代价的,先把丑话说在前面(后面每层都会回收):
- 裁判本身有偏差:judge 模型的能力上限就是评分的可信度上限,换个裁判分数线会整体漂移;
- 裁判按次收费:每条样本评四个指标 ≈ 十几次 LLM 调用,数据集一大就是真金白银。
因果要点:因为评估必须高频、统一、懂语义,而人供不起频率、字面指标供不起语义,所以 LLM 裁判是唯一解------但裁判的偏差和成本从此成为评估系统自身要管理的两个变量。
第二层:裁判怎么打分------四个指标,各防一种病
RAG 的失败模式就两大类:检索错了 和生成错了。四个指标各盯一个具体的病,设计各不相同------理解了"为什么这样算",分数低了你才知道去哪修。
2.1 Faithfulness 忠实度:防"编"
问的是:答案的每句话,都能在检索到的资料里找到出处吗?
先解释一个关键词:claim(原子论断) ------一句"不能再拆、能独立判断真假"的最小陈述。自然语言的句子往往是好几个事实的打包,比如"评价等级分四级,D 级供应商暂停合作并被列入黑名单三年"这一句话里其实装了三个可以独立为真或为假的论断:①等级分四级;②D 级暂停合作;③D 级列入黑名单三年。资料里可能证实了①②却从没提过③------只有拆到这个粒度,才能发现"一句话里三分之二是真的、三分之一是编的"。拆 claims 这步也是 LLM 干的(prompt 要求把答案分解为原子陈述)。
算法分三步:
#mermaid-svg-iUsAtUxbczPZ9ltf{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-iUsAtUxbczPZ9ltf .error-icon{fill:#552222;}#mermaid-svg-iUsAtUxbczPZ9ltf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-iUsAtUxbczPZ9ltf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-iUsAtUxbczPZ9ltf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-iUsAtUxbczPZ9ltf .marker.cross{stroke:#333333;}#mermaid-svg-iUsAtUxbczPZ9ltf svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-iUsAtUxbczPZ9ltf p{margin:0;}#mermaid-svg-iUsAtUxbczPZ9ltf .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf .cluster-label text{fill:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf .cluster-label span{color:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf .cluster-label span p{background-color:transparent;}#mermaid-svg-iUsAtUxbczPZ9ltf .label text,#mermaid-svg-iUsAtUxbczPZ9ltf span{fill:#333;color:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf .node rect,#mermaid-svg-iUsAtUxbczPZ9ltf .node circle,#mermaid-svg-iUsAtUxbczPZ9ltf .node ellipse,#mermaid-svg-iUsAtUxbczPZ9ltf .node polygon,#mermaid-svg-iUsAtUxbczPZ9ltf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-iUsAtUxbczPZ9ltf .rough-node .label text,#mermaid-svg-iUsAtUxbczPZ9ltf .node .label text,#mermaid-svg-iUsAtUxbczPZ9ltf .image-shape .label,#mermaid-svg-iUsAtUxbczPZ9ltf .icon-shape .label{text-anchor:middle;}#mermaid-svg-iUsAtUxbczPZ9ltf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-iUsAtUxbczPZ9ltf .rough-node .label,#mermaid-svg-iUsAtUxbczPZ9ltf .node .label,#mermaid-svg-iUsAtUxbczPZ9ltf .image-shape .label,#mermaid-svg-iUsAtUxbczPZ9ltf .icon-shape .label{text-align:center;}#mermaid-svg-iUsAtUxbczPZ9ltf .node.clickable{cursor:pointer;}#mermaid-svg-iUsAtUxbczPZ9ltf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-iUsAtUxbczPZ9ltf .arrowheadPath{fill:#333333;}#mermaid-svg-iUsAtUxbczPZ9ltf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-iUsAtUxbczPZ9ltf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-iUsAtUxbczPZ9ltf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iUsAtUxbczPZ9ltf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-iUsAtUxbczPZ9ltf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iUsAtUxbczPZ9ltf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-iUsAtUxbczPZ9ltf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-iUsAtUxbczPZ9ltf .cluster text{fill:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf .cluster span{color:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-iUsAtUxbczPZ9ltf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-iUsAtUxbczPZ9ltf rect.text{fill:none;stroke-width:0;}#mermaid-svg-iUsAtUxbczPZ9ltf .icon-shape,#mermaid-svg-iUsAtUxbczPZ9ltf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-iUsAtUxbczPZ9ltf .icon-shape p,#mermaid-svg-iUsAtUxbczPZ9ltf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-iUsAtUxbczPZ9ltf .icon-shape .label rect,#mermaid-svg-iUsAtUxbczPZ9ltf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-iUsAtUxbczPZ9ltf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-iUsAtUxbczPZ9ltf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-iUsAtUxbczPZ9ltf :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生成的答案
① 拆 claims
把答案拆成原子论断
② 逐条验证
每条 claim 能否从
retrieved_contexts 推断出
③ 算比例
支持数 / 总 claims 数
沿用上一篇的供应商制度场景。检索到的资料是:"评价等级分为 A、B、C、D 四级,被评为 D 级的供应商将暂停合作资格。"模型却答:"评价等级分四级,D 级供应商将暂停合作并被列入黑名单三年。"
- 拆 claims:①"等级分四级" ②"D 级暂停合作" ③"D 级列入黑名单三年";
- 逐条验证:① 支持 ② 支持 ③ 不支持(资料没提黑名单);
- Faithfulness = 2/3 ≈ 0.67。
为什么拆 claims 而不是整句判? 因为长答案常常部分正确------整句判定会让"两句对一句错"的答案直接归零,拆成原子论断才能精确定位到哪一句是编的,这个定位能力正是第五层归因的基础。
(工程选项:验证这一步默认用 LLM,也可以换 Vectara 开源的 HHEM-2.1-Open 幻觉检测小模型------免费、可本地跑,生产环境降成本的常规手段。)
2.2 Answer Relevancy 答案相关性:防"跑题"
问的是:答案真的在回答用户的问题吗?(只管切不切题,不管对不对------对错是 Faithfulness 的事。)
算法设计很巧:
#mermaid-svg-LhRqDn7cToRxC8g1{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LhRqDn7cToRxC8g1 .error-icon{fill:#552222;}#mermaid-svg-LhRqDn7cToRxC8g1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LhRqDn7cToRxC8g1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LhRqDn7cToRxC8g1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LhRqDn7cToRxC8g1 .marker.cross{stroke:#333333;}#mermaid-svg-LhRqDn7cToRxC8g1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LhRqDn7cToRxC8g1 p{margin:0;}#mermaid-svg-LhRqDn7cToRxC8g1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 .cluster-label text{fill:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 .cluster-label span{color:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 .cluster-label span p{background-color:transparent;}#mermaid-svg-LhRqDn7cToRxC8g1 .label text,#mermaid-svg-LhRqDn7cToRxC8g1 span{fill:#333;color:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 .node rect,#mermaid-svg-LhRqDn7cToRxC8g1 .node circle,#mermaid-svg-LhRqDn7cToRxC8g1 .node ellipse,#mermaid-svg-LhRqDn7cToRxC8g1 .node polygon,#mermaid-svg-LhRqDn7cToRxC8g1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LhRqDn7cToRxC8g1 .rough-node .label text,#mermaid-svg-LhRqDn7cToRxC8g1 .node .label text,#mermaid-svg-LhRqDn7cToRxC8g1 .image-shape .label,#mermaid-svg-LhRqDn7cToRxC8g1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-LhRqDn7cToRxC8g1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LhRqDn7cToRxC8g1 .rough-node .label,#mermaid-svg-LhRqDn7cToRxC8g1 .node .label,#mermaid-svg-LhRqDn7cToRxC8g1 .image-shape .label,#mermaid-svg-LhRqDn7cToRxC8g1 .icon-shape .label{text-align:center;}#mermaid-svg-LhRqDn7cToRxC8g1 .node.clickable{cursor:pointer;}#mermaid-svg-LhRqDn7cToRxC8g1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LhRqDn7cToRxC8g1 .arrowheadPath{fill:#333333;}#mermaid-svg-LhRqDn7cToRxC8g1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LhRqDn7cToRxC8g1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LhRqDn7cToRxC8g1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LhRqDn7cToRxC8g1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LhRqDn7cToRxC8g1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LhRqDn7cToRxC8g1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LhRqDn7cToRxC8g1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LhRqDn7cToRxC8g1 .cluster text{fill:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 .cluster span{color:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LhRqDn7cToRxC8g1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LhRqDn7cToRxC8g1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-LhRqDn7cToRxC8g1 .icon-shape,#mermaid-svg-LhRqDn7cToRxC8g1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LhRqDn7cToRxC8g1 .icon-shape p,#mermaid-svg-LhRqDn7cToRxC8g1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LhRqDn7cToRxC8g1 .icon-shape .label rect,#mermaid-svg-LhRqDn7cToRxC8g1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LhRqDn7cToRxC8g1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LhRqDn7cToRxC8g1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LhRqDn7cToRxC8g1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 生成的答案
① 反向出题
LLM 从答案反推出
n=3 个可能的问题
② 算余弦
每个反推问题与
原问题的 embedding 余弦
③ 取平均
背后的洞察一句话:如果答案真的切题,那从答案大概率能反推出原问题 ------就像你听别人打电话,从这一头的回答能猜出那一头问的是什么。答案跑题了,反推出的问题就会和原问题越离越远,余弦自然低。反向出题的数量默认 3 个(strictness 参数可调)。
2.3 Context Precision 上下文精度:防"噪音占位"
问的是:检索回来的块里,相关的排在前面了吗? 这是检索侧的指标,但它不只算"有没有噪音",更算噪音站在哪个位置------算法是 AP(Average Precision)风格的加权:
Context Precision@K = Σ (Precision@k × vk) / 相关块总数
vk ∈ {0,1}:第 k 位的块是否相关(LLM 逐块判定)
官方示例一组数字把这个设计讲透了:检索回来两块,一块"埃菲尔铁塔在巴黎"(相关)、一块"勃兰登堡门在柏林"(无关)------
- 相关块排第 1、无关块排第 2:得分 1.0(噪音在队尾,无害);
- 无关块排第 1、相关块排第 2:得分 0.5(噪音占了首位,重罚)。
为什么罚位置? 回收上一篇的伏笔:LLM 有 Lost in the Middle、top 块先进 prompt------噪音块的位置越靠前,对生成的毒害越大。所以这个指标低,矛头直指召回排序和 reranker,而不是召回数量。
2.4 Context Recall 上下文召回:防"漏检"
问的是:回答问题需要的信息,检索都捞齐了吗?
算法和 Faithfulness 镜像对称:把参考答案(reference)拆成 claims,逐条判定能否归因到检索回来的上下文,算归因比例:
Context Recall = reference 中能被检索上下文支撑的 claims 数 / reference 总 claims 数
镜像设计带来一个重要的使用约束:它是四个指标里唯一必须有 reference(标准答案)的------"漏没漏"是相对于"该有什么"而言的,没有标准答案就没有分母。怎么搞到 reference,是第四层的主题。
2.5 两个"答案"别搞混:response 与 reference
读到这里容易绕晕:Faithfulness 拆"答案"的 claims,Context Recall 拆"参考答案"的 claims------这是两个不同的东西:
- response(生成的答案) :你的 RAG 系统这次实际吐出来的回答。它是考生的答卷------可能编、可能漏、每次改动系统后再跑都可能不一样,是被考核的对象;
- reference(参考答案) :评估数据集里事先标好的标准答案 ,人工写或 TestsetGenerator 合成(第四层),评估开始前就冻结在 golden set 里。它是阅卷用的标准答卷,不随系统改动变化。
同一个例子看两者各自怎么拆:问题是"D 级供应商会受到什么处理?",系统检索到的资料是"评价等级分为 A、B、C、D 四级,被评为 D 级的供应商将暂停合作资格"。
- 系统生成的 response :"评价等级分四级,D 级供应商将暂停合作并被列入黑名单三年。" → 拆出 3 个 claims(①分四级 ②暂停合作 ③黑名单三年)→ Faithfulness 拿这 3 条对资料核"有没有出处":①② 有、③ 没有 → 0.67(生成的病:编);
- 人工标注的 reference :"D 级供应商将被暂停合作资格。" → 拆出 2 个 claims(①D 级是评价等级之一 ②D 级暂停合作)→ Context Recall 拿这 2 条对资料核"有没有被捞到":两条都能在检索资料里找到 → 1.0(检索没漏)。
| response(生成的答案) | reference(参考答案) | |
|---|---|---|
| 谁写的 | 你的 RAG 系统(被考核的考生) | 人工 / 合成器(出题人附的标准答卷) |
| 何时产生 | 评估时现场生成 | 评估前冻结在 golden set |
| 会不会变 | 改一次系统就变 | 冻结后不变 |
| 谁拆它 | Faithfulness | Context Recall |
| 拆完跟谁核、核什么 | 对检索资料核"有没有出处"(防编) | 对检索资料核"有没有被捞到"(防漏) |
一句话:两个指标拆 claims 的方向正好镜像------Faithfulness 是"答卷 → 资料"(生成是否忠于资料),Context Recall 是"标准答案 → 资料"(资料是否覆盖了标准答案)。一个管生成科,一个管检索科。
四指标全景:
| 指标 | 输入依赖 | 防的病 | 病灶在哪层 |
|---|---|---|---|
| Faithfulness | 答案 + 检索上下文 | 生成编造 | 生成/prompt(链路十一层) |
| Answer Relevancy | 答案 + 问题 + embedding | 答案跑题 | 生成或整体错位 |
| Context Precision | 检索上下文 + reference/答案 | 噪音块占位靠前 | 召回排序/reranker(八、九层) |
| Context Recall | 检索上下文 + reference(必需) | 该捞的没捞到 | 切分/embedding/query 处理(三、四、六层) |
因果要点:因为 RAG 的病分"检索"和"生成"两科,所以四个指标不是四个分数,是四张不同科室的化验单------Faithfulness/Context Recall 拆 claims 是为了定位到句,Answer Relevancy 反向出题是因为正着没法算"切题",Context Precision 加权位置是因为噪音的毒性随位置递增。
第三层:跑通最小闭环------四行配置,一次打分
先立版本旗帜:本文代码按 ragas 0.4.x(当前最新 0.4.3,2026-01 发布)的 collections API 编写。网上大量教程(包括官方旧文档截图)是 0.1/0.2 的老 API,照抄必踩坑,版本对照见文末专节。
python
pip install ragas # 0.4.3
python
import os
from openai import AsyncOpenAI
from ragas.llms import llm_factory
from ragas.embeddings.base import embedding_factory
from ragas.metrics.collections import (
Faithfulness, AnswerRelevancy, ContextPrecision, ContextRecall,
)
os.environ["OPENAI_API_KEY"] = "sk-..." # 裁判模型的钥匙
client = AsyncOpenAI()
judge_llm = llm_factory("gpt-4o-mini", client=client) # 统一工厂,按模型名推断 provider
judge_emb = embedding_factory("openai", model="text-embedding-3-small", client=client)
# 每个指标是一个类实例,构造时注入裁判
faith = Faithfulness(llm=judge_llm)
relevancy = AnswerRelevancy(llm=judge_llm, embeddings=judge_emb) # 反向出题要比余弦,所以多吃一个 embedding
precision = ContextPrecision(llm=judge_llm)
recall = ContextRecall(llm=judge_llm)
# 一条真实问答流水账:问题、检索到的块、生成的答案、人工标注的参考答案
sample = dict(
user_input="D 级供应商会受到什么处理?",
retrieved_contexts=["评价等级分为 A、B、C、D 四级,被评为 D 级的供应商将暂停合作资格。"],
response="评价等级分四级,D 级供应商将暂停合作并被列入黑名单三年。",
reference="D 级供应商将被暂停合作资格。",
)
r1 = faith.score(user_input=sample["user_input"], response=sample["response"],
retrieved_contexts=sample["retrieved_contexts"])
r2 = recall.score(user_input=sample["user_input"], reference=sample["reference"],
retrieved_contexts=sample["retrieved_contexts"])
print(r1.value) # ≈0.67 ------ 分数
print(r1.reason) # "第 3 条 claim「列入黑名单三年」无法从上下文推断" ------ 打分的理由
两个细节值得记住:
score()同步 /ascore()异步 ,每个指标返回的不是裸分数,是MetricResult------.reason字段是裁判的判卷理由。分数告诉你"有病",理由直接告诉你"病灶在哪",批量跑完先把低分样本的 reason 捞出来看,比盯分布图高效得多;- 各指标吃的输入不一样(对照 2.4 的全景表):Answer Relevancy 不需要上下文、Context Recall 必须要 reference------传参前先想清楚"这个指标在跟谁比"。
因果要点:因为裁判是 LLM,所以评估代码的骨架就是"把流水账(问题/上下文/答案/参考答案)逐条递给裁判"------真正的工程量不在调用,而在流水账从哪来,这就是下一层。
第四层:没有标注数据怎么办------让题库自己长出来
第三层的 sample 里,reference 是人工写的。100~200 条 golden set 是评估的入场券,但人工标注是体力活。两条路:
路一:人工标注(最准,兜底)。从真实用户 query 日志里抽样,人工写参考答案。覆盖了真实分布,但慢。
路二:TestsetGenerator 合成(快,冷启动)。RAGAS 内置的合成器直接从你的文档自动造出 (问题, 上下文, 参考答案) 三元组。原理不是"让 LLM 随便编题",而是两步结构化生成:
#mermaid-svg-nzuYcibAqfQUKaBq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nzuYcibAqfQUKaBq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nzuYcibAqfQUKaBq .error-icon{fill:#552222;}#mermaid-svg-nzuYcibAqfQUKaBq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nzuYcibAqfQUKaBq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nzuYcibAqfQUKaBq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nzuYcibAqfQUKaBq .marker.cross{stroke:#333333;}#mermaid-svg-nzuYcibAqfQUKaBq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nzuYcibAqfQUKaBq p{margin:0;}#mermaid-svg-nzuYcibAqfQUKaBq .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nzuYcibAqfQUKaBq .cluster-label text{fill:#333;}#mermaid-svg-nzuYcibAqfQUKaBq .cluster-label span{color:#333;}#mermaid-svg-nzuYcibAqfQUKaBq .cluster-label span p{background-color:transparent;}#mermaid-svg-nzuYcibAqfQUKaBq .label text,#mermaid-svg-nzuYcibAqfQUKaBq span{fill:#333;color:#333;}#mermaid-svg-nzuYcibAqfQUKaBq .node rect,#mermaid-svg-nzuYcibAqfQUKaBq .node circle,#mermaid-svg-nzuYcibAqfQUKaBq .node ellipse,#mermaid-svg-nzuYcibAqfQUKaBq .node polygon,#mermaid-svg-nzuYcibAqfQUKaBq .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nzuYcibAqfQUKaBq .rough-node .label text,#mermaid-svg-nzuYcibAqfQUKaBq .node .label text,#mermaid-svg-nzuYcibAqfQUKaBq .image-shape .label,#mermaid-svg-nzuYcibAqfQUKaBq .icon-shape .label{text-anchor:middle;}#mermaid-svg-nzuYcibAqfQUKaBq .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nzuYcibAqfQUKaBq .rough-node .label,#mermaid-svg-nzuYcibAqfQUKaBq .node .label,#mermaid-svg-nzuYcibAqfQUKaBq .image-shape .label,#mermaid-svg-nzuYcibAqfQUKaBq .icon-shape .label{text-align:center;}#mermaid-svg-nzuYcibAqfQUKaBq .node.clickable{cursor:pointer;}#mermaid-svg-nzuYcibAqfQUKaBq .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nzuYcibAqfQUKaBq .arrowheadPath{fill:#333333;}#mermaid-svg-nzuYcibAqfQUKaBq .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nzuYcibAqfQUKaBq .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nzuYcibAqfQUKaBq .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nzuYcibAqfQUKaBq .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nzuYcibAqfQUKaBq .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nzuYcibAqfQUKaBq .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nzuYcibAqfQUKaBq .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nzuYcibAqfQUKaBq .cluster text{fill:#333;}#mermaid-svg-nzuYcibAqfQUKaBq .cluster span{color:#333;}#mermaid-svg-nzuYcibAqfQUKaBq div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nzuYcibAqfQUKaBq .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nzuYcibAqfQUKaBq rect.text{fill:none;stroke-width:0;}#mermaid-svg-nzuYcibAqfQUKaBq .icon-shape,#mermaid-svg-nzuYcibAqfQUKaBq .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nzuYcibAqfQUKaBq .icon-shape p,#mermaid-svg-nzuYcibAqfQUKaBq .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nzuYcibAqfQUKaBq .icon-shape .label rect,#mermaid-svg-nzuYcibAqfQUKaBq .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nzuYcibAqfQUKaBq .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nzuYcibAqfQUKaBq .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nzuYcibAqfQUKaBq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 文档集合
① 建知识图谱
切块成节点 → NER/关键词抽取
→ 按实体相似度连边
② 按场景合成
Scenario = 节点组合 × 题型
× 问句长度 × 风格
(问题, 相关上下文, 参考答案)
测试集
第一步建图,是为了让"多跳题"有据可依------两块都提到"黑名单"的 chunk 被实体边连起来,合成器才能造出"串标行为和黑名单处罚有什么关系"这类需要跨块推理 的题。第二步按 Scenario 组合,是为了题型分布可控:
python
from ragas.testset import TestsetGenerator
from ragas.testset.synthesizers import (
SingleHopSpecificQuerySynthesizer, SingleHopAbstractQuerySynthesizer,
MultiHopSpecificQuerySynthesizer, MultiHopAbstractQuerySynthesizer,
)
generator = TestsetGenerator(llm=judge_llm, embedding_model=judge_emb)
testset = generator.generate_with_langchain_docs(docs, testset_size=100)
# 进阶:用 query_distribution 控制四类题的比例(单跳/多跳 × 具体/抽象)
合成数据的铁律:参考答案是 LLM 生成的,本身必须抽查。 建议流程:先合成 100 条 → 人工过一遍、删掉答案离谱的 → 剩下的冻结成 golden set → 之后每次改动都跑这同一套题。题不换,分才可比。
因果要点:因为 Context Recall 必须有 reference,而人工标注慢,所以冷启动靠合成、长期靠真实 query 沉淀------golden set 一旦冻结就是资产,它的质量上限就是你能发现的坏case的上限。
第五层:分数低了然后呢------按指标组合归因到链路旋钮
单个分数只报病,指标组合才是诊断书。对照上一篇的链路层号:
| 化验单组合 | 诊断 | 回哪层拧旋钮 |
|---|---|---|
| Faithfulness 低,Context Recall 高 | 捞对了但生成在编 | 十一层:prompt 溯源约束、拒答机制 |
| Faithfulness 高,Context Recall 低 | 生成诚实但检索漏了("忠于残缺资料的不完整答案") | 三层切分 / 四层 embedding / 六层 query 改写 |
| Context Precision 低 | 噪音块占位靠前 | 八层融合策略 / 九层 reranker |
| Answer Relevancy 低,其余正常 | 答非所问 | 六层 query 理解 / 十一层 prompt 指令 |
| 四项全低 | 系统性错位,先查数据 | 索引是否建在错误版本的文档上 |
第一种组合最常见也最危险的是它的镜像------高 Recall 低 Faithfulness :系统找到了对的文档,然后在上面添油加醋,答案还挂着引用编号,因为看起来可信所以更危险。
归因完顺手做一件事:把低分样本的 .reason 汇总归类。Faithfulness 低分样本的 reason 集中出现"资料未提及",是生成问题;集中出现"上下文与结论矛盾",先怀疑是不是检索把两个相似条款都捞进来互相干扰------病在召回不在生成。这就是为什么第三层说 reason 比分数值钱。
因果要点:因为四个指标盯的是链路的不同段,所以分数矩阵 × reason 文本 = 从症状到旋钮的映射表------评估的产出物不是报告,是下一轮的改动清单。
第六层:防回退------把评估焊进 CI
评估只在发版前跑一次,等于没评估。正确的姿势是像单元测试一样:每次改动(换 embedding、调切分、改 prompt)都自动跑 golden set,分数跌破阈值就拦下。
python
# eval_gate.py ------ CI 质量门禁(精简骨架)
import sys, statistics
THRESHOLDS = {
"faithfulness": 0.85,
"answer_relevancy": 0.80,
"context_precision": 0.70,
"context_recall": 0.75,
}
scores = {k: [] for k in THRESHOLDS}
for sample in golden_set: # 冻结的 golden set
result = run_my_rag_pipeline(sample["user_input"]) # 你的系统跑一遍
scores["faithfulness"].append(
faith.score(user_input=sample["user_input"], response=result.answer,
retrieved_contexts=result.contexts).value)
# ... 其余三个指标同理
failed = [k for k, th in THRESHOLDS.items() if statistics.mean(scores[k]) < th]
if failed:
print("质量门禁未通过:", {k: statistics.mean(scores[k]) for k in failed})
sys.exit(1) # 非零退出码 → CI 标红、阻断合并
生产化三个注意(每一条都是第一层埋的"裁判变量"的回收):
- 锁定裁判模型版本 :judge 从 gpt-4o-mini 换成新版,分数线会整体漂移------阈值瞬间失真。裁判版本、prompt、数据集三者必须一起冻结,换裁判要重新校准阈值;
- 评估作业慢且贵:每条样本四个指标 ≈ 十几次 LLM 调用,100 条就是上千次。CI 里给评估作业设独立超时(15~20 分钟)、大数据集分批跑;日常 PR 可以只跑抽样子集,全量留给 nightly;
- 别用 deprecated 的
evaluate():0.4.x 里整表跑的evaluate()已标记弃用(官方推荐@experiment装饰器做实验追踪),逐指标score()的 collections 写法既不受影响也更适合门禁脚本。
#mermaid-svg-vB3zIp0BvvwlIo0t{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-vB3zIp0BvvwlIo0t .error-icon{fill:#552222;}#mermaid-svg-vB3zIp0BvvwlIo0t .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-vB3zIp0BvvwlIo0t .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-vB3zIp0BvvwlIo0t .marker{fill:#333333;stroke:#333333;}#mermaid-svg-vB3zIp0BvvwlIo0t .marker.cross{stroke:#333333;}#mermaid-svg-vB3zIp0BvvwlIo0t svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-vB3zIp0BvvwlIo0t p{margin:0;}#mermaid-svg-vB3zIp0BvvwlIo0t .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t .cluster-label text{fill:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t .cluster-label span{color:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t .cluster-label span p{background-color:transparent;}#mermaid-svg-vB3zIp0BvvwlIo0t .label text,#mermaid-svg-vB3zIp0BvvwlIo0t span{fill:#333;color:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t .node rect,#mermaid-svg-vB3zIp0BvvwlIo0t .node circle,#mermaid-svg-vB3zIp0BvvwlIo0t .node ellipse,#mermaid-svg-vB3zIp0BvvwlIo0t .node polygon,#mermaid-svg-vB3zIp0BvvwlIo0t .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-vB3zIp0BvvwlIo0t .rough-node .label text,#mermaid-svg-vB3zIp0BvvwlIo0t .node .label text,#mermaid-svg-vB3zIp0BvvwlIo0t .image-shape .label,#mermaid-svg-vB3zIp0BvvwlIo0t .icon-shape .label{text-anchor:middle;}#mermaid-svg-vB3zIp0BvvwlIo0t .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-vB3zIp0BvvwlIo0t .rough-node .label,#mermaid-svg-vB3zIp0BvvwlIo0t .node .label,#mermaid-svg-vB3zIp0BvvwlIo0t .image-shape .label,#mermaid-svg-vB3zIp0BvvwlIo0t .icon-shape .label{text-align:center;}#mermaid-svg-vB3zIp0BvvwlIo0t .node.clickable{cursor:pointer;}#mermaid-svg-vB3zIp0BvvwlIo0t .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-vB3zIp0BvvwlIo0t .arrowheadPath{fill:#333333;}#mermaid-svg-vB3zIp0BvvwlIo0t .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-vB3zIp0BvvwlIo0t .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-vB3zIp0BvvwlIo0t .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vB3zIp0BvvwlIo0t .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-vB3zIp0BvvwlIo0t .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vB3zIp0BvvwlIo0t .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-vB3zIp0BvvwlIo0t .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-vB3zIp0BvvwlIo0t .cluster text{fill:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t .cluster span{color:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-vB3zIp0BvvwlIo0t .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-vB3zIp0BvvwlIo0t rect.text{fill:none;stroke-width:0;}#mermaid-svg-vB3zIp0BvvwlIo0t .icon-shape,#mermaid-svg-vB3zIp0BvvwlIo0t .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-vB3zIp0BvvwlIo0t .icon-shape p,#mermaid-svg-vB3zIp0BvvwlIo0t .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-vB3zIp0BvvwlIo0t .icon-shape .label rect,#mermaid-svg-vB3zIp0BvvwlIo0t .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-vB3zIp0BvvwlIo0t .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-vB3zIp0BvvwlIo0t .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-vB3zIp0BvvwlIo0t :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 通过
跌破
改动:换embedding /
调切分 / 改prompt
CI 触发
golden set 全量回归
四指标均值
≥ 阈值?
合并,记录分数存档
阻断合并
输出低分样本 + reason
按第五层矩阵归因
修完重跑
因果要点:因为评估的价值在"回归"而不在"验收",所以评估系统必须满足三条件才能进 CI------数据集冻结、裁判冻结、阈值门禁。不满足这三条的评估分数,只是看起来很像数据的噪音。
版本边界:RAGAS 的三张脸(照抄网上教程前必读)
RAGAS 迭代极快,API 已经换过两次脸,网上教程绝大多数是老版本:
| 版本 | 时间 | 长什么样 |
|---|---|---|
| 0.1.x | ~2024 | evaluate() 直接吃 HuggingFace Dataset;字段名 question/answer/contexts;指标是小写单例 faithfulness;TestsetGenerator 用 from_langchain(critic_llm) + evolutions.simple/reasoning |
| 0.2.x | 2025 Q1 | 字段全改名 :question→user_input、answer→response、contexts→retrieved_contexts;answer_relevancy→ResponseRelevancy;evaluate() 改吃 EvaluationDataset;指标必须类实例化并显式传 llm |
| 0.4.x(当前) | 2025-12 起 | collections API :ragas.metrics.collections.Faithfulness;llm_factory() 统一裁判配置(弃 LangchainLLMWrapper);score/ascore(**kwargs) 返回 MetricResult(.value + .reason);evaluate() 标记弃用、推荐 @experiment;TestsetGenerator 走 KG + Scenario(query_distribution 用 Synthesizer 类比例) |
两个实用提醒:
- 旧 API 在 0.4 里已弃用、1.0 将移除------新项目直接上 collections API;
- 仓库已从
explodinggradients/ragas改名vibrantlabsai/ragas(旧地址会跳转),搜 issue 和 PR 别找错地方。
取舍总览
| 环节 | 核心矛盾 | 主流取舍 | 代价 |
|---|---|---|---|
| 裁判选择 | 评得准 vs 评得起 | 强模型(GPT-4o 级)当裁判,小模型 HHEM 做 Faithfulness 验证平替 | 裁判能力即分数可信度上限 |
| 数据集 | 真实分布 vs 冷启动速度 | TestsetGenerator 合成起步 + 人工抽查 + 真实 query 沉淀替换 | 合成 reference 需人工过滤 |
| 指标 | 全面 vs 调用成本 | 四指标全跑做诊断,日常 CI 可裁剪(如只守 Faithfulness + Recall) | 裁剪会漏掉对应科室的病 |
| CI 门禁 | 严格防回退 vs 流水线时长 | 阈值门禁 + PR 抽样跑 + nightly 全量 | 抽样有漏检概率 |
| 阈值 | 防倒退 vs 防误伤 | 裁判/数据/阈值三者绑定冻结,换裁判重新校准 | 校准本身是体力活 |
后记
回头看这条链:因为人工和字面指标都供不起"高频、统一、懂语义"的阅卷,所以 LLM 当裁判;因为 RAG 的病分检索和生成两科,所以四个指标各防一种病、算法各为其病设计;因为裁判按次收费且自身有偏差,所以数据集要合成起步、裁判要锁定版本;因为评估的价值在回归不在验收,所以分数要归因成改动清单、门禁要焊进 CI。
RAGAS 给的不是四个分数,是一整套"让 RAG 调优从玄学变工程"的操作系统------分数是表象,归因是本体,CI 是落地。