RAG系统评估:检索质量与生成质量的联合评测方法
RAG系统评估:检索质量与生成质量的联合评测方法
一、RAG评估的盲区:检索好≠生成好,生成好≠整体好
RAG(检索增强生成)系统的评估面临一个独特的挑战:系统由检索和生成两个子模块串联组成,单独评估任何一个都无法反映整体质量。检索模块召回了相关文档,但生成模块可能忽略关键信息或产生幻觉;生成模块产出了流畅文本,但可能完全基于模型内部知识而非检索到的文档。
一个典型的评估误区:只评估检索的 Recall@K,发现召回率 90% 就认为系统没问题。但实际用户反馈却很差——因为生成模型虽然看到了正确的文档,却在回答中混入了与文档矛盾的信息。反过来,如果只评估生成文本的流畅度和相关性,可能忽略了检索模块引入的噪声文档对生成质量的负面影响。
RAG 系统的评估必须是联合的:检索质量影响生成质量,生成质量又反过来反映检索的有效性。需要一套端到端的评测框架,同时量化检索精度、生成忠实度和整体答案质量。
二、RAG 联合评测框架设计
flowchart TB subgraph 评测输入["评测输入"] I1[问题集<br/>Questions] I2[参考答案<br/>Ground Truth] I3[知识库<br/>Corpus] end subgraph 检索评估["检索质量评估"] R1[召回率<br/>Recall@K] R2[精确率<br/>Precision@K] R3[MRR<br/>Mean Reciprocal Rank] R4[上下文相关性<br/>Context Relevance] end subgraph 生成评估["生成质量评估"] G1[忠实度<br/>Faithfulness] G2[答案相关性<br/>Answer Relevancy] G3[答案正确性<br/>Answer Correctness] end subgraph 联合评估["联合评估指标"] J1[端到端准确率<br/>E2E Accuracy] J2[幻觉率<br/>Hallucination Rate] J3[信息利用率<br/>Context Utilization] J4[检索-生成一致性<br/>Retrieval-Generation Consistency] end I1 --> R1 I3 --> R1 R1 --> G1 R4 --> G1 I2 --> G3 G1 --> J2 G2 --> J4 G3 --> J1 R4 --> J3关键指标解析:
上下文相关性(Context Relevance):检索到的文档与问题的相关程度。高相关性意味着检索模块没有引入噪声。
忠实度(Faithfulness):生成答案是否忠实于检索到的文档,而非模型内部知识。这是 RAG 系统最核心的指标——如果答案不忠实于文档,RAG 就失去了意义。
答案相关性(Answer Relevancy):生成答案与问题的相关程度。一个忠实于文档但答非所问的答案同样没有价值。
信息利用率(Context Utilization):检索到的文档中有多少信息被生成答案实际利用。低利用率意味着检索了过多无关文档,浪费了 Token 预算。
三、RAG 评测框架的 Python 实现
3.1 忠实度评估
import json from dataclasses import dataclass @dataclass class RAGEvalSample: """RAG评测样本""" question: str retrieved_docs: list[str] generated_answer: str reference_answer: str class FaithfulnessEvaluator: """ 忠实度评估器 评估生成答案是否忠实于检索文档 方法:将答案拆分为声明,逐条验证是否可被文档支撑 """ def __init__(self, llm_client): self.llm = llm_client def evaluate(self, sample: RAGEvalSample) -> dict: """ 评估忠实度 返回:忠实度分数(0-1)和每条声明的验证结果 """ # 第一步:将答案拆分为独立声明 claims = self._extract_claims(sample.generated_answer) # 第二步:逐条验证声明是否可被文档支撑 verification_results = [] for claim in claims: supported = self._verify_claim(claim, sample.retrieved_docs) verification_results.append({ "claim": claim, "supported": supported, }) # 计算忠实度分数 supported_count = sum( 1 for r in verification_results if r["supported"]) faithfulness = supported_count / max(len(claims), 1) return { "faithfulness": faithfulness, "total_claims": len(claims), "supported_claims": supported_count, "unsupported_claims": len(claims) - supported_count, "details": verification_results, } def _extract_claims(self, answer: str) -> list[str]: """将答案拆分为独立声明""" prompt = f""" 请将以下答案拆分为独立的原子声明。每个声明应是一个可验证的事实陈述。 答案:{answer} 请以JSON数组格式输出,例如: ["声明1", "声明2", "声明3"] """ response = self.llm.chat(prompt) try: return json.loads(response) except json.JSONDecodeError: # 降级:按句号分割 return [s.strip() for s in answer.split("。") if s.strip()] def _verify_claim(self, claim: str, docs: list[str]) -> bool: """验证声明是否可被文档支撑""" docs_text = "\n".join(f"[文档{i+1}]: {doc}" for i, doc in enumerate(docs)) prompt = f""" 请判断以下声明是否可被给定的文档支撑。 声明:{claim} 文档: {docs_text} 请仅回答"是"或"否"。 """ response = self.llm.chat(prompt).strip() return response.startswith("是")3.2 端到端评测流水线
class RAGEvaluationPipeline: """RAG端到端评测流水线""" def __init__(self, llm_client, rag_system): self.llm = llm_client self.rag = rag_system self.faithfulness_eval = FaithfulnessEvaluator(llm_client) def evaluate_dataset( self, dataset: list[dict], top_k: int = 5, ) -> dict: """ 对完整数据集执行评测 dataset中每个样本包含:question, reference_answer """ results = [] for item in dataset: # 执行RAG推理 rag_output = self.rag.query( item["question"], top_k=top_k) sample = RAGEvalSample( question=item["question"], retrieved_docs=rag_output.retrieved_docs, generated_answer=rag_output.answer, reference_answer=item["reference_answer"], ) # 评估各项指标 eval_result = self._evaluate_single(sample) results.append(eval_result) # 聚合结果 return self._aggregate(results) def _evaluate_single(self, sample: RAGEvalSample) -> dict: """评估单个样本的所有指标""" # 检索质量 context_relevance = self._compute_context_relevance(sample) # 生成质量 faithfulness = self.faithfulness_eval.evaluate(sample) answer_relevancy = self._compute_answer_relevancy(sample) # 联合指标 hallucination_rate = 1.0 - faithfulness["faithfulness"] return { "question": sample.question, "context_relevance": context_relevance, "faithfulness": faithfulness["faithfulness"], "answer_relevancy": answer_relevancy, "hallucination_rate": hallucination_rate, } def _compute_context_relevance(self, sample: RAGEvalSample) -> float: """计算上下文相关性""" prompt = f""" 请评估以下检索到的文档与问题的相关程度,给出0-1的分数。 问题:{sample.question} 文档: {chr(10).join(sample.retrieved_docs[:3])} 请仅输出0到1之间的数字。 """ response = self.llm.chat(prompt).strip() try: return float(response) except ValueError: return 0.5 def _compute_answer_relevancy(self, sample: RAGEvalSample) -> float: """计算答案相关性""" prompt = f""" 请评估以下答案与问题的相关程度,给出0-1的分数。 问题:{sample.question} 答案:{sample.generated_answer} 请仅输出0到1之间的数字。 """ response = self.llm.chat(prompt).strip() try: return float(response) except ValueError: return 0.5 def _aggregate(self, results: list[dict]) -> dict: """聚合评测结果""" n = len(results) return { "total_samples": n, "avg_context_relevance": sum( r["context_relevance"] for r in results) / n, "avg_faithfulness": sum( r["faithfulness"] for r in results) / n, "avg_answer_relevancy": sum( r["answer_relevancy"] for r in results) / n, "avg_hallucination_rate": sum( r["hallucination_rate"] for r in results) / n, }四、RAG 评测的架构权衡
LLM-as-Judge 的可靠性
使用 LLM 评估 LLM 的输出存在"同源偏差"——评估 LLM 可能对同类模型的输出更宽容。缓解方案是使用与生成模型不同的 LLM 作为评估器,并定期用人工评估校准。
评测成本
每个样本的忠实度评估需要多次 LLM 调用(声明提取 + 逐条验证),100 个样本可能需要 500+ 次调用。建议先在小样本上验证评测框架的有效性,再扩展到完整数据集。
参考答案的必要性
某些指标(如答案正确性)需要参考答案,但构建高质量参考答案的成本很高。忠实度和上下文相关性不需要参考答案,更适合日常自动化评测。
适用边界:RAG 联合评测适合知识库更新频繁、需要量化检索与生成协同效果的场景。对于简单的 FAQ 系统,关键词匹配评测即可。
五、总结
RAG 系统的评估必须同时关注检索质量和生成质量,以及两者的协同效果。落地路线建议:
- 忠实度优先:先建立忠实度评估,这是 RAG 系统最核心的质量指标。
- 检索-生成联合:不要单独评估检索或生成,关注端到端的答案质量。
- 幻觉监控:将幻觉率纳入日常监控,超过阈值触发知识库或检索策略的优化。
- 人工校准:定期用人工评估校准自动化指标,确保评测结果与用户感知一致。
