# RAG上线评估指标体系:六大核心指标与压测实战全解析

RAG上线评估指标体系:六大核心指标与压测实战全解析

导读:你的 RAG 系统跑通了,能回答问题了------但这够吗?召回率有没有90%?引用的来源对不对?该拒答的问题有没有硬答?上线前不评估,上线后用户骂。本文从 RAG 系统上线的六大核心指标出发,拆解每个指标的计算方式、达标标准和评测方法,附标准问答对设计思路和批量压测脚本,帮你建立从开发到上线的完整质量保障体系。

适合读者

  • RAG 系统开发完成、准备上线评估的开发者
  • 需要设计 RAG 质量评估体系的工程师
  • 准备 RAG 面试、需要回答"RAG怎么评估"的同学
  • 需要向领导汇报"系统质量达标"的技术负责人

阅读收益

  • 掌握六大核心指标:Recall@K、MRR、引用准确率、拒答准确率、事实准确率、幻觉率
  • 理解每个指标的达标标准和计算方式
  • 学会设计标准问答对(含应答应拒答)
  • 掌握批量压测脚本的编写方法
  • 获得可直接落地的上线评估检查清单

目录

  1. 为什么需要系统化的评估体系
  2. 六大核心指标详解
  3. 指标一:Recall@K(召回率)
  4. 指标二:MRR(平均倒数排名)
  5. [指标三:Citation Precision(引用准确率)](#指标三:Citation Precision(引用准确率))
  6. [指标四:Refusal Accuracy(拒答准确率)](#指标四:Refusal Accuracy(拒答准确率))
  7. [指标五:Factual Accuracy(事实准确率)](#指标五:Factual Accuracy(事实准确率))
  8. [指标六:Hallucination Rate(幻觉率)](#指标六:Hallucination Rate(幻觉率))
  9. 标准问答对设计
  10. 批量压测脚本
  11. 上线评估检查清单
  12. 面试速答版
  13. 总结与延伸
  14. 文末互动

1. 为什么需要系统化的评估体系

1.1 直觉评估的陷阱

复制代码
错误做法:
  "我问了10个问题,看起来都答对了,可以上线了"

问题:
  - 10个问题太少,不能代表真实分布
  - "看起来对"不等于"事实对"
  - 没测拒答场景(不该答的问题硬答了没发现)
  - 没测边界场景(极端query的表现)
  - 没量化指标,无法跟踪优化效果

1.2 系统化评估的价值

复制代码
系统化评估 = 可量化 + 可对比 + 可回归

可量化:召回率85% vs 90%,清楚知道差多少
可对比:vector模式 vs hybrid模式,哪个更好
可回归:优化后重跑评测,确保没退化

2. 六大核心指标详解

指标 全称 含义 达标标准 计算方式
Recall@K 召回率 相关文档是否被召回 >= 0.90 召回的相关文档 / 全部相关文档
MRR 平均倒数排名 正确答案排多靠前 >= 0.75 1/排名 的平均值
Citation Precision 引用准确率 引用的来源对不对 >= 0.95 正确引用 / 总引用
Refusal Accuracy 拒答准确率 不该答的是否拒答 >= 0.95 正确拒答 / 应拒答总数
Factual Accuracy 事实准确率 回答中的事实对不对 >= 0.90 正确事实 / 总事实
Hallucination Rate 幻觉率 编造事实的比例 <= 0.10 幻觉事实 / 总事实

记忆口诀:召回九成排名七成五,引用拒答各九成五,事实九成幻觉一成。


3. 指标一:Recall@K(召回率)

3.1 定义

复制代码
Recall@K = 召回的相关文档数 / 全部相关文档数

示例:
  知识库中有5条关于"年假"的文档
  检索Top-10召回了其中4条
  Recall@10 = 4/5 = 0.80

3.2 为什么重要

复制代码
Recall低 = 相关资料没被召回 → LLM看不到正确资料 → 可能瞎编

场景:
  用户问"年假几天"
  知识库里有"员工手册v2.4:年假10天"
  但向量检索没召回这条 → LLM不知道 → 瞎编"年假5天"

3.3 计算方法

python 复制代码
def recall_at_k(
    retrieved_ids: list[str],
    relevant_ids: list[str],
    k: int,
) -> float:
    """计算Recall@K"""
    retrieved_top_k = set(retrieved_ids[:k])
    relevant = set(relevant_ids)

    recalled = len(retrieved_top_k & relevant)
    total_relevant = len(relevant)

    return recalled / total_relevant if total_relevant > 0 else 0.0

4. 指标二:MRR(平均倒数排名)

4.1 定义

复制代码
MRR = 平均倒数排名

对每条查询:
  找到第一个相关文档的排名位置 rank
  倒数 = 1/rank

MRR = 所有查询的倒数之和 / 查询总数

示例:
  查询1:第一个相关文档排第2 → 1/2 = 0.5
  查询2:第一个相关文档排第1 → 1/1 = 1.0
  查询3:没有相关文档 → 0
  MRR = (0.5 + 1.0 + 0) / 3 = 0.5

4.2 为什么重要

复制代码
MRR衡量"正确答案排多靠前":
  MRR=1.0 → 正确答案总是排第1
  MRR=0.5 → 正确答案平均排第2
  MRR=0.1 → 正确答案平均排第10

即使Recall@K很高(召回了),但如果正确答案排在第10,
而Top-5只送前5条给LLM,LLM还是看不到正确答案。

5. 指标三:Citation Precision(引用准确率)

5.1 定义

复制代码
Citation Precision = 正确引用 / 总引用数

"正确引用" = 引用编号指向的文档确实包含该事实

示例:
  回答:"年假10天[1]"
  文档[1]内容:"员工每年享有10天带薪年假"
  → 引用正确

  回答:"年假10天[1]"
  文档[1]内容:"公积金提取需要身份证"
  → 引用错误!文档[1]根本没提年假

5.2 为什么重要

复制代码
引用准确 = 可追溯 = 可验证 = 可信
引用错误 = 假溯源 = 用户发现被骗 = 系统不可信

场景:
  模型回答"根据员工手册v2.4,年假10天[1]"
  用户去查员工手册v2.4 → 发现根本没这句话
  → 用户信任崩塌

6. 指标四:Refusal Accuracy(拒答准确率)

6.1 定义

复制代码
Refusal Accuracy = 正确拒答数 / 应该拒答的总数

"应该拒答" = 知识库中没有相关资料的问题
"正确拒答" = 系统如实说明"没有找到相关资料"

错误类型:
  假阳性(False Positive):该答的拒答了
  假阴性(False Negative):不该答的硬答了

拒答准确率关注:假阴性率要低(不该答的别硬答)

6.2 为什么重要

复制代码
答错比不答更可怕:
  不答 → 用户知道系统不知道 → 可以去问别人
  硬答 → 用户拿到错误信息 → 可能做错误决策

场景:
  用户问"公司年终奖怎么发"
  知识库里没有年终奖政策
  系统硬答:"根据公司规定,年终奖为2个月工资..."
  → 编的!用户信以为真,年底发现根本没有

7. 指标五:Factual Accuracy(事实准确率)

7.1 定义

复制代码
Factual Accuracy = 正确事实数 / 回答中总事实数

"事实" = 回答中提到的具体信息(数字、日期、人名、政策条款等)

示例:
  回答:"年假10天,需要入职满1年才能享受,申请需提前3天"
  事实1:年假10天 ✓
  事实2:入职满1年 ✓
  事实3:提前3天申请 ✗(实际是提前1天)
  
  Factual Accuracy = 2/3 = 0.67

7.2 评测方法

python 复制代码
def check_factual_accuracy(answer: str, ground_truth: dict) -> float:
    """事实准确率人工/半自动评测"""
    # 1. 提取回答中的关键事实
    facts = extract_facts(answer)
    # facts = ["年假10天", "入职满1年", "提前3天"]

    # 2. 与标准答案比对
    correct = 0
    for fact in facts:
        if fact in ground_truth["correct_facts"]:
            correct += 1
        elif fact in ground_truth["wrong_facts"]:
            pass  # 错误事实
        else:
            # 不确定,标记需人工审核
            pass

    return correct / len(facts) if facts else 1.0

8. 指标六:Hallucination Rate(幻觉率)

8.1 定义

复制代码
Hallucination Rate = 幻觉事实数 / 回答中总事实数

幻觉事实 = 回答中编造的事实(资料中没有的)

Factual Accuracy + Hallucination Rate <= 1.0
(因为有些事实"不确定",可能资料有但没检索到)

8.2 为什么单独监控幻觉率

复制代码
Factual Accuracy = 0.90 看起来不错
但 Hallucination Rate = 0.15 很危险

意味着:
  90%的事实是对的
  但15%的事实是编造的

用户更关心"有没有编造的",而不是"大部分对不对"

9. 标准问答对设计

9.1 设计原则

复制代码
标准问答对需要覆盖:
  1. 常见应回答问题(60%)
     - 高频问题:年假、报销、请假
     - 边界问题:数字精确匹配、多条件组合

  2. 应该拒答的问题(30%)
     - 知识库外的问题:公司CEO是谁
     - 模糊问题:"那个政策"
     - 无关问题:"今天天气怎么样"

  3. 陷阱问题(10%)
     - 诱导幻觉:"听说我们公司年终奖是5个月?"
     - 矛盾信息:测试模型是否会强行合成
     - 超长问题:测试上下文处理能力

总数:50-100条

9.2 问答对示例

python 复制代码
"""标准问答对设计示例"""
from dataclasses import dataclass
from typing import Literal

@dataclass
class QAItem:
    query: str                    # 用户问题
    category: Literal[            # 问题类别
        "normal",                 # 正常应回答
        "refusal",                # 应该拒答
        "trap",                   # 陷阱问题
    ]
    relevant_doc_ids: list[str]  # 应该召回的文档ID(normal用)
    should_refuse: bool          # 是否应该拒答
    expected_facts: list[str]    # 回答中应包含的事实
    ground_truth: str | None     # 标准答案(用于人工评判)


# 正常问题
test_cases = [
    QAItem(
        query="年假几天",
        category="normal",
        relevant_doc_ids=["chunk_annual_leave_v24"],
        should_refuse=False,
        expected_facts=["10天", "带薪"],
        ground_truth="根据员工手册v2.4,正式员工每年享有10天带薪年假。",
    ),
    QAItem(
        query="怎么申请公积金提取",
        category="normal",
        relevant_doc_ids=["chunk_gjj_01", "chunk_gjj_02"],
        should_refuse=False,
        expected_facts=["身份证", "公积金卡", "申请表"],
        ground_truth=None,
    ),
    # 应该拒答的问题
    QAItem(
        query="公司CEO是谁",
        category="refusal",
        relevant_doc_ids=[],
        should_refuse=True,
        expected_facts=[],
        ground_truth=None,
    ),
    QAItem(
        query="今天天气怎么样",
        category="refusal",
        relevant_doc_ids=[],
        should_refuse=True,
        expected_facts=[],
        ground_truth=None,
    ),
    # 陷阱问题
    QAItem(
        query="听说我们公司年终奖是5个月工资,是真的吗?",
        category="trap",
        relevant_doc_ids=[],
        should_refuse=True,
        expected_facts=[],
        ground_truth=None,
    ),
]

10. 批量压测脚本

python 复制代码
"""RAG系统批量压测脚本"""
import asyncio
import json
from dataclasses import dataclass, asdict
from datetime import datetime


@dataclass
class EvaluationResult:
    query: str
    category: str
    answer: str
    retrieved_docs: list[str]
    recall: float
    mrr: float
    refused: bool
    should_refuse: bool
    citation_correct: float
    factual_score: float


class RAGEvaluator:
    def __init__(self, rag_service):
        self.rag = rag_service

    async def evaluate_single(self, qa: QAItem) -> EvaluationResult:
        """评测单个问答对"""
        # 1. 执行RAG查询
        result = await self.rag.ask(qa.query)

        # 2. 计算Recall@K
        retrieved_ids = [d.metadata["chunk_id"] for d in result.retrieved_docs]
        recall = self._recall_at_k(retrieved_ids, qa.relevant_doc_ids, k=10)

        # 3. 计算MRR
        mrr = self._mrr(retrieved_ids, qa.relevant_doc_ids)

        # 4. 检查拒答
        refused = result.refused

        # 5. 引用准确率(简化版:检查引用编号是否有效)
        citation_precision = self._check_citations(
            result.answer, retrieved_ids
        )

        # 6. 事实准确率(需要人工标注或自动化提取)
        factual_score = self._check_facts(
            result.answer, qa.expected_facts
        )

        return EvaluationResult(
            query=qa.query,
            category=qa.category,
            answer=result.answer,
            retrieved_docs=retrieved_ids,
            recall=recall,
            mrr=mrr,
            refused=refused,
            should_refuse=qa.should_refuse,
            citation_correct=citation_precision,
            factual_score=factual_score,
        )

    async def evaluate_batch(
        self,
        test_cases: list[QAItem],
    ) -> dict:
        """批量评测,输出汇总报告"""
        results = []
        for qa in test_cases:
            result = await self.evaluate_single(qa)
            results.append(result)

        # 汇总统计
        normal_results = [r for r in results if r.category == "normal"]
        refusal_results = [r for r in results if r.category == "refusal"]

        metrics = {
            "total": len(results),
            "recall_at_10": sum(r.recall for r in normal_results) / len(normal_results),
            "mrr": sum(r.mrr for r in normal_results) / len(normal_results),
            "citation_precision": sum(r.citation_correct for r in results) / len(results),
            "refusal_accuracy": sum(
                1 for r in refusal_results if r.refused == r.should_refuse
            ) / len(refusal_results) if refusal_results else 0,
            "factual_accuracy": sum(r.factual_score for r in normal_results) / len(normal_results),
        }

        # 计算幻觉率(1 - 事实准确率,近似)
        metrics["hallucination_rate"] = 1 - metrics["factual_accuracy"]

        return {
            "metrics": metrics,
            "details": [asdict(r) for r in results],
            "timestamp": datetime.now().isoformat(),
        }

    def _recall_at_k(self, retrieved, relevant, k):
        if not relevant:
            return 1.0
        recalled = len(set(retrieved[:k]) & set(relevant))
        return recalled / len(relevant)

    def _mrr(self, retrieved, relevant):
        for i, doc_id in enumerate(retrieved):
            if doc_id in relevant:
                return 1.0 / (i + 1)
        return 0.0

    def _check_citations(self, answer, retrieved_ids):
        import re
        citations = re.findall(r'\[(\d+)\]', answer)
        if not citations:
            return 1.0  # 没有引用,不算错
        valid = sum(1 for c in citations if int(c) <= len(retrieved_ids))
        return valid / len(citations)

    def _check_facts(self, answer, expected_facts):
        if not expected_facts:
            return 1.0
        found = sum(1 for fact in expected_facts if fact in answer)
        return found / len(expected_facts)


# 使用示例
async def main():
    evaluator = RAGEvaluator(rag_service)
    report = await evaluator.evaluate_batch(test_cases)

    print("=" * 50)
    print("RAG系统评测报告")
    print("=" * 50)
    for metric, value in report["metrics"].items():
        print(f"{metric}: {value:.2%}")

    # 保存报告
    with open("rag_evaluation_report.json", "w", encoding="utf-8") as f:
        json.dump(report, f, ensure_ascii=False, indent=2)


if __name__ == "__main__":
    asyncio.run(main())

11. 上线评估检查清单

序号 检查项 达标标准 验证方法
1 Recall@10 >= 90% 标准问答对批量跑
2 MRR >= 0.75 标准问答对批量跑
3 引用准确率 >= 95% 抽样检查引用与原文一致性
4 拒答准确率 >= 95% 测试应拒答问题
5 事实准确率 >= 90% 人工/半自动验证
6 幻觉率 <= 10% 1 - 事实准确率
7 标准问答对数量 >= 50条 检查测试集规模
8 覆盖度 常见+拒答+陷阱 检查测试集分布
9 检索延迟 P95 < 100ms 压测工具
10 端到端延迟 P95 < 3s 监控面板

12. 面试速答版

RAG系统上线需要关注六大指标:Recall@K(召回率,要90%+)、MRR(正确答案排多靠前,要0.75+)、引用准确率(引用的来源对不对,要95%+)、拒答准确率(不该答的是否拒答,要95%+)、事实准确率(回答中的事实对不对,要90%+)、幻觉率(编造事实的比例,要<10%)。评估方法是准备50-100条标准问答对,包含应回答、应拒答、陷阱问题三类,跑批量脚本出指标,对比不同检索模式效果,定期回归测试确保优化不引入退化。记忆口诀:召回九成排名七成五,引用拒答各九成五,事实九成幻觉一成。


13. 总结与延伸

13.1 核心知识点回顾

复制代码
六大指标:
  Recall@K:召回率,>=90%
  MRR:平均倒数排名,>=0.75
  Citation Precision:引用准确率,>=95%
  Refusal Accuracy:拒答准确率,>=95%
  Factual Accuracy:事实准确率,>=90%
  Hallucination Rate:幻觉率,<=10%

评估方法:
  准备50-100条标准问答对(60%正常+30%拒答+10%陷阱)
  批量跑评测脚本
  对比不同检索模式
  定期回归测试

记忆口诀:
  召回九成排名七成五,引用拒答各九成五,事实九成幻觉一成

13.2 延伸方向

  • 自动化评测:用LLM做评判员,自动判断回答质量
  • A/B测试:线上灰度发布,对比不同版本的指标变化
  • 用户反馈闭环:收集用户点赞/纠错反馈,动态更新评测集
  • 多维度评测:除六大指标外,增加可读性、完整性、友好度等维度

14. 文末互动

你的 RAG 系统上线前做过系统化的评估吗?用了哪些指标------只看召回率,还是也测了拒答准确率和幻觉率?评论区聊聊你的评估经验。

思考题:如果"事实准确率"需要人工逐条核对,50条问答对可能需要2-3小时。你有什么办法可以半自动化地评测事实准确率,减少人工成本?欢迎在评论区讨论。


本文聚焦 RAG 系统上线的六大评估指标和压测实战。如果觉得有帮助,欢迎点赞收藏,后续会更新自动化评测和用户反馈闭环的进阶内容。

相关推荐
Python 实战手记1 小时前
微信公众号跨主体迁移变更审核流程实操解析:场景条件、公证材料规范、避坑要点与校验脚本实现
人工智能
william_yangshun1 小时前
【AI Agent 实战】cindy 中文版:开箱即用的开源 AI 代理上手指南
人工智能·开源
我命由我123451 小时前
人脸识别 - 人脸识别选帧
java·人工智能·python·算法·安全·java-ee·人脸识别
宠友信息1 小时前
IM系统开发技术路线分析,即时通讯源码助力快速搭建聊天应用
java·spring boot·redis·websocket·mysql·uni-app·vue
Ai-_Man1 小时前
豆包收藏夹能批量导出吗?从底层逻辑拆解「AI导出鸭」如何解构这一技术难题
人工智能·ai·小程序
速易达网络1 小时前
宇树机器人具身智能研发的全技术栈
人工智能
ReleaseU1 小时前
PTC 模式深度实战:测试驱动开发的 Agent 化
人工智能·大模型
一只鹿鹿鹿2 小时前
数据资产管理解决方案(Word文件)
大数据·数据库·安全·web安全·系统安全
2401_894915532 小时前
Geo 优化源码部署实战:环境配置、参数调优完整指南
运维·服务器·数据库·tcp/ip·安全