Python RAG 实战手册——评估 RAG 系统

34 阅读35分钟

你无法改进无法衡量的东西。当你调优 retrieval parameters、调整 prompts,或者切换模型时,你需要 metrics 来告诉你这些变化是有帮助还是有损害。没有 evaluation,optimization 就会变成猜测。

RAG evaluation 需要不同于传统机器学习(ML)的评估方法。在传统 ML 中,你训练模型从带标签数据中学习模式,然后测试它们能否泛化到未见样本。RAG 系统并不学习——它们检索并生成。Foundation models 已经能够跨任务泛化。问题不在于模型是否学到了模式,而在于它是否检索到了正确的信息,并生成了有用答案。

这种差异会影响你如何评估。关键挑战在于,测试问题应该覆盖真实用户意图,但不能逐字出现在系统数据中;同时,这些问题又必须能从可用知识中回答。RAG 中良好的泛化意味着能够处理同一个底层问题的不同表述。Intent 比 exact wording 更重要。这会影响 test design——你需要改写真实 queries,而不是拆分已有 labeled examples。

图 10-1 展示了这个问题:如果向一个只知道 football,也就是 soccer,规则的系统提出哲学问题,评估结果就没有意义。

image.png

图 10-1:RAG 系统只应在能够从其知识库中回答的问题上进行评估

图 10-2 展示了提升 RAG performance 的两个维度:RAG pipeline 本身,包括 retrieval、chunking、reranking,以及 generation model,包括通过 fine-tuning 或 prompt engineering 优化。由于可能调整项很多,你需要 evaluation metrics 来判断哪些变化真正提升了 accuracy 和 relevance。

image.png

图 10-2:优化 RAG 系统的方法

评估 generation 很有挑战,因为语言具有主观性。最可扩展的方法是使用 LLMs 来评估其他 LLM outputs,类似作者审阅自己的作品。如果你理解其限制,并仔细设计 evaluations,这种方法是可行的。

对于真实世界 RAG 系统,架构通常会超越简单的 retriever-generator pipeline。你会有不同类型的数据源和专门的 retrieval components。每增加一个模块,复杂性都会提高,并需要定制化 evaluation strategy。

用一个更具体的真实世界示例来说明:设想你是一个在线零售商,正在构建一个 chatbot,根据时尚博客文章提供穿搭建议。随着时间推移,你计划将这个 chatbot 演进成一个完整 shopping assistant,它可以访问 product catalog、item availability、order placement 和 order tracking。

你的 RAG 系统现在必须处理两个非常不同的数据源:

  • 支撑线上商店的 SQL database,存储 users 和 products 等结构化应用数据
  • 包含从时尚博客中提取出的 text snippets 的 vector database

一种可能架构如图 10-3 所示。Agent 或 orchestration layer 会决定查询哪个数据源。数据库相关问题会被路由到 text-to-SQL component,而语义问题则由连接到 vector database 的 retriever 处理。最终 generation step 会将 SQL results 和 retrieved context 组合起来,生成连贯答案。

现在你的 RAG 系统由多个组件组成:

  • 一个 orchestration agent,用于决定查询哪个数据源
  • 一个 text-to-SQL translation step
  • 一个基于 vector search 的 semantic retriever
  • 一个将 retrieved context 转换为最终答案的 generation module

每个组件都可能独立失败,因此你需要为每个部分设置 evaluation metrics,以识别瓶颈并提升整体性能。

image.png

图 10-3:一个稍复杂的 RAG 系统,包含两条可能的 retrieval paths

幸运的是,你不需要从零开始。Retriever step 以 vector store 实现,在概念上类似传统搜索引擎和分类系统。因此,你可以复用 precision 和 recall 等成熟 metrics。本章会同时覆盖从搜索引擎中借鉴并适配到 retrieval 的 metrics,以及专门用于评估 LLM outputs 的 generation metrics。

下面的 recipes 会引导你完成 RAG evaluation,从选择 metrics 到实现它们。每个 recipe 都包含可适配到你系统中的可运行代码。第一个 recipe 会帮助你根据系统架构和可用测试数据选择合适 metrics。然后你会学习如何生成 synthetic test data,实现三个无需 reference answers 也能工作的核心 metrics,最后再引入 human evaluation,以评估主观质量维度。

你可以在本书 GitHub repository 中找到本章所有代码示例。

10.1 为 RAG 系统选择合适的 Evaluation Metrics

Problem

你需要选择 evaluation metrics,准确衡量 RAG 系统 retrieval 和 generation step 的性能。

Solution

评估 RAG 系统需要让 metrics 与你拥有的 ground truth data 相匹配。这个 recipe 会带你通过一个系统化方法,根据数据约束选择 evaluation metrics,然后聚焦于三个在没有 reference answers 时也能使用的核心 metrics。

将 Metrics 与 Ground Truth 匹配

首先识别你有哪些 labeled data。答案决定了你可以可靠衡量系统的哪些部分:

如果你有 reference questions 与 correct answers 的配对,就可以使用 answer-based metrics 进行端到端评估,例如 semantic similarity 或 LLM-based correctness scoring。这些 metrics 直接衡量系统是否生成正确结果。

如果你有 labeled retrieval data,也就是 questions 与应该被检索出的 document chunks 配对,就可以使用 context precision 和 recall 等 metrics 评估 retrieval quality。这些 metrics 衡量 retriever 是否找到了正确的信息。

如果你既没有 reference answers,也没有 labeled retrieval data,就可以使用 faithfulness、answer relevancy 和 coherence 等 intrinsic metrics 评估 generation quality。这些 metrics 会检查模型是否基于 context 作答,并生成有用 responses。

无论你的 ground truth 情况如何,都应选择一组小而均衡的 metrics,通常是两到三个,用来覆盖 pipeline 的不同部分。大多数情况下,这意味着至少一个 retrieval metric 和一个 generation metric。这样可以防止你优化一个组件时,悄悄损害另一个组件。

使用常见 Metric Categories

图 10-4 展示了 RAG evaluation metrics 的完整版图,并将其组织成三组:

Generation metrics
评估生成答案的性质,例如 faithfulness、correctness、coherence、fluency 和 relevance。这个类别还包括 safety metrics,用于覆盖 harmful content 或 protected material。

