外观
从凭感觉坏例到可归因:RAGAS 四维评估实战
问题背景:RAG 上线后,为什么“看起来能答”仍然不够
很多 RAG 系统上线前依赖人工抽测:问几个问题,看回答是否通顺。但线上真实流量会迅速暴露三类问题:检索没找到、找错了但模型强行解释、找到了证据却没有回答用户问题。仅靠最终答案的主观打分,很难判断问题出在知识库、召回、排序、上下文拼装,还是生成模型。
RAG 评估的核心价值,是把一条 badcase 拆成可定位的环节:
- 用户问题是否清晰;
- 检索上下文是否包含正确证据;
- 上下文是否混入噪声;
- 答案是否忠于证据;
- 答案是否真正回应问题。
RAGAS 提供了一套面向 RAG 的自动化指标体系。生产中最常用的是四个维度:Faithfulness(忠实度)、Answer Relevancy(答案相关性)、Context Precision(上下文精确率)、Context Recall(上下文召回率)。它们分别对应“有没有胡说”“有没有答非所问”“检索结果是否靠前且有用”“正确证据是否被召回”。
方案对比:人工评估、规则评估与 LLM-as-a-Judge
| 方案 | 优点 | 局限 | 适用阶段 |
|---|---|---|---|
| 人工专家评估 | 准确、能判断业务语义 | 成本高、速度慢、标准易漂移 | 金标集验收、争议样本复核 |
| 规则与关键词评估 | 稳定、便宜、可重复 | 难以识别同义表达和推理答案 | 格式校验、强事实字段检查 |
| RAGAS/LLM 自动评估 | 可批量跑、维度清晰、便于归因 | 受裁判模型影响,需要抽样校准 | 回归测试、日常监控、版本对比 |
| 线上用户反馈 | 接近真实效果 | 噪声大、滞后、样本有偏 | 持续迭代与告警 |
建议不要把 RAGAS 当作“绝对真理”,而是把它作为高吞吐的评测层:先用 RAGAS 找出低分样本和指标组合,再对边界样本进行人工复核。
RAGAS 四维指标:每个分数到底在诊断什么
Faithfulness:答案是否忠于检索上下文
忠实度检查回答中的每个主张能否由上下文支持。分数低通常意味着模型发生了幻觉、使用了参数记忆,或者把检索到的相似但错误的信息当成事实。
典型表现:上下文写“支持 7 天退款”,答案却写“支持 30 天无理由退款”。即使语言流畅,也应判为低忠实度。
Answer Relevancy:答案是否围绕问题
答案相关性关注回答是否切题,而不是简单比对字数或关键词。常见低分原因有两类:一是答案堆砌背景资料,没有给出结论;二是问题本身有多个子问,回答只覆盖其中一部分。
Context Precision:有用上下文是否排在前面
上下文精确率关注检索结果中有用片段的排名和噪声比例。即使正确片段在 Top 5,但排在最后,经过截断或长上下文稀释后,模型也可能忽略它。该指标适合诊断召回排序、rerank 和 chunk 质量。
Context Recall:标准答案所需证据是否被召回
上下文召回率通常需要 ground truth,即人工标注的标准答案或证据。它检查回答标准答案所需的信息是否能在上下文中找到。该指标低,优先排查知识库缺失、切片粒度、过滤条件、Embedding 和召回参数。
实操步骤:用可运行代码完成一次 RAGAS 评估
下面示例使用 OpenAI 兼容接口作为 RAG 系统和 RAGAS 裁判模型。你可以替换为内部模型网关,但要保证裁判模型具备较强的事实判断与指令遵循能力。
先安装依赖:
bash
pip install "ragas>=0.1.21" datasets openai pandas第一步:构造评测样本
评测集不要只收集“容易回答”的问题。建议按业务意图、文档来源、时间范围、问题难度分层抽样,并保留一定比例的多跳问题、拒答问题和过期知识问题。
python
# eval_rag.py
# 可直接运行:python eval_rag.py
import os
import pandas as pd
from datasets import Dataset
from openai import OpenAI
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
# 如果使用公司内部 OpenAI 兼容网关,修改 OPENAI_BASE_URL 即可
client = OpenAI(
api_key=os.getenv("OPENAI_API_KEY", "replace-with-your-key"),
base_url=os.getenv("OPENAI_BASE_URL", "https://api.openai.com/v1"),
)
# 小规模金标集:ground_truth 用于上下文召回率评估
samples = [
{
"question": "会员退款有效期是多久?",
"ground_truth": "会员服务在购买后 7 天内,且未使用核心权益时可申请退款。",
},
{
"question": "企业版是否支持私有化部署?",
"ground_truth": "企业版支持专有云私有化部署,需要联系商务开通。",
},
]
# 模拟知识库检索结果。生产中替换成向量库、混合检索或 rerank 后的结果
fake_knowledge_base = {
"会员退款有效期是多久?": [
"会员服务购买后 7 天内,且未使用核心权益,可申请退款。",
"退款将在审核通过后的 3 到 5 个工作日原路退回。",
],
"企业版是否支持私有化部署?": [
"企业版支持专有云私有化部署,适用于对数据隔离要求较高的客户。",
"私有化版本需要联系商务确认资源、版本和交付周期。",
],
}
def retrieve(question: str):
"""返回检索片段列表。"""
return fake_knowledge_base.get(question, ["知识库中未找到相关说明。"])
def generate_answer(question: str, contexts: list[str]) -> str:
"""根据上下文生成答案,禁止使用无依据信息。"""
context_text = "\n".join(f"[{i+1}] {c}" for i, c in enumerate(contexts))
prompt = f"""
你是严谨的客服助手。请只能依据以下资料回答问题。
如果资料不足,请回答“根据现有资料无法确认”。
资料:
{context_text}
问题:{question}
答案:
"""
resp = client.chat.completions.create(
model=os.getenv("RAG_MODEL", "gpt-4o-mini"),
messages=[{"role": "user", "content": prompt}],
temperature=0,
)
return resp.choices[0].message.content
# 组装 RAGAS 需要的字段
rows = []
for item in samples:
contexts = retrieve(item["question"])
answer = generate_answer(item["question"], contexts)
rows.append({
"question": item["question"],
"answer": answer,
"contexts": contexts,
"ground_truth": item["ground_truth"],
})
dataset = Dataset.from_list(rows)
# 运行四维评估
result = evaluate(
dataset=dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall,
],
)
# 输出总体分数与逐条明细
print("总体指标:")
print(result)
df = result.to_pandas()
print("\n逐条明细:")
print(df[[
"question",
"faithfulness",
"answer_relevancy",
"context_precision",
"context_recall",
]])
df.to_csv("ragas_result.csv", index=False, encoding="utf-8-sig")第二步:固定裁判模型与参数
RAGAS 的多数指标依赖 LLM-as-a-Judge。为了让历史版本可比较,需要记录:
- 裁判模型名称与版本;
- temperature,建议设为 0;
- RAGAS、依赖库和指标提示词版本;
- 评测集版本;
- 检索 TopK、rerank 模型、切片大小和重叠长度。
否则一次分数上升可能只是裁判模型变了,而不是 RAG 效果真的改善。
第三步:建立 badcase 归因表
不要只看平均分,建议导出逐条结果并按指标组合归因:
| 指标现象 | 更可能的问题 | 优先动作 |
|---|---|---|
| 召回率低、精确率也低 | 知识库缺失或检索语义不匹配 | 补文档、优化切片、调整 Embedding 查询 |
| 召回率高、精确率低 | 噪声片段太多,排序差 | 加 rerank、降低 TopK、增强元数据过滤 |
| 上下文指标高、忠实度低 | 模型忽略证据或自行补全 | 强化约束、降低温度、要求引用证据 |
| 忠实度高、相关性低 | 回答保守或没有覆盖问题 | 改答案模板、拆分子问题、补充回答要求 |
| 所有指标都高但用户不满意 | 评测集与真实问题分布不一致 | 引入线上问题,重建金标集 |
可以用下面的规则做自动归因:
python
# attribution.py
import pandas as pd
def attribute_case(row):
"""根据 RAGAS 四维分数给出初步归因。"""
f = row["faithfulness"]
a = row["answer_relevancy"]
p = row["context_precision"]
r = row["context_recall"]
if r < 0.6 and p < 0.6:
return "检索失败:疑似知识缺失或查询语义不匹配"
if r >= 0.6 and p < 0.6:
return "排序失败:正确证据存在,但噪声过多或排名靠后"
if f < 0.7 and p >= 0.6 and r >= 0.6:
return "生成幻觉:上下文足够,但答案引入无依据信息"
if f >= 0.7 and a < 0.7:
return "答非所问:答案有依据,但未完整回应用户意图"
return "表面通过:建议结合用户反馈人工复核"
df = pd.read_csv("ragas_result.csv")
df["attribution"] = df.apply(attribute_case, axis=1)
df.to_csv("ragas_badcase_attribution.csv", index=False, encoding="utf-8-sig")
print(df[["question", "attribution"]])如何设计一套可持续的 RAG 评测集
第一,金标答案要写“可验证事实”,不要只写营销话术。例如“支持私有化部署”比“企业版能力强大、安全可靠”更容易评估。
第二,问题要覆盖真实用户语言。用户通常不会直接输入文档标题,而会说“我买错了能不能退”“你们能不能部署到我们自己机房”。这些口语化问法能检验查询改写和语义召回。
第三,必须包含反事实和无答案问题。如果知识库没有说明,就应该拒答,而不是让模型猜。可以在 ground_truth 中明确写“资料不足,应拒答”。
第四,评测集要版本化。每次知识库大更新、切片策略调整、模型替换、rerank 上线,都应在同一评测集上回归,再补充新增业务问题。
避坑总结:RAGAS 落地最容易踩的 5 个坑
1. 只看平均分,忽略指标组合
错误现象:整体均分 0.85 就认为系统合格,但某类售后问题忠实度只有 0.55。修正方法是按问题类型、文档来源、时间范围分组统计,并设置单项阈值,例如忠实度低于 0.7 必须阻断发布。
2. 没有 ground_truth 却误读上下文召回率
错误现象:只传 question、answer、contexts,发现召回率结果不稳定或无法反映真实缺失。修正方法是为核心评测集标注标准答案或必需证据点;没有标注时,只能把召回率作为辅助指标,不能作为主要验收依据。
3. 检索片段过长,导致裁判模型“看起来都相关”
错误现象:一个 chunk 包含整篇文档,上下文精确率虚高,但生成答案抓不住重点。修正方法是控制 chunk 粒度,保留标题路径和小节信息;对长文档先做语义切分,再用 rerank 选择最相关片段。
4. 用同一个弱模型既生成答案又当裁判
错误现象:模型把自己的错误表述判成正确,尤其是数字、日期、权限类问题。修正方法是裁判模型使用能力更强、更稳定的模型,并对低分样本、高分但人工怀疑的样本进行双向抽检。
5. 把 RAGAS 分数当成分数线,而不是诊断信号
错误现象:团队为了提高答案相关性,在提示词中要求模型多写背景,结果忠实度和用户满意度下降。修正方法是每次只改动一个环节,例如只替换 rerank 模型或只调整切片大小,并对比四维指标变化,避免用一个指标掩盖另一个问题。
结语
RAGAS 的价值不在于给出一个漂亮总分,而在于把 RAG 系统拆成“检索—排序—生成—问答意图”四个可观测环节。真正有效的评估流程应当是:固定金标集,自动计算四维指标,按指标组合归因 badcase,再通过人工复核校准裁判模型。这样,每次优化 Embedding、切片、rerank、提示词或生成模型时,才能知道收益来自哪里,也能避免修复一个问题却引入新的幻觉。
免责声明
本工具与文章内容仅用于学习测试场景,优化建议仅供参考,请勿直接用于生产环境,由此产生的风险自行承担。