RAG评估实战-用RAGAS量化检索与生成质量
> **[进阶选读]** 本篇适合 RAG 系统已上线、需要系统评估和量化优化效果的读者。RAGAS 是目前最主流的 RAG 评估框架,学完本篇能对你的 RAG 系统做定量分析,而不是凭感觉判断好坏。
RAG 评估实战:用 RAGAS 量化检索与生成质量
[进阶选读] 本篇适合 RAG 系统已上线、需要系统评估和量化优化效果的读者。RAGAS 是目前最主流的 RAG 评估框架,学完本篇能对你的 RAG 系统做定量分析,而不是凭感觉判断好坏。
1.1 为什么需要专门的 RAG 评估
RAGAS 四大评估维度,全面量化 RAG 系统的检索与生成质量
传统软件工程的质量指标——响应延迟、吞吐量、错误率——对 RAG 系统同样重要,但远远不够。一个 RAG 系统可以在 200ms 内稳定返回答案,但这个答案可能是完全错误的、与文档不符的,或者根本没有回答用户的问题。
工程指标和质量指标之间存在根本性的差异:
| 指标类型 | 衡量什么 | 示例 | 局限 |
|---|---|---|---|
| 工程指标 | 系统运行效率 | 延迟、QPS、成本 | 无法判断答案质量 |
| 质量指标 | 答案内容好坏 | 准确性、相关性、完整性 | 需要专门的评估框架 |
评估 RAG 系统的质量困难之处在于:答案的"好坏"不是一个简单的布尔值。答案可能部分正确、过于冗长、包含幻觉内容、遗漏关键信息——每种问题都需要不同的度量方式。
RAGAS(Retrieval-Augmented Generation Assessment,检索增强生成评估)是目前最广泛使用的 RAG 评估框架,提供了四个针对性的指标,覆盖检索质量和生成质量两个维度。
1.2 RAGAS 四大核心指标详解
1.2.1 Faithfulness(忠实度)
衡量什么: 生成的答案中,每一个陈述是否都能从检索到的文档中找到依据。
评分方式: 将答案分解为独立的原子陈述,逐一验证每个陈述是否有文档支撑,最终得分 = 有支撑的陈述数 / 总陈述数。
低分意味着什么: 模型产生了幻觉(hallucination,指 AI 模型自信地生成了不实或未经文档支撑的内容,就像"凭空想象"出来的事实)——答案中包含文档未提及的内容。这通常是最严重的问题,会导致用户获得错误信息。
典型场景:
问题:公司退款政策的期限是多久?
检索文档:退款申请须在购买后 30 天内提交。
模型回答:您可以在 60 天内申请退款,无需提供购买凭证。
Faithfulness = 0("60天"和"无需凭证"均无文档支撑)
1.2.2 Answer Relevancy(答案相关性)
衡量什么: 生成的答案是否直接回答了用户的问题,而不是答非所问或过度冗长。
评分方式: 基于答案反向生成多个候选问题,计算这些候选问题与原始问题的语义相似度。高相关性意味着答案的内容与问题高度契合。
低分意味着什么: 答案虽然可能是事实正确的,但没有聚焦在用户真正关心的问题上。常见于答案过于宽泛,或者答案回答了相关但不同的问题。
典型场景:
问题:如何重置密码?
答案:我们的系统使用了行业领先的加密技术,支持多种登录方式,包括邮箱、手机号和第三方账号。安全是我们的首要考虑...
Answer Relevancy = 低(答案没有回答"如何重置密码")
1.2.3 Context Precision(上下文精确率)
衡量什么: 检索到的文档片段中,有多少是真正对回答问题有用的,而不是无关噪音。
评分方式: 对每个检索到的文档片段,判断它是否实际用于生成答案,计算有用片段占总检索片段的比例,并考虑排序(排名靠前的有用片段权重更高)。
低分意味着什么: 检索模块质量差,引入了太多噪音文档。噪音文档不仅浪费 token,还可能干扰模型生成准确答案。
1.2.4 Context Recall(上下文召回率)
衡量什么: 真正需要的信息是否都被检索到了,检索有没有遗漏关键文档。
评分方式: 将参考答案分解为多个陈述,判断每个陈述是否能从检索到的文档中找到支持,得分 = 有文档支持的陈述数 / 总陈述数。
低分意味着什么: 检索召回不足,向量数据库没有命中所需文档。可能是分块策略问题、嵌入模型不匹配,或者索引质量差。
需要 ground truth 答案:这个指标需要提供参考答案(ground truth),是唯一需要人工标注数据的指标。
1.3 快速上手:用 ragas 库跑评估
1.3.1 安装依赖
pip install ragas langchain-openai datasets
1.3.2 准备评估数据集
RAGAS 需要三元组数据:问题(question)、上下文(contexts)、答案(answer),以及可选的参考答案(ground_truth)。
from datasets import Dataset
# 构建评估数据集
# 实际使用时,这些数据来自你的 RAG 系统的实际输出
eval_data = {
"question": [
"LangGraph 的 Send API 是什么?",
"RAG 系统中的 Chunking 有什么作用?",
"向量数据库和关系型数据库有什么区别?",
],
"contexts": [
# 每个问题对应一个列表,包含检索到的文档片段
[
"Send API 是 LangGraph 的动态并行机制,允许在运行时向节点派发任意数量的任务。",
"Send 对象的第一个参数是目标节点名称,第二个参数是输入状态。",
],
[
"Chunking 是将长文档切分为较小片段的过程,直接影响检索的粒度和准确性。",
"合理的 chunk 大小通常在 200-500 token 之间,需要在检索精度和上下文完整性之间取得平衡。",
],
[
"向量数据库专为高维向量的相似性搜索设计,支持 ANN(近似最近邻)算法。",
"关系型数据库使用 SQL 进行精确查询,不支持语义相似性搜索。",
"向量数据库适合语义搜索场景,关系型数据库适合结构化数据的精确查询。",
],
],
"answer": [
"Send API 是 LangGraph 的动态并行派发机制,允许在节点运行时生成任意数量的 Send 对象,每个 Send 对象触发目标节点的一次独立执行。",
"Chunking 将长文档切分为小片段,使向量检索能够精确命中相关内容。chunk 大小影响检索粒度,过小会丢失上下文,过大会引入噪音。",
"向量数据库专门为语义相似性搜索设计,使用 ANN 算法;关系型数据库用于结构化数据的精确查询。两者适用场景不同,不是竞争关系。",
],
"ground_truth": [
"Send API 允许在 LangGraph 节点内部动态创建 Send 对象列表,实现运行时的并行任务派发,是实现 Map-Reduce 模式的核心机制。",
"Chunking 是 RAG 系统中将文档切分为可检索片段的过程,chunk 的大小和策略直接影响检索质量和最终答案的准确性。",
"向量数据库针对高维向量相似性搜索优化,关系型数据库针对结构化数据精确查询优化,两者在 RAG 系统中各司其职。",
],
}
dataset = Dataset.from_dict(eval_data)
1.3.3 运行评估
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
# 配置评估使用的 LLM 和 Embedding 模型
# RAGAS 本身也需要 LLM 来进行指标计算
from ragas.llms import LangchainLLMWrapper
from ragas.embeddings import LangchainEmbeddingsWrapper
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
ragas_llm = LangchainLLMWrapper(llm)
ragas_embeddings = LangchainEmbeddingsWrapper(embeddings)
# 运行评估
results = evaluate(
dataset=dataset,
metrics=[
faithfulness,
answer_relevancy,
context_precision,
context_recall,
],
llm=ragas_llm,
embeddings=ragas_embeddings,
)
print(results)
# 输出示例:
# {'faithfulness': 0.92, 'answer_relevancy': 0.88,
# 'context_precision': 0.85, 'context_recall': 0.79}
1.3.4 查看详细结果
import pandas as pd
# 转换为 DataFrame,查看每个样本的详细得分
df = results.to_pandas()
print(df[["question", "faithfulness", "answer_relevancy",
"context_precision", "context_recall"]].to_string())
1.4 如何构建评估数据集
评估数据集的质量直接决定评估结论的可信度。以下是三种构建方式:
1.4.1 方式一:从生产日志采样
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
def collect_rag_outputs(rag_pipeline, questions: list[str]) -> list[dict]:
"""从 RAG 管道收集输入输出,构建评估样本"""
samples = []
for question in questions:
# 假设 rag_pipeline 返回 (answer, source_documents)
answer, source_docs = rag_pipeline.invoke(question)
samples.append({
"question": question,
"answer": answer,
"contexts": [doc.page_content for doc in source_docs],
# ground_truth 需要人工标注,此处留空
"ground_truth": None,
})
return samples
1.4.2 方式二:用 LLM 自动生成评估问题
def generate_eval_questions(documents: list[str], n_questions: int = 50) -> list[str]:
"""从文档库自动生成评估问题"""
llm = ChatOpenAI(model="gpt-4o-mini")
questions = []
for doc in documents[:n_questions]:
response = llm.invoke([
HumanMessage(content=f"""基于以下文档内容,生成2个高质量的评估问题:
1. 问题应该能用文档中的信息直接回答
2. 问题应该是用户可能真实提出的
3. 只输出问题,每行一个,不加序号
文档内容:
{doc[:500]}""")
])
new_questions = response.content.strip().split("\n")
questions.extend([q.strip() for q in new_questions if q.strip()])
return questions[:n_questions]
1.4.3 方式三:使用 RAGAS 内置的测试集生成
from ragas.testset import TestsetGenerator
from langchain_community.document_loaders import DirectoryLoader
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
# 加载文档
loader = DirectoryLoader("./docs", glob="**/*.md")
documents = loader.load()
# 自动生成测试集(RAGAS 会自动生成问题和 ground truth)
generator = TestsetGenerator.from_langchain(
generator_llm=ChatOpenAI(model="gpt-4o"),
critic_llm=ChatOpenAI(model="gpt-4o"),
embeddings=OpenAIEmbeddings(),
)
testset = generator.generate_with_langchain_docs(
documents,
test_size=50,
distributions={"simple": 0.5, "reasoning": 0.25, "multi_context": 0.25},
)
eval_dataset = testset.to_dataset()
1.5 如何解读评估结果
以下是各指标得分区间的参考解读:
| 指标 | 得分区间 | 含义 | 优化方向 |
|---|---|---|---|
| Faithfulness | < 0.7 | 严重幻觉问题 | 检查 prompt,增加"只根据文档回答"的约束 |
| Faithfulness | 0.7-0.9 | 基本可用,有改进空间 | 优化 prompt 模板,考虑更强的模型 |
| Faithfulness | > 0.9 | 良好 | 保持 |
| Answer Relevancy | < 0.7 | 答案经常跑题 | 优化 prompt,增加"直接回答问题"的指令 |
| Context Precision | < 0.6 | 检索噪音过多 | 增加重排序(re-ranking)步骤,调整 Top-K |
| Context Recall | < 0.7 | 检索遗漏关键信息 | 优化分块策略,调整嵌入模型,增大 Top-K |
联合诊断:
高 Faithfulness + 低 Context Recall
→ 模型只用检索到的文档回答,但检索不完整
→ 优先优化检索模块(分块、嵌入模型、检索策略)
低 Faithfulness + 高 Context Recall
→ 所需信息都检索到了,但模型没有严格遵循文档
→ 优先优化 prompt,增加忠实度约束,考虑换更强的 LLM
低 Context Precision + 低 Answer Relevancy
→ 检索引入大量噪音,导致模型产生了不相关的答案
→ 优先优化检索质量,引入重排序
1.6 RAGAS 评估流程图
1.7 持续评估:集成到 CI/CD 流程
CI/CD(持续集成/持续交付)是软件开发中自动化测试和部署的工程实践,每次代码变更时自动运行测试,确保质量达标才能上线。
评估不应该是一次性的工作,而应该作为 RAG 系统迭代的质量门禁。
# eval_pipeline.py
import json
import sys
from datetime import datetime
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision, context_recall
from datasets import Dataset
# 质量阈值配置
QUALITY_THRESHOLDS = {
"faithfulness": 0.85,
"answer_relevancy": 0.80,
"context_precision": 0.75,
"context_recall": 0.70,
}
def load_eval_dataset(path: str) -> Dataset:
with open(path) as f:
data = json.load(f)
return Dataset.from_dict(data)
def run_evaluation(dataset_path: str) -> dict:
dataset = load_eval_dataset(dataset_path)
results = evaluate(
dataset=dataset,
metrics=[faithfulness, answer_relevancy, context_precision, context_recall],
)
scores = dict(results)
return scores
def check_quality_gates(scores: dict) -> tuple[bool, list[str]]:
"""检查是否通过质量门禁"""
failures = []
for metric, threshold in QUALITY_THRESHOLDS.items():
if metric in scores and scores[metric] < threshold:
failures.append(
f"{metric}: {scores[metric]:.3f} < threshold {threshold}"
)
return len(failures) == 0, failures
def save_results(scores: dict, output_path: str):
"""保存评估结果用于趋势追踪"""
record = {
"timestamp": datetime.now().isoformat(),
"scores": scores,
}
with open(output_path, "a") as f:
f.write(json.dumps(record, ensure_ascii=False) + "\n")
if __name__ == "__main__":
dataset_path = sys.argv[1] if len(sys.argv) > 1 else "eval_dataset.json"
print("Running RAGAS evaluation...")
scores = run_evaluation(dataset_path)
print("\nEvaluation Results:")
for metric, score in scores.items():
status = "PASS" if score >= QUALITY_THRESHOLDS.get(metric, 0) else "FAIL"
print(f" {metric}: {score:.3f} [{status}]")
passed, failures = check_quality_gates(scores)
save_results(scores, "eval_history.jsonl")
if not passed:
print("\nQuality gate FAILED:")
for failure in failures:
print(f" - {failure}")
sys.exit(1) # 非零退出码,触发 CI 失败
else:
print("\nAll quality gates PASSED.")
sys.exit(0)
在 GitHub Actions(GitHub 提供的 CI/CD 自动化工具,可以在代码推送时自动触发脚本执行)中集成:
# .github/workflows/rag-eval.yml
name: RAG Quality Evaluation
on:
pull_request:
paths:
- 'rag/**'
- 'prompts/**'
- 'indexing/**'
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install dependencies
run: pip install ragas langchain-openai datasets
- name: Run RAG evaluation
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: python eval_pipeline.py eval_dataset.json
这样每次修改检索逻辑或 prompt 模板时,CI 流程会自动运行评估,只有所有质量指标达标才允许合并代码。