Retrieval metrics
使用 recall、precision 和 F1 score 等指标评估 retrieval quality。

General metrics
捕捉系统行为,例如 latency 和 token usage。

image.png

图 10-4:RAG evaluation metrics,分为 generation metrics、retrieval metrics 和 general system metrics

此外,还应考虑针对 specialized retrieval components 定制的 metrics。一个例子是 text to SQL,在这里经典 vector similarity metrics 并不适用。作为起点,Manas Singh 的一个 YouTube 视频提供了使用 LLMs as judges 评估 SQL generation 的实践洞察。

聚焦三个核心 Metrics

本章聚焦于一个常见场景:你缺少 reference answers,但需要自动化 evaluation。该方法使用 LLM-as-judge,也就是一个语言模型评估另一个语言模型的输出,来评估三个 metrics。这三个 metrics 覆盖标准 RAG 系统中最关键的 failure modes。它们假设 retriever 是一个返回相似 text chunks 的 vector store。

在这种情况下,三个 metrics 如下:

Context precision
衡量 retrieved chunks 中真正与 query 相关的比例。这个 metric 针对 retrieval step,用于检测 search results 中的噪声,见 Recipe 10.4。

Faithfulness
衡量生成答案是否严格基于 provided context,避免 hallucinations 和 unsupported claims。这个 metric 针对 generation step,用于捕捉模型编造信息的情况,见 Recipe 10.5。

Answer relevancy
衡量生成答案是否直接回答用户问题。这个 metric 针对 generation step,用于识别 misdirection,也就是答案事实正确但对用户没有帮助的情况,见 Recipe 10.6。

图 10-5 展示了不同 metrics 在 RAG pipeline 中覆盖的内容。

image.png

图 10-5:应用于 RAG pipeline 的 evaluation metrics

你可以在后续 recipes 中找到这三个 metrics 的代码实现。

Discussion

三指标方法有效,是因为每个 metric 针对一个独立 failure mode。一个系统可能检索到了精确 chunks,但仍然 hallucinate;也可能 faithfulness 很高但 answer relevancy 很低;还可能回答了问题,却基于无关 context。通过同时衡量这三个维度,你可以捕捉单一 metric 会遗漏的问题。

LLM-as-judge 很适合这些 metrics,因为语言模型可以比较 semantic similarity,并验证逻辑一致性,而不需要领域知识。Judge 不需要知道正确答案——它只判断 claims 是否被 context 支持,或者 answers 是否回应了 questions。这使得当你缺少 reference answers,但仍然需要大规模 automated evaluation 时,该方法很实用。

将这组 metrics 用于系统变更后的 regression testing 和持续 production monitoring。

请记住,LLM-as-a-judge 并不能捕捉用户关心的所有内容。它难以评估 tone 或 brand voice 等主观质量,这些需要 human evaluation。它可能遗漏让用户沮丧的问题:答案过于啰嗦、措辞令人困惑,或缺少 clarifying questions。使用 synthetic test data 时,这些限制会更严重,因为 synthetic test data 往往会产生比真实用户更容易的问题。

TIP

将 automated metrics 与 human judgment 平衡使用。使用 LLM-as-judge 做 continuous monitoring 和 high-frequency testing,但用周期性 human review 作为补充。Human evaluation 有助于校准 metrics,捕捉 automated approach 遗漏的问题,并确保系统在用户真正重视的维度上改善。

See Also

Ragas documentation 提供了一个完整 evaluation framework,包含 RAG 系统的详细 metric definitions 和代码示例。

Hugging Face RAG evaluation cookbook 提供了使用 synthetic datasets 评估 RAG 系统的端到端教程。

10.2 由人类评估 RAG 系统

Problem

你想收集显式用户反馈,例如 ratings、comparisons 或 comments,以基于真实用户判断评估 RAG 系统。

Solution

为了收集 RAG 系统的用户反馈,你可以根据部署场景和用户规模,实现几种实践方法。

如果你有足够大的测试用户群,可以向用户提供旧版和新版 RAG 系统,让两者回答同一组问题,并要求用户对结果排序。这种 pairwise comparison 方法甚至可以用于生产环境。你可以不时向用户展示两个 alternative answers,并询问他们更偏好哪一个。

在大多数真实场景中,用户只会看到一个 question-answer pair。即便如此,你也可以通过 thumbs-up 或 thumbs-down 等简单信号收集有价值的显式反馈。为了理解差响应的根本原因,通常还需要更详细输入。

图 10-6 展示了一个 feedback form 示例。当用户点击 thumbs down 时,会出现一个 dialog,其中包含一个短 free-text question、可选 quick labels,以及 Submit button。

image.png

图 10-6:RAG 系统的在线监控

TIP

第 11 章中,你会看到如何使用 Streamlit 构建 chatbot 的示例。Streamlit 社区为 chat applications 提供了 ready-to-use modules,包括内置 user feedback components。关于支持的 feedback options,请参考 Streamlit st.feedback documentation。

你可以收集 passive monitoring signals:

  • Timestamps 揭示用户最频繁与应用交互的时间。
  • User identifiers 有助于分析哪些用户群体与系统交互。
  • Response times 使你可以识别 pipeline 中的瓶颈并提升性能。
  • Prompt logging 允许你追踪 response 是如何生成的。
  • Retrieved documents 可以被跟踪,用于评估 retrieval accuracy。
  • LLM responses 可以被分析,用于估计 answer relevancy,并判断模型是否正确解释了 provided context。

使用 Python 的 time module 在整个 RAG pipeline 中收集 timestamps,以跟踪 response time。图 10-7 展示了 latency 通常如何分布。

image.png

图 10-7:提升 RAG 系统 response time 的技术

Discussion

Human evaluators 能理解 context、tone 和 intent,这是 automated metrics 无法做到的。一次 thumbs-down 往往指向知识库中缺失或过时的内容,而不是技术失败。Written feedback 特别有价值,因为它会揭示 metrics 无法检测到的 gaps、unclear phrasing 或 unmet expectations。

