📖 本章学习目标
- 使用 Hit Rate、MRR 和 NDCG 量化检索质量
- 使用 Faithfulness 和 Answer Relevancy 衡量生成质量
- 用 RAGAS 框架对 RAG 系统进行自动化、可复现的评估
- 构建和维护黄金标准问答数据集
在《第8章:RAG 的局限性及应对策略》中,我们依赖定性判断与经验直觉来识别系统瓶颈;在《第9章:高级检索策略》、《第10章:检索后处理与重排序》、《第11章:Prompt 工程与上下文优化》中,我们基于工程经验进行启发式调优。能做到前面这些其实已经是一个基本合格的RAG工程了。然而,如果我还问你“当前RAG 系统当前处于何种水平?调整 chunk_size 从 500 增至 800 是否带来了可度量的改善?”这种更精确的量化追问时,显然这种定性判断便无法提供可靠的答案。
评估体系正是为回答上述问题而存在。它使 RAG 系统的优化过程从定性直觉与主观猜测,转向以数据和指标为驱动的科学方法。缺乏可量化的评估,就谈不上有效的工程优化。这是 RAG 系统落地实践中必须遵循的基本原则。
没有评估,就没有优化。 这是工程实践的铁律。
一、RAG 评估的双维度框架
1. 双维度框架的定义
RAG 系统的核心架构由**检索(Retrieval)与生成(Generation)**两个模块构成。这两个模块在功能上相互解耦,各自承担不同的职责:检索模块负责从外部知识库中召回与查询相关的上下文,生成模块则基于检索到的上下文和原始查询合成最终答案。因此,这两个模块的失效模式与评估逻辑也存在本质差异。
双维度评估框架正是基于这种架构解耦而设计的。它将 RAG 系统的整体性能拆解为检索质量和生成质量两个独立但相互关联的评估维度,分别衡量"找对了吗"和"答对了吗"这两个核心问题。
graph TD
A[RAG 评估] --> B[检索质量<br/>找对了吗?]
A --> C[生成质量<br/>答对了吗?]
B --> B1[Hit Rate-K]
B --> B2[MRR]
B --> B3[NDCG-K]
C --> C1[Faithfulness<br/>忠实度]
C --> C2[Answer Relevancy<br/>相关性]
C --> C3[Context Precision<br/>上下文精确率]
两个维度同样重要,但评估方式截然不同:
- 检索质量:通常采用自动化评估方式。由于检索结果可以与已知的正确答案进行精确对比,因此可以通过计算召回率、排序质量等指标进行量化。
- 生成质量:通常需要借助 LLM-as-Judge 或人工评分。由于生成答案以自然语言形式呈现,难以通过精确匹配的方式进行评估,因此需要引入语义层面的判断。
2. 为什么需要双维度评估?
如上文所述,检索与生成两个模块在功能上相互解耦,各自承担不同的职责,也各自存在独立的失效模式。 如果仅以最终答案的正确性作为唯一评估标准,将无法定位问题究竟出在哪个环节,从而导致优化方向出现偏差,甚至可能掩盖系统的真实问题。例如 检索质量高但生成质量低则可能是模型未能有效利用检索到的相关文档,进而导致可能产生幻觉或忽略关键信息;而检索质量低但生成质量高说明模型依赖训练数据中的记忆进行回答,答案虽然正确但无法追溯来源,违背了 RAG 系统的设计初衷。
因此,同时对检索质量和生成质量进行监控,是全面诊断 RAG 系统性能瓶颈、建立可量化优化闭环的必要前提。
3. 评估流程全景
graph LR
A[构建评估数据集] --> B[运行 RAG 系统]
B --> C[收集结果]
C --> D[计算指标]
D --> E[分析改进]
E --> A
这是一个持续迭代的闭环过程,具体步骤如下:
(1)构建评估数据集(黄金标准):准备一组具有代表性的查询及其对应的标准答案和相关文档,作为评估的基准。
(2)运行 RAG 系统:对评估数据集中的每个查询执行完整的 RAG 流程,记录检索结果和生成答案。
(3) 计算各项指标:根据检索质量和生成质量的评估标准,分别计算对应的量化指标。
(4)分析薄弱环节:通过指标分析定位系统在检索或生成环节的具体问题,制定针对性的优化策略。
(5)更新评估集并重新迭代:根据优化方向补充或调整评估数据,回到步骤 1 进行新一轮的评估与验证。
该流程的核心在于建立可重复、可量化的评估机制,使 RAG 系统的优化从经验驱动转向数据驱动。
二、检索质量指标
检索质量评估的核心命题在于:检索模块召回的候选文档集合中,是否包含能够支撑正确回答所需的关键信息? 该评估独立于生成模块,仅关注检索环节的信息召回能力与排序质量。
以下三个指标构成了检索质量评估的核心体系,它们从不同粒度刻画了检索系统的性能表现。
1. Hit Rate @K(命中率)
Hit Rate @K 是最基础的召回能力指标,用于衡量在前 K 个检索结果中,至少存在一个与查询相关的文档的比例。其数学定义为:
其中 为查询总数, 为指示函数,当第 个查询的 Top-K 结果中存在至少一个相关文档时取值为 1,否则为 0。你也可以简单的理解为Hit Rate @K = (命中次数) / (总查询数)。
interface EvalResult {
query: string;
retrievedDocIds: string[]; // 检索返回的文档 ID 列表(按排序)
relevantDocIds: string[]; // 人工标注的相关文档 ID 集合
}
function hitRateAtK(results: EvalResult[], k: number): number {
let hits = 0;
for (const result of results) {
const topK = result.retrievedDocIds.slice(0, k);
const hasRelevant = topK.some(id => result.relevantDocIds.includes(id));
if (hasRelevant) {
hits++;
}
}
return hits / results.length;
}
// 使用示例
const testResults: EvalResult[] = [
{
query: "如何配置数据库连接池?",
retrievedDocIds: ["doc_123", "doc_456", "doc_789"],
relevantDocIds: ["doc_123", "doc_999"],
},
// ... 更多测试结果
];
const hr = hitRateAtK(testResults, 5);
console.log(`Hit Rate @5: ${(hr * 100).toFixed(1)}%`);
指标解读:
- Hit Rate @5 = 80% 表示在 80% 的查询中,Top-5 检索结果里至少包含一个相关文档。
- 理想值为 100%,即所有查询均能成功召回相关文档。
- 实际生产环境中,该指标的典型值区间为 60%–90%,具体取决于知识库的覆盖密度、分块策略以及检索算法的表达能力。
该指标的优势在于定义直观,易于理解和向非技术干系人解释、计算复杂度低,适合大规模数据集的快速评估,并且能够直接反映检索系统的召回覆盖能力。但其局限性同样明显:它不区分排名位置,相关文档排在第 1 位与排在第 K 位得分相同,无法反映排序质量;同时也不考虑相关文档的数量,召回 1 个相关文档与召回全部相关文档得分相同,无法体现召回的完整性。此外,该指标对 K 值较为敏感,K 越大命中率越高,但过大的 K 会引入噪声,增加后续生成模块的处理负担。
2. MRR(Mean Reciprocal Rank,平均倒数排名)
MRR 用于衡量第一个相关结果在检索列表中的排名位置。排名越靠前,得分越高,因此该指标对顶部结果的质量更为敏感。其数学定义为:
其中 为第 个查询中第一个相关文档的排名位置(从 1 开始计数)。若该查询的检索结果中不存在任何相关文档,则 。
function mrr(results: EvalResult[]): number {
let sumReciprocalRanks = 0;
for (const result of results) {
let found = false;
for (let i = 0; i < result.retrievedDocIds.length; i++) {
if (result.relevantDocIds.includes(result.retrievedDocIds[i])) {
sumReciprocalRanks += 1 / (i + 1); // 排名第 i+1 位
found = true;
break;
}
}
if (!found) {
sumReciprocalRanks += 0; // 未找到相关结果
}
}
return sumReciprocalRanks / results.length;
}
// 使用
const mrrScore = mrr(testResults);
console.log(`MRR: ${mrrScore.toFixed(3)}`);
以 3 个查询为例,若第一个相关文档分别排在第 1 位、第 3 位,以及未找到任何相关文档,则对应的倒数排名分别为 1.0、0.333 和 0,最终 MRR = (1.0 + 0.333 + 0) / 3 ≈ 0.444。
指标解读:
- MRR = 1.0:所有查询的第一个检索结果均为相关文档,达到完美排序。
- MRR = 0.5:平均而言,第一个相关文档排在第 2 位左右。
- MRR = 0.1:第一个相关文档平均排在第 10 位之后,排序质量较差。
在实际工程中,MRR > 0.7 可视为优秀,0.5–0.7 为良好,0.3–0.5 为一般,< 0.3 则表明排序质量存在较大问题。
该指标对顶部结果的位置敏感,能够反映检索结果的排序质量,且计算简单,适合与 Hit Rate 配合使用形成"召回 + 排序"的联合评估。但其仅关注第一个相关结果,忽略后续相关文档的存在与位置,当存在多个相关文档时无法全面反映检索系统的整体召回能力,同时对长尾查询或知识库覆盖不足的场景区分度有限。
3. NDCG @K(归一化折损累计增益)
NDCG全称是Normalized Discounted Cumulative Gain,是检索质量评估中最精细的指标,它同时考虑了相关性等级、排名位置以及多个相关文档的综合贡献,能够全面刻画检索系统的排序质量。其计算过程分为三个步骤。
(1)DCG(Discounted Cumulative Gain)
第一步是计算DCG(折损累计增益)。DCG 的核心思想是:相关性越高的文档应该排在越靠前的位置,排名越靠后的结果对整体质量的贡献应该被"折损"。其数学定义为:
其中 为第 个检索结果的相关性得分,通常采用多级标注(如:高度相关 = 2,部分相关 = 1,不相关 = 0)。分母 为位置折扣因子,排名越靠后折扣越大。
(2)IDCG(Ideal DCG)
第二步是计算 IDCG(理想折损累计增益)。IDCG 是将收集到的相关性得分按照从大到小的顺序重新排列,得到理想状态下的排序序列,再排序后的理想序列代入 DCG 公式进行计算计算理想情况下的 DCG 值,作为归一化的基准。
其中 是理想排序中第 个位置的相关性得分, 为截断位置。
(3)NDCG(Normalized DCG)
第三步是计算 NDCG(归一化折损累计增益)。NDCG 是 DCG 与 IDCG 的比值:
NDCG 的取值范围为 [0, 1],值越接近 1 表示排序质量越接近理想状态。
interface ScoredResult {
docId: string;
relevance: number; // 相关性得分:0=不相关, 1=部分相关, 2=高度相关
}
function ndcgAtK(results: ScoredResult[][], k: number): number {
let sumNdcg = 0;
for (const queryResults of results) {
const topK = queryResults.slice(0, k);
// 计算 DCG
let dcg = 0;
for (let i = 0; i < topK.length; i++) {
dcg += topK[i].relevance / Math.log2(i + 2); // i+2 因为 log2(1)=0
}
// 计算 IDCG(理想情况:按相关性降序排列)
const ideal = [...topK].sort((a, b) => b.relevance - a.relevance);
let idcg = 0;
for (let i = 0; i < ideal.length; i++) {
idcg += ideal[i].relevance / Math.log2(i + 2);
}
// NDCG
const ndcg = idcg > 0 ? dcg / idcg : 0;
sumNdcg += ndcg;
}
return sumNdcg / results.length;
}
// 使用
const scoredResults: ScoredResult[][] = [
[
{ docId: "doc_123", relevance: 2 },
{ docId: "doc_456", relevance: 1 },
{ docId: "doc_789", relevance: 0 },
],
// ... 更多查询
];
const ndcg = ndcgAtK(scoredResults, 5);
console.log(`NDCG@5: ${ndcg.toFixed(3)}`);
指标解读:
- NDCG = 1.0:检索结果的排序与理想排序完全一致,达到完美排序质量。
- NDCG = 0.5:排序质量处于中等水平,存在明显优化空间。
- NDCG = 0.0:检索结果完全不相关或排序完全错误。
该指标同时考虑相关性等级和排名位置,是最全面的检索质量评估指标,支持多个相关文档的综合评估,能够反映检索系统的整体召回与排序能力。归一化后的取值范围也便于跨数据集、跨系统的横向对比。但其计算复杂度较高,不适合超大规模数据集的实时评估,同时需要细粒度的多级相关性标注(0/1/2 或更细),标注成本显著高于二值标注,且对标注一致性要求较高,不同标注者之间的主观差异可能影响指标稳定性。
4. 三个指标的对比与选型建议
| 指标 | 考虑排名 | 考虑多文档 | 考虑相关性等级 | 计算复杂度 | 适用场景 |
|---|---|---|---|---|---|
| Hit Rate @K | 否 | 否 | 否 | 低 | 快速评估召回覆盖能力 |
| MRR | 仅第一个 | 否 | 否 | 低 | 关注顶部结果的排序质量 |
| NDCG @K | 是 | 是 | 是 | 高 | 全面评估检索排序质量 |
在日常监控场景中,推荐采用 Hit Rate @5 与 MRR 的组合,两者计算开销低,能够覆盖召回能力与顶部排序质量两个核心维度。在进行深度分析时,若具备多级相关性标注的条件,可采用 NDCG @10 全面评估检索系统的排序质量。在 A/B 测试中,建议同时监控三个指标:Hit Rate 反映召回覆盖率的变化,MRR 反映顶部排序的改善,NDCG 反映整体排序质量的提升,三者结合能够完整刻画优化策略的综合效果。
三、生成质量指标
生成质量评估关注的是在检索模块已经返回了候选文档的前提下,生成模块能否基于这些上下文合成出准确、切题且可溯源的最终答案。与检索质量不同,生成质量评估的对象是自然语言文本,无法通过简单的字符串匹配或集合交集来判断对错,因此需要引入语义层面的理解与判断。
在深入具体指标之前,先介绍一下 RAGAS(Retrieval-Augmented Generation Assessment)评估框架。RAGAS 是目前 RAG 系统评估领域最主流的开源工具之一,它提供了一套标准化的评估流程和指标体系,能够自动化地对 RAG 系统的各个环节进行量化打分。
RAGAS 的核心设计理念是 "LLM-as-Judge"(大语言模型作为裁判):利用大语言模型对生成结果进行语义层面的判断,再通过数学方法将判断结果转化为可量化的分数。这种方式克服了传统 NLP 评估指标(如 BLEU、ROUGE)仅依赖词重叠匹配的局限性,能够更准确地反映答案在语义层面的质量。
以下四个指标均来自 RAGAS 框架,它们从不同角度刻画了生成环节的质量表现。其中 Faithfulness 和 Answer Relevancy 聚焦于答案本身的质量,而 Context Precision 和 Context Recall 则从检索结果对生成的支撑角度进行间接评估。
说明:RAGAS 的四个核心指标实际上跨越了检索和生成两个环节,其中Context Precision和Context Recall严格来讲属于检索质量指标,文中之所以如此划分章节是因为RAGAS 框架的四大核心指标体系中,这四个指标经常被放在一起讨论。从端到端评估视角来看共同构成 RAG 系统的完整评估闭环。
1. Faithfulness(忠实度)
Faithfulness 用于衡量答案中的每一项事实陈述是否都能在检索到的上下文找到依据。该指标的核心假设是一个高质量的 RAG 答案不应包含超出检索上下文支撑范围的信息。如果答案中出现了检索结果中完全没有提及的事实,则说明模型可能依赖了训练数据中的先验知识,甚至产生了幻觉。
RAGAS 框架采用 LLM-as-Judge 的方式自动评估忠实度,具体流程分为三步:首先由 LLM 从生成的答案中逐条抽取事实陈述;然后将每条陈述与检索上下文进行比对,判断该陈述是否能在上下文中找到支撑证据;最后将有支撑的陈述数量除以陈述总数,得到忠实度得分。
RAGAS 是纯 Python 库。如果你的 RAG 系统用 TypeScript 构建,可以通过 HTTP 接口或子进程方式与 RAGAS 评估脚本集成。下文的代码示例都是使用子进程调用Python脚本的方式,展示的是评估逻辑本身,而非 RAG 系统的的真实实现。运行子进程和Python的脚本在第五小节讲解。
RAGAS 实现:
// ragas.ts是封装好的用于调用Python脚本的模块
// evaluate: 执行评估的方法;FAITHFULNESS评估的类型,值为faithfulness
import { evaluate, FAITHFULNESS } from "./ragas";
const dataset = [
{
question: "部署流程是什么?",
answer: "部署分为三步:代码审查、预发验证、生产发布。",
contexts: [
"部署流程包括代码审查、预发环境验证和生产环境发布三个步骤。",
],
},
];
const result = await evaluate({
dataset,
metrics: [FAITHFULNESS],
});
console.log(`Faithfulness: ${result.scores.faithfulness.mean.toFixed(3)}`);
指标解读:
Faithfulness 的取值范围为 [0, 1]。得分为 1.0 表示答案中的每一条事实陈述都能在检索上下文中找到对应依据,达到理想状态;得分为 0.5 表示仅有一半的陈述有支撑,其余可能存在编造;得分为 0.0 则意味着答案内容与检索结果完全脱节。
以下两个典型案例可以更直观地说明该指标的含义:
// 高忠实度示例
const highFaithfulness = {
question: "预发环境地址是什么?",
answer: "预发环境地址为 https://staging.example.com [文档 1]",
contexts: ["预发环境地址为 https://staging.example.com"],
};
// Faithfulness ≈ 1.0(答案完全基于检索结果)
// 低忠实度示例
const lowFaithfulness = {
question: "如何回滚部署?",
answer: "部署回滚分为三步:停止服务、恢复旧版本、重启。",
contexts: ["部署流程包括代码审查、预发验证、生产发布。"],
};
// Faithfulness ≈ 0.0(答案中的"回滚步骤"在检索结果中不存在)
Faithfulness 《第8章:RAG 的局限性及应对策略》中讨论的幻觉残留问题。在工程实践中,该指标是检测模型幻觉的最直接手段。高忠实度通常意味着低幻觉率,而忠实度骤降往往是 Prompt 设计不当或检索上下文质量恶化的早期信号。
2. Answer Relevancy(答案相关性)
Answer Relevancy 用于衡量生成答案与原始查询之间的语义切题程度。即让答案完全忠实于检索结果,如果答非所问或偏离了用户的核心意图,从用户体验的角度来看仍然是失败的。该指标评估的是"答案是否回答了用户真正想问的问题"。
RAGAS 框架评估答案相关性的策略基于"反向生成"思想:首先让 LLM 根据给定的答案反向生成若干个虚拟问题,然后计算这些虚拟问题与原始查询之间的余弦相似度。如果答案能够精准回答原始问题,那么从答案中反向推导出的虚拟问题应当与原问题高度相似;反之,如果答案偏离了主题,反向生成的问题将与原问题产生显著偏差。
RAGAS 实现:
import { evaluate, ANSWER_RELEVANCY } from "./ragas";
const result = await evaluate({
dataset,
metrics: [ANSWER_RELEVANCY],
});
console.log(`Answer Relevancy: ${result.scores.answer_relevancy.mean.toFixed(3)}`);
以下案例展示了高相关性与低相关性的典型区别。
// 高相关性
const highRelevancy = {
question: "如何配置数据库连接池?",
answer: "配置连接池需要设置最大连接数、最小空闲连接和超时时间。",
};
// Answer Relevancy ≈ 1.0(答案直接回答问题)
// 低相关性
const lowRelevancy = {
question: "如何配置数据库连接池?",
answer: "数据库是一种存储数据的系统,支持 SQL 查询。",
};
// Answer Relevancy ≈ 0.2(答案虽然陈述正确,但没有回答问题)
在实际工程中,Answer Relevancy 偏低通常提示两种可能的问题:一是 Prompt 中缺乏对回答范围的约束,导致模型自由发挥过度;二是检索上下文本身包含了大量与查询无关的噪声信息,干扰了生成方向。
3. Context Precision(上下文精确率)
Context Precision 从检索结果的角度出发,衡量被检索返回的文档中有多少是真正与查询相关的。该指标回答的问题是:"给模型看的材料里,有多少是有用的?"高分意味着检索结果精炼、噪声少,模型可以在干净的上下文中高效生成答案;低分则意味着 Prompt 中混入了大量无关文档,不仅浪费 Token 预算,还可能误导模型生成不准确的内容。
RAGAS 实现:
import { evaluate, CONTEXT_PRECISION } from "./ragas";
const result = await evaluate({
dataset,
metrics: [CONTEXT_PRECISION],
});
console.log(`Context Precision: ${result.scores.context_precision.mean.toFixed(3)}`);
指标解读:
Context Precision 的取值范围同样为 [0, 1]。得分为 1.0 表示所有检索返回的文档都与查询相关,检索精度达到完美;得分为 0.5 表示检索结果中一半相关、一半噪声;得分为 0.0 则意味着所有返回的文档均与查询无关,检索环节完全失效。
Context Precision 与检索质量指标中的 Hit Rate 和 NDCG 存在互补关系:后者关注的是"相关文档是否被召回以及排在什么位置",而前者关注的是"召回的文档中有多少是真正有用的"。在实际评估中,建议将 Context Precision 与 NDCG 结合分析。当 NDCG 高但 Context Precision 低时,说明检索系统虽然能把相关文档排到前面,但同时也引入了较多噪声文档,需要在检索后处理或重排序环节进行优化。
4. Context Recall(上下文召回率)
Context Recall 衡量的是在人工标注的相关文档集合中,有多少最终被检索系统成功召回。该指标回答的问题是:"所有应该被找到的文档,系统找到了多少?"它与检索质量指标中的 Hit Rate 存在关联,但评估粒度更细:Hit Rate 只关心 Top-K 中是否至少有一个相关文档,而 Context Recall 关注的是相关文档的整体召回比例,能够更敏锐地反映检索系统对长尾文档的覆盖能力。
RAGAS 实现:
import { evaluate, CONTEXT_RECALL } from "./ragas";
const result = await evaluate({
dataset,
metrics: [CONTEXT_RECALL],
});
console.log(`contextRecall: ${result.scores.context_recall.mean.toFixed(3)}`);
指标解读:
Context Recall 得分为 1.0 表示所有人工标注的相关文档均被成功召回,检索覆盖能力达到理想状态;得分为 0.5 表示仅召回了一半的相关文档,大量有用信息被遗漏;得分为 0.0 则意味着检索系统未能召回任何相关文档。
Context Recall 偏低时,常见的优化方向包括:调整分块策略(chunk_size)以平衡信息完整性与语义密度、引入混合检索(关键词检索 + 向量检索)以覆盖更多匹配模式、以及优化向量模型的微调数据以提升语义匹配的覆盖率。
5. 生成质量指标总览
| 指标 | 评估对象 | 评估方式 | 核心关注点 |
|---|---|---|---|
| Faithfulness(忠实度) | 答案 | LLM-as-Judge | 答案事实是否有检索依据,检测幻觉 |
| Answer Relevancy(答案相关性) | 答案 | LLM-as-Judge + 余弦相似度 | 答案是否切题,是否回答用户意图 |
| Context Precision(上下文精确率) | 检索上下文 | LLM-as-Judge | 检索结果中有多少是真正有用的 |
| Context Recall(上下文召回率) | 检索上下文 | LLM-as-Judge | 相关文档被检索召回的比例 |
这四个指标共同构成了生成质量评估的完整视角。在实际工程中,建议将 Faithfulness 与 Answer Relevancy 作为答案质量的核心监控指标,将 Context Precision 与 Context Recall 作为上下文质量的辅助诊断指标,四者结合使用,才能全面把握生成环节的健康状况。
四、构建评估数据集
1. 评估数据集的核心作用与结构
评估数据集(又称"黄金标准"或"测试集")是 RAG 系统量化评估的基石。前文讨论的所有指标——检索质量的 Hit Rate、NDCG,以及生成质量的 Faithfulness、Answer Relevancy、Context Precision、Context Recall,都依赖于一组经过人工确认的"问题-答案-上下文"三元组作为参照基准。没有评估数据集,就无法计算任何指标,评估也就无从谈起。
评估数据集的每个样本代表理想状态下的正确答案,通过将系统的实际输出与评估数据集进行对比,才能量化系统的真实表现。
评估数据集的标准结构:
interface EvalSample {
question: string; // 测试问题
groundTruth: string; // 标准答案(人工确认)
expectedContexts: string[]; // 预期应该检索到的文档片段
relevantDocIds: string[]; // 相关文档 ID(用于计算检索指标)
}
其中 groundTruth 是人工确认的标准答案,用于与系统生成的答案进行语义对比;expectedContexts 是人工标注的相关文档片段,用于计算 Context Precision 和 Context Recall;relevantDocIds 是相关文档的唯一标识,用于计算 Hit Rate 和 NDCG。
2. 数据集构建流程
graph LR
A[从知识库选取文档片段] --> B[针对片段生成问题]
B --> C[人工确认问题和答案]
C --> D[标注相关文档 ID]
D --> E[审核数据质量]
E --> F[形成评估数据集]
(1)从知识库中选取文档片段
// 随机选取 50 个文档片段,确保覆盖不同主题
const selectedDocs = shuffle(allChunks).slice(0, 50);
选取时需要注意覆盖多样性:不同主题、不同难度、不同类型的文档都应有所涉及,避免评估集偏向某一特定领域。如果知识库存在明显的主题分布不均(如技术文档占 80%、FAQ 占 20%),则应按比例分层抽样,确保评估集能够真实反映知识库的整体结构。
(2)针对每个片段生成 3-5 个可回答的问题
这一阶段可以用 LLM 辅助生成,大幅提高效率。
async function generateQuestions(doc: Document, llm: ChatOpenAI): Promise<string[]> {
const prompt = `基于以下文档内容,生成 3 个可以被该文档回答的问题。
文档内容:
${doc.pageContent}
要求:
1. 问题要具体,不要过于宽泛
2. 问题的答案应该能在文档中找到
3. 每个问题一行
请生成问题:`;
const response = await llm.invoke(prompt);
const questions = (response.content as string)
.split('\n')
.filter(q => q.trim().length > 0)
.slice(0, 3);
return questions;
}
(3)人工审核问题-答案对的准确性
这一步不可省略。LLM 生成的问题可能存在歧义、表述不清、答案不准确或遗漏关键信息等问题。人工审核是保证评估数据集质量的关键环节,建议由熟悉知识库内容的领域专家完成。
(4)标注相关文档 ID
对于每个问题,人工标注哪些文档是相关的。相关文档不仅包括直接包含答案的文档,还应包括提供背景信息或辅助理解的文档。这一步的标注质量直接影响 Context Precision 和 Context Recall 的计算准确性。
3. 数据集规模建议
| 阶段 | 样本数 | 用途 |
|---|---|---|
| 开发初期 | 20-50 | 快速验证,发现明显问题 |
| 日常评估 | 100-200 | 稳定监控,A/B 测试 |
| 正式发布 | 500+ | 全面评估,对外报告 |
一般需要遵循以下几个核心的原则:
- 质量 > 数量:100 条精心标注的数据比 500 条粗糙的数据更有价值
- 覆盖多样性:问题应覆盖知识库的不同主题和难度,避免评估集偏向某一特定领域
- 定期更新:随着知识库更新,评估集也要同步更新,否则评估结果会失真。
4. 数据集维护与版本管理
评估数据集不是一次性的产物,需要持续维护。每次知识库重大更新时,应添加新的测试问题;当文档被删除或内容变更时,应淘汰过时的样本;当检索策略发生变化时,可能需要重新标注相关文档 ID。
建议使用 Git 管理评估数据集的版本,便于追踪变更和回滚:
eval-dataset/
├── v1.0/ # 初始版本
│ ├── samples.json
│ └── metadata.json
├── v1.1/ # 新增 20 个样本
│ ├── samples.json
│ └── metadata.json
└── v2.0/ # 重大更新,重新标注
├── samples.json
└── metadata.json
每个版本应附带 metadata.json 记录变更说明、样本数量、覆盖主题分布等元信息,确保评估结果的可复现性。
五、搭建 RAG 评估流水线
前面几小节分别讨论了检索质量指标(Hit Rate、MRR、NDCG)、生成质量指标(Faithfulness、Answer Relevancy、Context Precision、Context Recall)以及评估数据集的构建方法。本节的目标是将这些知识整合为一条完整的评估流水线:从数据准备、系统调用、指标计算到结果诊断与自动化执行,形成可重复、可追溯的评估闭环。
1. 评估流水线的整体架构
一条完整的 RAG 评估流水线应当包含四个阶段:数据准备、系统调用、指标计算和结果分析。
graph LR
A["数据准备<br/>(评估数据集)"] --> B["系统调用<br/>(检索+生成)"]
B --> C["指标计算<br/>(RAGAS)"]
C --> D["结果分析<br/>(诊断+迭代)"]
数据准备阶段使用第四节构建的评估数据集;系统调用阶段将评估集中的问题输入到待评估的 RAG 系统中,获取检索结果和生成答案;指标计算阶段使用 RAGAS 对检索结果和生成答案进行量化评分;结果分析阶段根据指标表现定位问题环节并指导优化方向。
2. 评估脚本示例
以下是一个基于 TypeScript + RAGAS 的完整评估脚本示例,展示了如何将评估数据集、RAG 系统调用和指标计算串联起来。注意 RAGAS 是 Python 库,需要通过 pip 安装。
(1)Python 评估脚本
pip install ragas datasets
# ragas.py
import sys
import json
from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
faithfulness,
answer_relevancy,
context_precision,
context_recall,
)
METRIC_MAP = {
"faithfulness": faithfulness,
"answer_relevancy": answer_relevancy,
"context_precision": context_precision,
"context_recall": context_recall,
}
if __name__ == "__main__":
metric_names = sys.argv[1:]
metrics = [METRIC_MAP[name] for name in metric_names if name in METRIC_MAP]
data = json.loads(sys.stdin.read())
dataset = Dataset.from_dict({
"question": [d["question"] for d in data],
"answer": [d["answer"] for d in data],
"contexts": [d["contexts"] for d in data],
"ground_truth": [d.get("ground_truth", "") for d in data],
})
result = evaluate(dataset=dataset, metrics=metrics)
scores = {}
for name in metric_names:
scores[name] = {
"mean": float(result[name]),
}
print(json.dumps({"scores": scores}))
(2)TypeScript 子进程调用封装
// ragas.ts
import { spawn } from "child_process";
export type MetricType =
| "faithfulness"
| "answer_relevancy"
| "context_precision"
| "context_recall";
export interface EvalSample {
question: string;
answer: string;
contexts: string[];
ground_truth?: string;
}
export interface MetricScore {
mean: number;
std?: number;
}
export interface EvalResult {
scores: Record<MetricType, MetricScore>;
}
export interface EvaluateOptions {
dataset: EvalSample[];
metrics: MetricType[];
}
export function evaluate(options: EvaluateOptions): Promise<EvalResult> {
const { dataset, metrics } = options;
return new Promise((resolve, reject) => {
const pythonProcess = spawn("python3", [
"ragas.py",
...metrics,
]);
let stdout = "";
let stderr = "";
pythonProcess.stdout.on("data", (data: Buffer) => {
stdout += data.toString();
});
pythonProcess.stderr.on("data", (data: Buffer) => {
stderr += data.toString();
});
pythonProcess.on("close", (code: number) => {
if (code !== 0) {
reject(new Error(`Python 进程退出码 ${code}: ${stderr}`));
return;
}
try {
const result: EvalResult = JSON.parse(stdout.trim());
resolve(result);
} catch (e) {
reject(new Error(`解析 Python 输出失败: ${stdout}`));
}
});
pythonProcess.stdin.write(JSON.stringify(dataset));
pythonProcess.stdin.end();
});
}
// 导出指标常量(大写命名)
export const FAITHFULNESS: MetricType = "faithfulness";
export const ANSWER_RELEVANCY: MetricType = "answer_relevancy";
export const CONTEXT_PRECISION: MetricType = "context_precision";
export const CONTEXT_RECALL: MetricType = "context_recall";
(3)完整流程
// evaluate.ts
import { readFileSync } from "fs";
import { evaluate, FAITHFULNESS, ANSWER_RELEVANCY, CONTEXT_PRECISION, CONTEXT_RECALL } from "./ragas";
// 1. 从 JSON 文件加载评估数据集
const evalDataset = JSON.parse(
readFileSync("eval_dataset.json", "utf-8")
) as Array<{ question: string; ground_truth: string }>;
// 2. 调用待评估的 RAG 系统(检索 + 生成)
// 这里替换为真实的 RAG 系统调用
async function callRagSystem(question: string) {
// TODO: 替换为真实的检索器 + 生成器调用
return {
answer: "模拟答案",
contexts: ["模拟上下文"],
};
}
const samples = [];
for (const item of evalDataset) {
const ragResult = await callRagSystem(item.question);
samples.push({
question: item.question,
answer: ragResult.answer,
contexts: ragResult.contexts,
ground_truth: item.ground_truth,
});
}
// 3. 执行完整评估流程
const result = await evaluate({
dataset: samples,
metrics: [FAITHFULNESS, ANSWER_RELEVANCY, CONTEXT_PRECISION, CONTEXT_RECALL],
});
// 4. 输出结果
console.log("\n=== RAG 评估结果 ===");
console.log(`Faithfulness: ${result.scores.faithfulness.mean.toFixed(3)}`);
console.log(`Answer Relevancy: ${result.scores.answer_relevancy.mean.toFixed(3)}`);
console.log(`Context Precision: ${result.scores.context_precision.mean.toFixed(3)}`);
console.log(`Context Recall: ${result.scores.context_recall.mean.toFixed(3)}`);
3. 结果诊断与迭代优化
评估跑通之后,关键是如何解读指标结果并指导优化方向。不同指标的组合表现对应不同的问题定位。
当 Faithfulness 偏低时,说明模型在答案中引入了检索上下文未支撑的信息,即产生了幻觉。此时应当收紧 Prompt 中的约束指令,确保模型只能基于检索上下文作答;同时检查检索上下文的质量,必要时更换更强的生成模型。
当 Answer Relevancy 偏低时,说明答案没有切中用户问题的核心。需要检查 Prompt 是否对回答范围做了充分约束,同时排查检索上下文是否混入了过多噪声信息干扰了生成方向。
当 Context Precision 偏低时,说明检索返回的文档中噪声较多,无关文档占据了 Prompt 的 Token 预算。此时可以考虑引入重排序(Rerank)模型对检索结果进行二次排序,或者优化检索策略,例如引入关键词检索与向量检索相结合的混合检索方案。
当 Context Recall 偏低时,说明部分相关文档未能被检索到,系统遗漏了应该返回的信息。需要检查分块策略是否合理——分块过大可能稀释语义密度,分块过小可能割裂上下文;同时检查 Embedding 模型是否适配当前领域,以及是否需要考虑引入关键词检索作为向量检索的补充以覆盖更多匹配模式。
4. 集成到 CI/CD 实现自动化评估
评估流水线搭建完成后,下一步是将其集成到 CI/CD 流程中,实现每次代码变更自动触发评估。这样可以确保检索策略、Prompt 模板或切分参数的任何改动都不会导致系统质量退化。
以 GitHub Actions 为例,评估流水线的 CI/CD 配置如下:
# .github/workflows/rag-evaluation.yml
name: RAG Evaluation
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install Node dependencies
run: npm ci
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"
- name: Install Python dependencies
run: pip install -r requirements.txt
- name: Run RAGAS evaluation
run: npx tsx evaluate.ts
- name: Check metrics thresholds
run: npx tsx check_metrics.ts --threshold-file thresholds.json
其中 check_metrics.ts 是一个自定义的指标检查脚本,读取预设的阈值配置文件,对各项指标进行门槛校验:
{
"faithfulness": 0.80,
"answer_relevancy": 0.75,
"context_precision": 0.70,
"context_recall": 0.70
}
当任意指标低于设定阈值时,CI 任务自动失败并阻断合并。阈值的设定应当基于历史基线数据而非凭空设定——建议在生产环境稳定运行一段时间后,记录各项指标的平均值和波动范围,将阈值设为基线值的一定衰减比例(如基线值的 90%),这样既能捕获显著退化,又不会因正常波动产生误报。
脚本示例如下:
// check_metrics.ts
import { readFileSync } from "fs";
import { EvalResult } from "./ragas";
export interface ThresholdConfig {
faithfulness?: number;
answer_relevancy?: number;
context_precision?: number;
context_recall?: number;
}
/**
* 读取阈值配置文件
*/
export function loadThresholds(filePath: string): ThresholdConfig {
try {
const content = readFileSync(filePath, "utf-8");
return JSON.parse(content);
} catch (e) {
throw new Error(`读取阈值配置文件失败: ${filePath}`);
}
}
/**
* 校验评估结果是否满足阈值要求
* @returns 返回所有未达标的指标列表,空数组表示全部通过
*/
export function checkThresholds(
result: EvalResult,
thresholds: ThresholdConfig
): string[] {
const violations: string[] = [];
const checks: Array<{
metric: keyof ThresholdConfig;
score: number | undefined;
}> = [
{ metric: "faithfulness", score: result.scores.faithfulness?.mean },
{ metric: "answer_relevancy", score: result.scores.answer_relevancy?.mean },
{ metric: "context_precision", score: result.scores.context_precision?.mean },
{ metric: "context_recall", score: result.scores.context_recall?.mean },
];
for (const { metric, score } of checks) {
const threshold = thresholds[metric];
if (threshold === undefined) continue; // 未配置阈值的指标跳过
if (score === undefined) {
violations.push(`${metric}: 分数缺失`);
continue;
}
if (score < threshold) {
violations.push(
`${metric}: ${score.toFixed(3)} < ${threshold.toFixed(3)}`
);
}
}
return violations;
}
// CLI 入口
if (import.meta.url === `file://${process.argv[1]}`) {
const thresholdFile = process.argv.find((arg) =>
arg.startsWith("--threshold-file=")
)?.split("=")[1];
if (!thresholdFile) {
console.error("用法: tsx check_metrics.ts --threshold-file=thresholds.json");
process.exit(1);
}
// 从 stdin 读取评估结果(由 evaluate.ts 管道传入)
let stdinData = "";
process.stdin.setEncoding("utf-8");
process.stdin.on("data", (chunk) => {
stdinData += chunk;
});
process.stdin.on("end", () => {
try {
const result: EvalResult = JSON.parse(stdinData.trim());
const thresholds = loadThresholds(thresholdFile);
const violations = checkThresholds(result, thresholds);
if (violations.length === 0) {
console.log(" 所有指标均通过阈值检查");
process.exit(0);
} else {
console.error(" 以下指标未通过阈值检查:");
for (const v of violations) {
console.error(` - ${v}`);
}
process.exit(1);
}
} catch (e) {
console.error(`解析评估结果失败: ${e}`);
process.exit(1);
}
});
}
5. 评估报告的生成与追踪
除了自动化门禁检查,评估流水线还应当能够定期生成评估报告,追踪指标随时间的变化趋势。可以将每次评估的结果写入数据库或文件存储,配合可视化图表观察指标走势。当某项指标出现持续下降趋势时,即使尚未跌破阈值,也应当引起关注并排查原因。
一个简单的做法是将每次评估结果追加写入 JSONL 文件:
import { appendFileSync } from "fs";
function saveReport(result: EvalResult) {
const record = {
timestamp: new Date().toISOString(),
gitCommit: process.env.GITHUB_SHA || "local",
scores: result.scores,
};
appendFileSync("eval_reports.jsonl", JSON.stringify(record) + "\n");
}
配合简单的可视化脚本(如 Python + Matplotlib 或前端图表库),即可生成指标趋势图,直观地看到每次改动对 RAG 系统质量的影响。
六、 指标选型指南
不同指标的自动化程度和标注成本差异较大,实际落地时可以根据团队资源做取舍。
| 指标 | 衡量什么 | 自动化程度 | 需要的标注数据 | 典型阈值 |
|---|---|---|---|---|
| Hit Rate @5 | 相关文档是否被召回 | 全自动 | 每条查询标记相关文档 ID | > 0.7 |
| MRR | 相关文档的排名 | 全自动 | 同 Hit Rate | > 0.5 |
| Faithfulness | 答案是否有检索支撑 | LLM 自动 | 无需人工标注 | > 0.8 |
| Answer Relevancy | 答案是否切题 | LLM 自动 | 无需人工标注 | > 0.7 |
| Context Precision | 检索结果中有多少相关 | LLM 自动 | 无需人工标注 | > 0.6 |
| Context Recall | 相关文档有多少被召回 | 需要标注 | 标注相关文档 ID | > 0.7 |
可以参考以下建议进行选型:
- CI 门禁优先:Faithfulness 和 MRR 是最核心的两个指标,分别覆盖生成质量和检索质量,建议在 CI 流程中集成自动评估,每次修改检索策略、Prompt 模板或切分参数后自动跑一遍,出现显著下降则阻断发布。
- 人工标注按需:Context Recall 需要人工标注相关文档 ID,成本较高,建议在系统上线前做一次性的全面评估,后续用 Faithfulness 和 Answer Relevancy 做持续监控即可。
- 阈值动态调整:表格中的阈值是典型参考值,实际应当基于历史基线数据设定。建议在生产环境稳定运行一段时间后,将阈值设为基线值的 90%,这样既能捕获显著退化,又不会因正常波动产生误报。
FAQ
Q1:需要多少测试数据才够?
50-100 条精心标注的问答对是起点。少于 50 条统计意义不足,超过 500 条边际收益递减但标注成本线性增长。关键是问题要覆盖知识库的不同主题和难度。
Q2:LLM 自动评估 Faithfulness 靠谱吗?
在 RAGAS 的研究和社区实践中,LLM 自动评估的 Faithfulness 分数与人工评分的一致性约为 0.8-0.85(Spearman 相关系数)。对于日常开发迭代足够,但关键决策仍需要人工抽检。
提高可靠性的方法可以是使用更强的 LLM(如 GPT-4o 而非 GPT-3.5)、多次评估取平均、 结合多个指标综合判断
Q3:RAG 评估需要多长时间?
RAGAS 对 100 条测试数据的完整评估(含 Faithfulness 和 Answer Relevancy)通常需要 3-5 分钟,瓶颈是 LLM API 调用的速率限制。
优化方法可以是并行调用 LLM API、缓存评估结果、只对变更的部分重新评估。
Q4:如何设定指标的合格阈值?
没有统一标准,需要根据业务需求确定。以下是一些经验值:
- 客服系统:Faithfulness > 0.85, Answer Relevancy > 0.8
- 技术文档:Faithfulness > 0.9, Context Precision > 0.7
- 法律/医疗:Faithfulness > 0.95, 人工审核所有答案
Q5:检索质量和生成质量哪个更重要?
取决于应用场景,比如:
- 查找型应用(如技术文档搜索):检索质量更重要
- 对话型应用(如客服机器人):生成质量更重要
- 关键任务(如法律建议):两者都必须高
通常建议先优化检索质量(因为它是基础),再优化生成质量。
练习
练习 1:为你的 RAG 系统建立评估集
从你的知识库中选取 10 个文档片段,为每个片段生成 2-3 个问题并人工确认标准答案。标注每个问题应检索到的文档 ID。
验证标准:
- 至少 20 条有效问答对
- 覆盖不同主题(如部署、API、故障排查等)
- 问题和答案都经过人工审核
练习 2:运行 RAGAS 评估
将评估集导入 RAGAS,跑一次完整评估。记录 Faithfulness 和 Answer Relevancy 的基线分数。
验证标准:
- 输出完整的 RAGAS 评估报告
- 明确当前的基线指标值
- 分析哪些指标最需要改进
练习 3:对比不同检索策略的评估结果
对你《第9章:高级检索策略》中实现的混合检索器,对比启用和禁用混合检索时的评估指标差异。
验证标准:
- Hit Rate @5 提升至少 10%
- MRR 提升至少 0.1
- 记录评估结果的变化
练习 4:建立自动化评估脚本
编写一个脚本,自动运行 RAG 系统并对评估集进行测试,输出各项指标。
验证标准:
- 一键运行,自动输出评估报告
- 能够保存历史评估结果,便于对比
- 能够检测指标异常(如下降超过 10%)
📚 延伸阅读
- RAGAS Documentation — RAGAS 评估框架完整文档,包含详细的 API 说明和使用示例
- RAGAS 论文 — "RAGAS: Automated Evaluation of Retrieval Augmented Generation",想了解算法细节的读者可以阅读
- MTEB Leaderboard — 大规模文本嵌入基准排行榜,可以查看各种 Embedding 和检索模型的表现
- BEIR Benchmark — 信息检索基准测试数据集,用于评估检索模型的性能
- LangSmith Evaluation — LangChain 的评估平台,提供可视化的评估结果和分析工具