当你需要衡量 user satisfaction 和 tone、clarity、usefulness 等主观质量时,应使用 human feedback。当测试重大变更,例如切换模型或彻底重构 retrieval 时,这类反馈尤其有价值。Pairwise comparisons 在这些场景中效果很好。并排向用户展示两个 answers 并跟踪 preferences,可以揭示 automated metrics 常常遗漏的 improvements 或 regressions。

Human evaluation 对持续监控来说太慢且昂贵。它也不一致,因为不同人经常会对同一个答案给出不同评分。应把这种方法用于 targeted reviews,而不是每次代码变更。Human feedback 捕捉 tone、completeness 和 perceived usefulness。Automated metrics 提供规模、一致性,并能快速检测 hallucinations 或 irrelevant retrieval 等技术失败。混合方法效果最好。

使用 automated metrics 做 continuous monitoring 和 regression testing,并在重大变更后增加周期性 human reviews。一个实践方法是对每次代码变更或部署运行 automated metrics,但每月或在重大系统更新后进行 human evaluation。每次 review session 抽样 20–30 条 responses,让两到三名团队成员或 beta users 参与,以减少个人偏差。同时跟踪定量 ratings,例如 thumbs-up/down、1–5 分,以及定性反馈,例如哪里错了、什么会更有帮助。

See Also

Chatbot Arena 展示了用于比较 LLM outputs 的 pairwise evaluation workflows,可以适配到 RAG system evaluation。

Microsoft Copilot 提供了轻量用户反馈的设计模式,包括 ratings、explanations 和 contextual logging。

10.3 为自动化测试创建 Synthetic Data

Problem

你需要一组适合测试 RAG solution 的 question-answer pairs。

Solution

最快且最实用的方法,是使用 LLM 自动生成 synthetic question-answer pairs。图 10-8 可视化了该过程。从 vector store 中随机选择 text chunks,然后 prompt 一个 LLM 生成可以从这些 chunks 中回答的问题。你可以使用单个 chunk,也可以组合多个 chunks 作为 context。这可以在不人工标注的情况下,大规模创建 test data。

image.png

图 10-8:创建 synthetic dataset

在这个 recipe 中,你会使用 Hugging Face 上的 RAG mini Wikipedia dataset。代码片段使用从 Wikipedia articles 中提取的 text chunks,使 LLM 有 context 来生成 synthetic questions 和 answers。除了 OpenAI SDK,还会使用 pandas 操作和处理文本数据。按如下方式安装 dependencies:

pip install pandas openai pydantic

首先,从 Hugging Face 加载 RAG mini Wikipedia dataset,以获得一组 text chunks。这个示例中无需将这些 chunks 存入 vector store;snippet 只是模拟这些内容是 vector store 的一部分。换句话说,它代表 RAG 系统 retriever 可用的知识:

# Example usage
import pandas as pd

df = pd.read_parquet(
    "hf://datasets/rag-datasets/rag-mini-wikipedia/data/"
    "passages.parquet/part.0.parquet"
)

docs_list = df.passage.to_list()

如果你查看 Hugging Face 上的数据集,会注意到它有两个部分。第一部分是来自 Wikipedia articles 的 raw text corpus。第二部分是根据这些 Wikipedia snippets 创建的预构建 question-answer pairs。因为这个 recipe 展示如何自动生成这类 question-answer pairs,所以忽略第二部分。

图 10-9 展示了从 Hugging Face 加载的数据结构。你会得到一个包含 3,200 个字符串的列表。每个字符串都是 Wikipedia article 的一个 text snippet。

image.png

图 10-9:从 Hugging Face 加载的数据

下一步是从 document list 中随机选择 text chunks,并指示 LLM 基于这些 chunks 中的信息生成真实感问题和答案。

用于指示 LLM 的 prompt 应包含两个部分:

  • 一个 instruction section,告诉模型要做什么
  • 模型用于生成 questions 和 answers 的 context

在 instruction 部分,告诉模型像普通用户一样思考,并提出真实用户会问的自然 queries。模型还应为每个问题生成答案。因为问题是从 context 派生的,所以 context 应包含答案。下面是 prompt,其中包括所有必需组件和 context placeholder:

from pydantic import BaseModel

QA_generation_prompt = """
    Your task is to generate a question and its corresponding answer
    based on the provided context.

    The question should be:
    - Direct and focused, seeking a specific factual piece of
        information present in the context.
    - Formulated as a natural query a user might input into a
        search engine.
    - Independent of the context, meaning it should not contain
        phrases like "according to the passage" or "in the context".

    The answer should be:
    - A concise and accurate response directly extracted or inferred
        from the context.

    Present your output in the following format:

    Output:
    Question: (Your concise, fact-based question)
    Answer: (The direct answer to the question)

    Context: {context}\n
    Output:"""

接下来,将 response template 定义为 Pydantic model。返回的 Python object 是一个 question-answer pair,两个 fields 都表示为 strings。Snippet 的第二部分会从整个数据集中随机选择五个 text chunks,遍历它们,并以指定 Pydantic 格式生成 question-answer pairs:

from openai import OpenAI
from pydantic import Field
import random

# Initialize the OpenAI client with your API key
client = OpenAI()

class QuestionAnswerPairs(BaseModel):
    question: str = Field(
        description="A factoid question about the context."
    )
    answer: str = Field(
        description="The answer to the factoid question."
    )

question_answer_pairs = []

random_text_chunks = random.sample(docs_list, 5)

for text_chunk in random_text_chunks:
    # Generate hypothetical questions using the GPT-5.2 model
    completion = client.chat.completions.parse(
        messages=[
            {
                "role": "user",
                "content": QA_generation_prompt.format(context=text_chunk),
            }
        ],
        model="gpt-5.2",
        response_format=QuestionAnswerPairs,
    )

    question_answer_pair = (
        completion.choices[0].message.parsed.model_dump()
    )
    question_answer_pair["context"] = text_chunk
    question_answer_pairs.append(question_answer_pair)

图 10-10 展示了输出。它包含针对随机选择 contexts 生成的五个 question-answer pairs。每个 item 是一个 dictionary,包含 question、answer 和使用的 context。

image.png

图 10-10:Synthetic question-answer pairs

这些 questions 现在可以用于评估 retriever、generator 和整体系统性能。因为这些问题是从真实数据派生出来的,RAG 系统应该能够在已有内容中找到答案。如果无法找到,这就是系统存在问题的明确信号。

Discussion

LLM-generated questions 保证可以从知识库中回答,因为它们直接来自已存储 chunks。这为测试系统变更提供了可靠 baseline:如果你的 RAG 系统不能回答 synthetic questions,说明某处坏了。该方法可以扩展,因为你可以自动生成数百个 test cases。

使用 synthetic test generation 做 continuous testing,尤其适合知识库频繁变化的场景。它可以在 code changes 后捕捉明显失败,例如 model updates、prompt modifications 或 parameter adjustments。Synthetic data 衡量的是技术正确性,也就是系统是否检索到相关 chunks 并生成 grounded answers。在早期开发中,当你需要快速获得 basic functionality 的反馈时,它很有价值。

不要把 synthetic data 作为唯一 evaluation。Synthetic questions 系统性地比真实 queries 更容易,因为它们来自干净、聚焦的 chunks。真实用户会用含糊方式提问,会组合多个 intents,会引用 conversation context,或询问 edge cases。Synthetic questions 无法捕捉 query difficulty distribution——它们测试的是理想条件,而不是含糊或表述不佳问题的长尾。

也不要用 synthetic data 评估 clarity、tone 或 helpfulness 等用户体验维度。这些需要 human judgment。不要依赖它评估 multiturn conversations,因为生成的问题是 single-turn,缺少对话动态。应通过周期性分析 production queries 来补充。

至少生成 50–100 个 synthetic question-answer pairs,以覆盖知识库中的多样 topics。这个 recipe 的代码中展示的五个示例足以 prototyping,但不足以可靠 evaluation。应覆盖以下方面:

  • 知识库中的不同 document types,如果你有 articles、FAQs 和 technical specs,就应从每类中抽样
  • 不同 question complexities,例如 factual lookups、comparisons、multihop reasoning
  • 靠近知识边界的 edge cases

人工审查 10–20 个生成 pairs 样本,以确保质量。检查 questions 是否自然,answers 是否准确。如果发现系统性问题,例如措辞过于复杂、答案不正确或问题过于琐碎,就调整 generation prompt 并重新生成。

当你显著更新知识库时,需要重新生成 test data。Synthetic tests 只有在反映当前内容时,才能捕捉问题。如果你添加重要新章节或重构文档,旧测试问题可能不再与 retrieval patterns 对齐。

See Also

Hugging Face RAG evaluation cookbook 展示了如何使用 synthetic datasets 评估 RAG systems,并提供逐步实现示例。

Ragas test set generation guide 提供了从知识库生成 synthetic questions 和 ground truth answers 的技术。

10.4 通过计算 Context Precision@k 评估 Retriever Step

Problem

你想通过将 retrieved text chunks 与用户问题比较,评估它们的相关性。

Solution

要计算 context precision,按照图 10-11 所示步骤:

  1. 从 test dataset 中随机选择一个问题。
  2. 让 RAG system 回答该问题。捕获 generated answer 和使用的 text chunks。
  3. 将每个 retrieved text chunk 与 answer 比较,并让 LLM 判断该 chunk 是否真正有助于生成该答案,或者是否无关。
  4. 通过将 relevant chunks 数量除以 retrieved chunks 总数,计算 context precision。例如,如果 prompt 中包含四个 chunks,但只有两个相关,precision score 就是 0.5。

image.png

图 10-11:逐步计算 context precision@k

下面公式定义了 context precision score。它计算前 k 个 retrieved text chunks 中,真正有助于生成最终答案的比例。

Context precision@k =
Σ(k=1 to k)(precision@k × v_k)
/
Top k results 中相关 items 的总数

其中:

k 表示 retrieved_contexts 中 chunks 的总数。

v_k 是 rank k 处的二元 relevance flag,如果该 chunk 相关则为 1,否则为 0。

为了判断某个 text chunk 是否与回答问题相关,有几种方法。

一种方法使用 LLM 将 retrieved context 与 reference context 比较。这种情况下,你有 golden dataset,指定每个问题期望检索什么 context,以及什么 context 是相关的。

另一种方法使用 LLM 将 retrieved context 与 generated answer 比较。这种情况下,你不需要 golden reference dataset 来指定期望 context。相反,你将 generated answer 与 context 比较,让 LLM 判断 retrieved chunks 中是否有完全无关、没有参与生成答案的内容。这可以指示某些 retrieved chunks 是噪声。

第三种方法不是 LLM-based,可以使用非 LLM 方法比较 context 与 answer,或 context 与 question。传统技术包括 string-similarity calculations 或 keyword-overlap metrics,用于在不使用 LLM 的情况下估计 relevance。

这个 recipe 使用第二种方法——使用 LLM 将 retrieved context 与 generated answer 比较。下面是一个关于 Mona Lisa 的简单示例:

Question
Who painted the Mona Lisa?

Context
The Mona Lisa is a 16th-century oil painting attributed to Italian polymath Leonardo da Vinci.

Answer
The Mona Lisa was painted by Leonardo da Vinci.

在这个例子中,很明显 context 帮助模型生成了合适答案。如果你使用 k 个 retrieved chunks,其中一个 chunk 讲的是完全不同的 painting 或 artist,LLM 很可能不会使用该信息。那个 chunk 会被评为 not helpful,也就是 verdict = 0

这个 recipe 将该概念打包成几个代码片段。为了做出判断,代码需要访问 LLM,这里你会使用 OpenAI 的一个模型和 OpenAI SDK。你还会使用 Pydantic,确保模型 response 只包含以下内容:

  • verdict,表示 context chunk 是否有帮助
  • reason,一个字符串形式的简短解释

使用以下命令安装 required dependencies:

pip install openai pydantic pandas

下面代码使用一个 sample question、answer 和 retrieved context。以下是后续步骤使用的 sample data:

question = "What is the capital of France?"
answer = "The capital of France is Paris."
retrieved_contexts = [
    """France, a country in Western Europe, is known for
    its capital city, Paris, which is a major European city
    and a global center for art, fashion, and culture.""",
    """Paris is the capital city of France and is renowned
    for its historical landmarks such as the Eiffel Tower
    and the Louvre Museum.""",
    """The Amazon rainforest is the world's largest tropical
    rainforest, spanning across nine South American countries
    and supporting immense biodiversity."""
]

下一步评估 context 中每个 text chunk 对回答问题的价值。

下面代码定义一个名为 Verification 的 Pydantic model,它包含:

  • reason,字符串
  • verdict,整数,只能是 1 或 0

verdict 为 1 表示该 text chunk 对 LLM 有帮助;为 0 表示没有帮助。

对于每个 text chunk,代码会遍历 retrieved chunks,发送 prompt,请求 response 以 Verification 格式返回,并将结果保存到 verifications 列表中:

from textwrap import dedent
from pydantic import BaseModel, Field
from openai import OpenAI

def context_relevance_verification(question, answer, retrieved_contexts):
    # Pydantic model for verification
    class Verification(BaseModel):
        reason: str = Field(..., description="Reason for verification")
        verdict: int = Field(
            ..., description="Binary (0/1) verdict of verification"
        )

    # Define the prompt for context relevance verification
    context_relevance_prompt = dedent('''
        Given a question, an answer, and a context, verify if the
        context was instrumental in deriving the given answer. Provide
        a detailed reason for your assessment and a binary verdict: "1"
        if the context is useful and "0" if it is not.

        Input:
        Question: "{question}",
        Answer: "{answer}",
        Context: "{context}"
        ''')

    list_of_verifications = []

    # Iterate through each question-answer pair and its context
    for retrieved_context in retrieved_contexts:
        prompt = context_relevance_prompt.format(
                question=question,
                answer=answer,
                context=retrieved_context)

        client = OpenAI()
        completion = client.chat.completions.parse(
            messages=[
                {
                    "role": "user",
                    "content": prompt,
                }
            ],
            model="gpt-5.2",
            response_format=Verification,
        )

        verification = completion.choices[0].message.parsed.model_dump()
        verification["question"] = question
        verification["answer"] = answer
        verification["retrieved_context"] = retrieved_context

        list_of_verifications.append(verification)
    return list_of_verifications

图 10-12 展示了应用于 sample data 时的输出。在这个 sample 中,用户询问法国首都。Retrieved context 包含三个 text chunks:第一个描述法国并提到其首都,第二个描述巴黎是法国首都,第三个讨论 Amazon rainforest,与问题完全无关。LLM 给前两个 chunks 分配 verdict = 1,给无关的第三个 chunk 分配 verdict = 0

image.png

图 10-12:Context relevance verification 的输出

有了 verdicts 列表后,现在可以计算 context precision,也就是 relevant chunks 相对于 retrieved chunks 总数的比例。定义一个简单函数来计算这个 relevance score:

import pandas as pd

def calculate_context_relevance_score(list_of_verifications):
    verdict_list = pd.DataFrame(list_of_verifications)["verdict"].to_list()

    # Add small epsilon to avoid division by zero if no chunks are relevant
    denominator = sum(verdict_list) + 1e-10
    numerator = sum(
        [
            (sum(verdict_list[: i + 1]) / (i + 1)) * verdict_list[i]
            for i in range(len(verdict_list))
        ]
    )
    score = numerator / denominator  # e.g., score = 0.835 for [1,0,1]
    return score

Discussion

Context precision 评估 retriever 是否浮现出相关信息。它将 retrieval 看作 ranking:给定一个 query,返回有助于回答问题的 chunks。无关 chunks 会浪费 tokens、增加成本,并混淆 generation model。该 metric 借鉴自 information retrieval,因为 vector similarity search 的运作方式类似传统搜索引擎——二者都是按 relevance 对 documents 排名。Precision@k、recall@k 和 F1@k 等 metrics 自然可以迁移到 RAG evaluation。

LLM-as-judge 会将每个 retrieved chunk 与 generated answer 比较,判断该 chunk 是否有贡献。这揭示了 retrieval noise,也就是那些语义上匹配但没有提供有用信息的 chunks。

当诊断 retrieval problems 时,应使用 context precision。如果用户抱怨答案错误,而你怀疑 retriever 没有找到正确信息,context precision 会揭示是否无关 chunks 占据主导。当调优 retrieval parameters,例如 top-k values、similarity thresholds、reranking strategies 时,它很有价值。Context precision 在优化 cost 和 latency 时也很重要。将 k 从 10 降到 3 可以减少 token usage,但前提是这三个 chunks 包含必要信息。调整 k 时应跟踪 context precision。

表 10-1 总结了如何结合 answer quality 解读 context precision scores。

表 10-1:解读 context precision scores

ScenarioInterpretation
High precision + high answer qualityRetriever 工作良好。
High precision + low answer qualityGeneration 问题,例如 hallucination、poor reasoning。
Low precision + high answer quality第一个 chunk 已足够,后续 chunks 增加噪声——考虑降低 k。
Low precision + low answer qualityRetrieval failure。检索到的 chunks 不包含所需信息。检查 embedding model、chunking strategy 或 knowledge base content。

下面列出了 context precision 之外的常见 retrieval metrics:

Precision@k
Top k results 中相关结果的比例。

Recall@k
所有相关结果中被检索进 top k 的比例。

F1@k
平衡 precision 和 recall 的分数。

Mean reciprocal rank(MRR)
第一个相关结果出现得有多早。

Average precision(AP)
同时考虑 relevance 和 ranking position。

Discounted cumulative gain(DCG@k)
当相关结果越早出现在 ranked results 中时,给予更高权重。

Normalized discounted cumulative gain(NDCG@k)
对 DCG 做归一化,以支持跨 query 比较。

当你关心无关内容时,使用 precision。当你担心遗漏信息时,使用 recall。F1@k 会平衡二者,但不会指出哪个问题占主导。当 result order 会影响 generation quality 时,MRR 和 NDCG@k 等 ranking-aware metrics 很重要。如果你的模型主要使用前几个 chunks,那么即使所有相关 chunks 都存在,排序差也会伤害效果。

TIP

ROUGE 和 BLEU metrics 常用于 summarization 和 translation,但并不适合评估 RAG retrievers。它们衡量 n-gram overlap,无法像 precision 和 recall metrics 那样捕捉 semantic relevance。

See Also

Phoenix evaluation documentation 解释 precision、recall 和 F-score,并提供评估 retrieval 和 RAG workflows 的指导。

Pinecone retrieval metrics guide 解释 precision@k、recall@k、F1@k、MRR、AP 和 NDCG@k 的定义与 use cases。

Ragas context precision 实现了 context precision 和其他 retrieval metrics,并提供可读源代码。

10.5 使用 LLM-as-a-Judge 评估 Generation 期间的 Faithfulness

Problem

你想评估 RAG 系统 response 是否基于 retriever 返回的信息。

Solution

要计算 faithfulness,按图 10-13 所示步骤:

  1. 使用 LLM 将 RAG 系统的 answer 拆解为 individual claims。
  2. 遍历所有 claims,并检查每个 claim 是否被 retrieved context 中的任何信息支持。
  3. 将 supported claims 数量除以 derived claims 总数,计算 faithfulness score。

image.png

图 10-13:计算 RAG 系统 response 的 faithfulness

第一步会向模型提供 question 和 answer,并将 answer 拆解为不同 claims。

下面是一个示例 question 和 answer,以及 LLM 在这种情况下会派生出的 statements:

Question
What are the main ingredients in a chocolate chip cookie?

Answer
They typically include flour, sugar, butter, eggs, and chocolate chips. Some recipes also call for vanilla extract or baking soda.

模型会从这个 answer 中派生出七条 standalone statements:

  1. Chocolate chip cookies typically include flour.
  2. Chocolate chip cookies typically include sugar.
  3. Chocolate chip cookies typically include butter.
  4. Chocolate chip cookies typically include eggs.
  5. Chocolate chip cookies include chocolate chips.
  6. Some recipes call for vanilla extract.
  7. Some recipes call for baking soda.

接下来,该过程会将每个 claim 与 retrieved context 比较,并判断哪些 claims 得到支持。Context 中找不到的 claims 会被视为 unsupported。

Faithfulness score 计算如下:

Faithfulness score =
Number of claims in the response supported by the retrieved context
/
Total number of claims in the response

对于这个 recipe,你需要访问 LLM。这里再次使用 OpenAI model,将 answers 拆解成 individual claims。

你还会使用 OpenAI SDK 和 Pydantic library,将模型 response schema 定义为 Pydantic model。安装二者:

pip install openai pydantic pandas

首先定义一个函数,指示模型将 answer 拆解为一个或多个 fully understandable statements。输出是一个遵循 Pydantic model schema 的 Python object。在这个例子中,它包含一个 statements attribute,用来保存字符串列表:

from textwrap import dedent
from pydantic import BaseModel, Field
from openai import OpenAI

def decompose_answer(question, answer):
    # Initialize the OpenAI client
    client = OpenAI()

    statement_prompt = """
    Given a question and an answer, analyze the complexity of each
    sentence in the answer. Break down each sentence into one or more
    fully understandable statements. Ensure that no pronouns or
    ambiguous references are used in any statement. Output the
    decomposed statements as a list of strings.

    Question: {question}
    Answer: {answer}
    """

    prompt = statement_prompt.format(question=question, answer=answer)

    class Statements(BaseModel):
        """Structured response for statement extraction."""

        statements: list[str] = Field(
            description="List of decomposed statements."
        )

    completion = client.chat.completions.parse(
        messages=[
            {
                "role": "user",
                "content": prompt,
            }
        ],
        model="gpt-5.2",
        response_format=Statements,
    )

    statements = completion.choices[0].message.parsed.statements
    return statements

接下来,将该函数应用到一个 sample question / answer pair。问题关于 Amazon rainforest,答案将其描述为最大的 tropical rainforests 之一,并包含其地理范围:

question = """Describe the geographic extent and ecological
significance of the Amazon rainforest."""

answer = dedent("""
The Amazon rainforest, the world's largest tropical rainforest, covers
significant territory across nine South American countries. This vast
area is renowned for its immense biodiversity, supporting millions
of different plant and animal species. While it plays a crucial role
in the global climate by absorbing substantial amounts of carbon dioxide,
its impact on the overall oxygen production of the Earth is often overstated.
""")

图 10-14 展示了输出。在这个例子中,从 answer text 中提取了九条 statements。

image.png

图 10-14:将 answer 拆解为 claims

然后,将这些 statements 逐条与 retrieved contexts 比较。对于每条 statement,检查它是否被提供给 LLM 的 context 中的信息直接支持。这可以判断答案是否真正基于 retrieved documents,还是模型引入了 unsupported information。

你可以使用 LLM 自身执行这些检查:

from pydantic import BaseModel, Field
from textwrap import dedent
from openai import OpenAI

def judge_faithfulness(statement, context):

    # Initialize the OpenAI client
    client = OpenAI()

    faithfulness_judge_prompt = dedent(
        """
        Your task is to judge the faithfulness of a statement based
        on the given context. You must return a verdict as 1 if the
        statement can be directly inferred from the context, or 0 if
        the statement cannot be directly inferred. Explain your
        reasoning.

        Context:
        {context}

        Statement:
        {statement}

        Answer::
        Reason: (Explain your reasoning)
        Verdict: (1 or 0)
        """
    )

    class StatementFaithfulness(BaseModel):
        """Structured response for statement extraction."""

        statement: str = Field(description="Decomposed statement.")
        reason: str = Field(
            description="Reasoning for the faithfulness judgment."
        )
        verdict: int = Field(
            description="1 if the statement is faithful, 0 otherwise."
        )

    prompt = faithfulness_judge_prompt.format(
        statement=statement, context=context
    )

    completion = client.chat.completions.parse(
        messages=[
            {
                "role": "user",
                "content": prompt,
            }
        ],
        model="gpt-5.2",
        response_format=StatementFaithfulness,
    )

    statement_faithfulness = (
        completion.choices[0].message.parsed.model_dump()
    )
    return statement_faithfulness

然后运行前面定义的 faithfulness score judge:

statements_faithfulness = []

context = dedent(
    """
    The Amazon rainforest is the largest tropical rainforest in the
    world, covering parts of nine South American countries. It is
    known for its incredible biodiversity, housing millions of species
    of plants, insects, birds, and other animals. The forest plays a
    crucial role in regulating the Earth's climate by absorbing vast
    amounts of carbon dioxide.
    """
)

for statement in statements:
    statement_faithfulness = judge_faithfulness(
        statement=statement, context=context
    )
    # Append the statement faithfulness to the list
    statements_faithfulness.append(statement_faithfulness)

图 10-15 展示了一个示例输出。代码会生成一个 dictionaries 列表,每个 dictionary 包含一条 statement、表示 claim 是否被 context 支持的 verdict,以及模型在 reason 字段中的简短解释。Verdict 会显示哪些 claims 基于 retrieved documents,以及原因。

image.png

图 10-15:使用 LLM 评估 faithfulness

最后,用这些 verdicts 计算最终 faithfulness score。Faithfulness score 是 supported claims 数量与 answer 中 claims 总数之比。分数越高,说明 RAG 系统越能保持 grounded,并在不添加 unsupported claims 的情况下提供信息。代码如下:

import pandas as pd

statements_faith_df = pd.DataFrame(statements_faithfulness)

number_of_claims = len(statements_faith_df["verdict"])  # 7
number_of_claims_in_context = statements_faith_df["verdict"].sum()  # 6
faithfulness_percentage = (
    number_of_claims_in_context / number_of_claims
) * 100

生产系统中应以 faithfulness scores 高于 80% 为目标。这表明 RAG 系统能够可靠地将答案建立在 retrieved context 上。低于 70% 的分数则暗示潜在 hallucination issues,也就是系统生成了 retrieved documents 不支持的 claims。

80% 这个阈值平衡了可靠性与有用性。85% 或更高表明 grounding 很强——系统很少编造信息。70%–80% 值得调查:审查失败案例,以识别问题是系统性的,例如在特定 topics 上 hallucinate,还是随机的。如果持续低于 70%,可以考虑调整 generation prompt,以强调 grounding;降低 temperature;或切换到 instruction-following 更好的模型。

Discussion

LLM-as-judge 很适合 faithfulness metric,因为任务很清楚:判断某个 claim 是否被文本支持,只需要阅读理解,而不需要世界知识。

在 medical、financial、legal 等高风险领域,应使用 faithfulness 检测 hallucinations,因为 unsupported claims 会造成 liability risk。Faithfulness 也可以评估 generation step 的变化——如果切换模型、调整 prompts 或修改 temperature,这个 metric 可以揭示新配置是否仍然保持 grounded。

高 faithfulness 并不保证答案好。系统可能返回 grounded 但 irrelevant 的信息。应将 faithfulness 与 answer relevancy,也就是答案是否回应问题,以及 context precision,也就是是否检索到正确信息,配合使用。这三个 metrics 会独立变化:改善 retrieval 不保证 faithfulness 更好,如果模型仍会 hallucinate;perfect faithfulness 也无法阻止低 answer relevancy,如果模型关注了 context 中错误方面。

取舍是 faithfulness versus informativeness。高 faithfulness 模型更保守,只陈述 context 中明确出现的信息。对于大多数 RAG 应用,应优先 faithfulness——用户期待可靠的信息检索。生产中目标应高于 80%;低于 70% 表示频繁出现 unsupported claims。

理解 Framework 实现

理解 evaluation frameworks 如何实现 faithfulness 的最可靠方式,是检查其源代码。在 VS Code 中,你可以按住 Ctrl 并点击从 Ragas 导入的 faithfulness function,直接跳转到实现,如图 10-16 所示。

image.png

图 10-16:浏览 framework code

沿着代码导航,你最终会找到一个名为 StatementGeneratorPrompt 的 class。图 10-17 展示了它的定义,并让你了解这些 frameworks 如何定义其 evaluation metrics。

image.png

图 10-17:Ragas library 中的 statement generator class

See Also

Ragas faithfulness metric 提供了 RAG 系统 faithfulness evaluation 的详细解释和实现。

OpenEvals repository 提供了 correctness、conciseness 和 groundedness 等 metrics 的预定义 evaluation prompts。

LangSmith evaluation guide 展示了如何使用 LangSmith 和 OpenEvals 评估 RAG applications。

10.6 评估 RAG 系统的 Response Relevancy

Problem

你想用自动化方式衡量 RAG 系统最终答案对用户原始问题的回应程度。

Solution

Response relevancy score 通过五个步骤计算:

  1. 将用户问题输入 RAG 系统,生成 response。
  2. 使用 LLM 分析 answer,并派生一组 artificial questions,这些问题可以引出 answer 中反映的相同内容。
  3. 为原始 user question 和每个 artificial question 生成 embeddings。
  4. 计算原始用户问题 embedding E_o 与每个 artificial question embedding E_g_i 之间的 cosine similarity。
  5. 对所有 artificial questions 的 cosine similarity scores 取平均,得到最终 relevancy score。

Answer relevancy 公式如下:

Answer relevancy =
1/N * Σ(i=1 to N) cosine similarity(E_g_i, E_o)

其中:

E_g_i 是第 i 个 generated question 的 embedding。

E_o 是原始 user question 的 embedding。

N 是 generated questions 数量,默认是 3。

图 10-18 可视化了 RAG 系统中计算 answer relevancy 的过程。

image.png

图 10-18:计算 RAG 系统 answer relevancy 的过程

为了实现这个 recipe,你需要一个 embedding model 和一个 LLM。在这个例子中,二者都来自 OpenAI,并通过 OpenAI SDK 访问。为了确保 artificially generated questions 以字符串列表形式返回,需要定义 Pydantic model。你还会使用 NumPy 计算 embedding vectors 之间的 cosine similarity。安装所需 Python packages:

pip install openai pydantic numpy pandas

在前两步中,你使用 LLM 生成从 RAG 系统 answer 派生出的 artificial questions,并将每个问题标记为 committal 或 noncommittal。

如果一个 answer 没有为某个问题提供相关、有价值的信息,这个问题就是 noncommittal。noncommittal 值为 0 是好的,因为这意味着答案包含了与问题相关的信息。

表 10-2 展示了一些 sample questions 和 responses 及其 noncommittal scores。

表 10-2:Noncommittal question assessment 示例

Generated questionResponseNoncommittal scoreExplanation
What is the capital of France?The capital of France is Paris.0答案用具体信息直接回应了问题。
How are you feeling today?As a large language model, I’m not capable of experiencing emotions.1答案承认了问题,但没有提供相关信息。
What is the current price of Tesla stock?I can’t provide real-time stock prices.1答案没有提供所请求的信息。

这里,你会在一次运行中完成这两个步骤。下面代码接收 answer,基于 answer 生成合适 questions,然后评估 answer 是否相关且有助于回答每个 generated question:

from textwrap import dedent

response_relevance_prompt = dedent("""
    Generate three relevant questions for the following answer
    and indicate if the answer is noncommittal.

    Output:
    Question: [Generated Question]
    Noncommittal (1=Yes, 0=No): [0 or 1]

    An answer is considered noncommittal if it is evasive, vague,
    or ambiguous (e.g., "I don't know," "Maybe," "It depends").

    Answer: {response}
    """)

定义一个 Pydantic model,确保代码稳定返回格式一致的 dictionaries 列表。该 model 定义 GenerateQuestions class,包含一个 items 列表;每个 item 有一个 question 和一个 noncommittal score。当 answer 是 noncommittal 时,该值为 1;当 answer 是 committal 时,该值为 0:

from pydantic import BaseModel, Field

prompt = response_relevance_prompt.format(response=response)

class GeneratedQuestion(BaseModel):
    question: str = Field(description="Generated question.")
    noncommittal: int = Field(
        description="1 if the answer is noncommittal, 0 otherwise."
    )

class GeneratedQuestions(BaseModel):
    questions: list[GeneratedQuestion] = Field(
        description="List of generated questions and noncommittal status."
    )

client = OpenAI()

completion = client.chat.completions.parse(
    messages=[
        {
            "role": "user",
            "content": prompt,
        }
    ],
    model="gpt-5.2",
    response_format=GeneratedQuestions,
)

generated_questions = (
    completion.choices[0].message.parsed.model_dump()["questions"]
)

接下来,定义一个使用 OpenAI embedding model 创建 embeddings 的函数:

from openai import OpenAI

client = OpenAI()

def create_embeddings(text_chunk, client):
    embed_model = "text-embedding-3-small"
    embedding = (
        client.embeddings.create(input=[text_chunk], model=embed_model)
        .data[0]
        .embedding
    )
    return embedding

TIP

Cosine similarity score 接近 1,表示两个 vectors 相同,或指向相同方向,也就是两个问题的语义内容非常相似。从数学上看,该分数可以为负,但使用现代 embedding models 时,这种情况很少见,除非文本刻意不相似或 embeddings 质量较差。

接下来,使用前面定义的函数,为 artificial questions 和原始 user query 生成 embeddings。

在这个例子中,用户询问法国首都,系统回答法国首都是巴黎。

随后计算每个 artificial question 与用户问题之间的 cosine similarity。这会给每个 question object 或 dictionary 添加新属性,例如 embedding vector 和计算出的 cosine similarity:

import numpy as np
import pandas as pd

user_question_embedding = create_embeddings(user_input, client)
cosine_sims = []

for generated_question in generated_questions:
    generated_question["embedding"] = create_embeddings(
        generated_question["question"], client
    )

    generated_question["cosine_sim"] = np.dot(
        user_question_embedding, generated_question["embedding"]
    ) / (
        np.linalg.norm(user_question_embedding)
        * np.linalg.norm(generated_question["embedding"])
    )

图 10-19 展示了包含 embedding 和 cosine similarity 的 generated question object 示例结构。

image.png

图 10-19:带 embedding 和 cosine similarity 的 generated question object 结构

最后一步使用每个 question 的 cosine similarity score 计算整体 response relevancy score。该逻辑会惩罚 evasive answers:如果任何 generated question 被评为 noncommittal,也就是 answer 没有为该问题提供有用信息,整体 score 会被设置为 0。否则,score 是所有 artificial questions 与用户问题之间 cosine similarity 的平均值。代码如下:

# Calculate the mean cosine similarity over all generated questions
cosine_sim_mean = pd.DataFrame(generated_questions)["cosine_sim"].mean()

# Check if any of the generated questions are noncommittal
has_noncommittal = any(pd.DataFrame(generated_questions)["noncommittal"])

# If any generated question is noncommittal, set score to 0
# This penalizes evasive answers that acknowledge but don't address questions
response_relevance_score = cosine_sim_mean * int(not has_noncommittal)

这种严格方法会将任何 noncommittal response 视为完全失败。如果你偏好更柔和的惩罚,使 score 按比例降低而不是直接归零,可以使用:

response_relevance_score = cosine_sim_mean * (1 - noncommittal_ratio)

其中,noncommittal_ratio 是被标记为 noncommittal 的 generated questions 比例。

Response relevancy score 为 0.87 是相当高的。这表示自动生成的问题与原始用户问题高度相似。

图 10-20 展示了最终 response relevancy score。

image.png

图 10-20:最终 response relevancy score

Discussion

Answer relevancy 使用 reverse question generation 来评估 alignment。如果 LLM 从一个 answer 中生成 questions,那么只要该 answer 回应了原问题,这些 questions 就应与原始问题相似。高 semantic similarity 表示对齐;低 similarity 则揭示 misdirection。Noncommittal check 会捕捉那些承认问题但不提供有用信息的 evasive answers。

当诊断为什么用户觉得技术上正确的答案不满意时,可以使用 answer relevancy。高 faithfulness 但低 satisfaction,通常表示 relevancy problems——系统提供了 grounded facts,但没有回应用户意图。该 metric 也可以评估 generation prompt changes。

该 metric 能检测 off-topic answers,但不能检测 incomplete answers。对于 multipart question,如果只回答了其中一部分,也可能仍然获得高 relevancy score。应结合 correctness metrics 或 human evaluation 做综合评估。

Faithfulness 问的是:“答案是否遵循 retrieved facts?” Answer relevancy 问的是:“答案是否回应了问题?” 二者都必要:faithfulness 确保可靠性,answer relevancy 确保有用性。

See Also

Ragas response relevancy 实现了 answer relevancy evaluation,并提供详细 metric definitions 和代码示例。

DeepEval answer relevancy metric 提供 response relevancy evaluation,并结合 faithfulness 和 contextual precision metrics。

OpenEvals correctness metric 提供了用于评估 answer correctness 的 prompts,可补充 relevancy assessment。