作者:来自 Elastic Jeffrey Rengifo

了解你的 RAG agent 是否已准备好投入生产。仅使用 Elasticsearch Workflows 和两个 Claude 模型,从正确性(correctness)、忠实性(faithfulness)和检索质量(retrieval quality)三个维度对其进行评分。
Agent Builder 现已正式发布。立即开始你的 Elastic Cloud Trial,并查看 Agent Builder 文档。
在 Elasticsearch Workflows 内部构建完整的 RAG 评测流水线:无需外部评测框架,也无需额外基础设施。一个小模型基于你的知识库回答问题;一个更强的模型则从正确性(correctness)、忠实性(faithfulness)和上下文相关性(context relevance)三个维度对每个回答进行评分。
在一个包含 35 个 HotpotQA 测试用例的测试中,Claude Haiku 4.5 获得了 0.74 的正确性评分和 0.90 的忠实性评分,足以精确定位 retrieval 或推理过程究竟在哪一步出现问题。知识库、评判标准、workflow 执行过程以及评分结果全部保存在同一个系统中。
前提条件
-
一个 Elastic Cloud 集群,或自托管的 Elasticsearch / Kibana 9.4+。如果还没有,可以免费试用。
-
Python 3.13
什么是 LLM-as-a-Judge?
LLM-as-a-Judge 使用一个更强大的语言模型,自动对较弱模型生成的结果进行评分,从而替代一致性较差且难以扩展的人工评审。
手工评估一个 RAG 系统对于少量问题来说是可行的,但无法扩展。如果你修改了检索策略、提示词(prompt)或模型,就需要重新检查所有答案。这不仅耗时,而且不同评审人员之间的评分标准也往往不一致。
LLM-as-a-Judge 模式通过使用一个更强大的语言模型来评估较弱模型的输出,从而解决这一问题。其核心思想非常简单:如果一个语言模型能够可靠地判断一个答案是否正确,那么你就可以在几分钟内自动完成数百个测试用例的质量评估。
在本文中,整个流水线包含两个阶段:
-
阶段 1(回答):Claude Haiku 4.5 接收问题以及从知识库中检索出的相关内容(passages),生成回答。这是需要进行评测的模型。
-
阶段 2(评判):Claude Sonnet 4.6 接收相同的问题、标准答案(ground-truth answer,即已知正确答案)、候选答案(阶段 1 的输出)以及检索到的 passages,并从三个维度对回答进行评分。
RAG 的三个评测指标:正确性(correctness)、忠实性(faithfulness)和上下文相关性(context relevance)
| 指标 | 衡量内容 | 低分意味着什么 |
|---|---|---|
| correctness(正确性) | 候选答案是否与标准答案一致? | agent 回答错误。 |
| faithfulness(忠实性) | 回答是否基于检索到的 passages? | agent 产生了幻觉(编造了内容)。 |
| context_relevance(上下文相关性) | 检索到的 passages 是否与问题相关? | retrieval 检索到了错误的文档。 |
这三个指标结合起来,能够告诉你问题出在哪里 。如果 correctness 很高,而 context_relevance 很低,这就是一个危险信号:尽管检索效果不佳,模型仍然回答正确,很可能是依赖自身训练数据而不是检索结果完成了回答。对于像 Wikipedia 这样的公开知识,这种方式通常没有问题;但对于模型从未见过的私有数据,这种做法就会失效。
另一种互补的方法是结合 Elasticsearch 使用 Ragas 指标,从基于框架(framework-based)的角度探索这些评测维度。
如何加载并索引 HotpotQA 数据集
该流水线使用 HotpotQA 的 distractor 配置:多跳(multi-hop)问题,每个问题都配有 10 个上下文段落,并存储在两个 Elasticsearch 索引中。
你可以参考本文配套的完整 notebook,其中包含环境配置、连接以及所有辅助代码。
完成本教程需要以下信息:
-
ELASTICSEARCH_URL -
ELASTICSEARCH_API_KEY(可按照连接信息指南获取) -
KIBANA_URL(可在Kibana 访问页面找到)
该流水线使用在 Kibana 中配置好的两个 AI connectors:
-
Anthropic Claude Haiku 4.5:作为回答模型(体积小、成本低、速度快)。
-
Anthropic Claude Sonnet 4.6:作为评判模型(能力更强)。
整个流水线要回答的问题是:
Haiku 在这项任务上的表现是否足够好,可以用于生产环境?/Is Haiku good enough on this task to be used in production?
我们使用 HotpotQA 的 distractor 配置。每个问题都包含 10 个上下文段落:其中两个支持正确答案,另外八个是干扰项(distractors)。这些段落会被索引到知识库中,供第一阶段(Stage 1)进行 retrieval。
这些问题属于多跳(multi-hop)问题,也就是说,回答一个问题需要综合来自两个不同段落的信息。
我们会抽取部分问题作为样本,将其上下文段落展开后索引到知识库索引中,并将问题---答案对存储到单独的评判列表(judgement list)中。最终数据会存储到两个 Elasticsearch 索引中:
知识库:上下文段落
hotpot-knowledge-base 保存回答模型进行 retrieval 时使用的上下文段落。每个 document 都包含一个 title 和一个 passage:
{"title": "Ed Wood (film)", "passage": "Ed Wood is a 1994 American biographical period comedy-drama film..."}
passage 字段会复制到 semantic_content(映射为 semantic_text),这样第一阶段(Stage 1)就可以使用自然语言 query 检索相关段落,而无需手动管理 embeddings。
Elasticsearch 会自动为已索引的段落生成 embeddings,并在执行 语义搜索(semantic search) 时自动为查询生成 embeddings。
INDEX_NAME = "hotpot-knowledge-base"
if es_client.indices.exists(index=INDEX_NAME):
es_client.indices.delete(index=INDEX_NAME)
es_client.indices.create(
index=INDEX_NAME,
mappings={
"properties": {
"title": {"type": "keyword"},
"passage": {
"type": "text",
"copy_to": "semantic_content",
},
"semantic_content": {
"type": "semantic_text",
"inference_id": ".jina-embeddings-v5-text-small",
},
}
},
)
print(f"Created index: {INDEX_NAME}")
评判列表:问题与标准答案
hotpot-judgement-list 保存评测用例。每个 document 都包含一个 question 和一个 answer(标准答案,ground truth):
{"question": "Were Scott Derrickson and Ed Wood of the same nationality?", "answer": "yes"}
answer 字段包含正确答案。第二阶段(Stage 2)的 judge 会将回答模型的输出与该答案进行比较。
Elasticsearch Workflow 如何运行评测
这篇使用 Elastic Workflows 构建自动化的介绍文章详细介绍了 trigger、step 和数据流的核心概念。

Workflow 定义
该 workflow 使用 YAML 定义,并通过 Workflows API 上传(Elastic 9.4+)。以下是完整定义:
WORKFLOW_YAML = """
name: agent_accuracy_eval
description: >
Batch evaluation. Loads the judgement list, iterates each case with
a foreach, runs stage 1 (RAG answer with Haiku) and stage 2 (LLM
judge with Sonnet), and indexes every score into the eval results
index.
enabled: true
consts:
kbIndex: hotpot-knowledge-base
judgeIndex: hotpot-judgement-list
resultsIndex: eval-results
triggers:
- type: manual
steps:
# Load the judgement list (all cases we want to evaluate).
- name: load_cases
type: elasticsearch.search
with:
index: "{{ consts.judgeIndex }}"
query:
match_all: {}
size: 35
# One iteration = one evaluation.
- name: eval_loop
type: foreach
foreach: "{{ steps.load_cases.output.hits.hits }}"
steps:
- name: retrieve
type: elasticsearch.search
with:
index: "{{ consts.kbIndex }}"
query:
semantic:
field: semantic_content
query: "{{ foreach.item._source.question }}"
size: 4
- name: agent_answer
type: ai.prompt
with:
connector-id: Anthropic-Claude-Haiku-4-5
prompt: >
You are a Wikipedia QA assistant. Answer the question
using ONLY the passages provided. Keep the answer short
(one line). If the passages do not contain the answer,
reply "unknown".
Question: {{ foreach.item._source.question }}
Passages:
1. {{ steps.retrieve.output.hits.hits[0]._source.passage }}
2. {{ steps.retrieve.output.hits.hits[1]._source.passage }}
3. {{ steps.retrieve.output.hits.hits[2]._source.passage }}
4. {{ steps.retrieve.output.hits.hits[3]._source.passage }}
- name: judge
type: ai.prompt
with:
connector-id: Anthropic-Claude-Sonnet-4-6
prompt: >
You are a STRICT evaluator. Score the candidate answer
against the ground truth on three axes. Each score MUST
be exactly one of these three values: 0.0, 0.5, or 1.0.
Do not return any other number.
correctness:
1.0 = candidate contains the ground truth answer exactly
or an unambiguous synonym, and nothing factually wrong.
0.5 = partially correct (one side of a multi-hop right,
or mostly right with minor noise).
0.0 = wrong, missing, or contradicts the ground truth.
faithfulness:
1.0 = every factual claim is supported by the passages.
0.5 = mostly supported, one minor unsupported claim.
0.0 = contains at least one unsupported claim.
context_relevance:
1.0 = the passages contain enough to answer the question.
0.5 = partial coverage (one hop covered, the other missing).
0.0 = passages do not cover the answer.
Be harsh. If in doubt between two scores, pick the lower one.
Question: {{ foreach.item._source.question }}
Ground truth: {{ foreach.item._source.answer }}
Candidate: {{ steps.agent_answer.output.content }}
Passages:
1. {{ steps.retrieve.output.hits.hits[0]._source.passage }}
2. {{ steps.retrieve.output.hits.hits[1]._source.passage }}
3. {{ steps.retrieve.output.hits.hits[2]._source.passage }}
4. {{ steps.retrieve.output.hits.hits[3]._source.passage }}
schema:
type: object
properties:
correctness:
type: number
minimum: 0
maximum: 1
faithfulness:
type: number
minimum: 0
maximum: 1
context_relevance:
type: number
minimum: 0
maximum: 1
required:
- correctness
- faithfulness
- context_relevance
# Persist each scored case so the notebook can query it later.
- name: save
type: elasticsearch.index
with:
index: "{{ consts.resultsIndex }}"
document:
qid: "{{ foreach.item._source.qid }}"
question: "{{ foreach.item._source.question }}"
ground_truth: "{{ foreach.item._source.answer }}"
candidate: "{{ steps.agent_answer.output.content }}"
correctness: "{{ steps.judge.output.content.correctness }}"
faithfulness: "{{ steps.judge.output.content.faithfulness }}"
context_relevance: "{{ steps.judge.output.content.context_relevance }}"
"""
让我们逐步了解关键部分:
-
consts:用于定义索引名称的命名常量。这样可以保持 YAML 文件简洁,并且方便将同一个 pipeline 指向不同的数据集。 -
load_cases:一个elasticsearch.searchstep,用于从评判列表中获取所有测试用例。 -
eval_loop:一个foreachstep,用于遍历搜索结果。每次迭代都会运行以下四个嵌套 step:-
retrieve:使用问题作为 query,在知识库上运行语义搜索,检索相关内容。 -
agent_answer:一个ai.promptstep,将问题以及检索到的前四个 passages 发送给 Haiku。connector-id字段引用 Kibana AI Connector。 -
judge:另一个ai.promptstep,这次调用 Sonnet。schema代码块强制模型返回一个具有固定字段的 JSON 对象。无需 markdown 代码块,无需 regex,也无需在读取时进行解析。minimum和maximum约束确保评分保持在 0--1 范围内。 -
save:一个 elasticsearch.index step,用于将每个评分后的测试用例写入结果索引。由于 judge 输出已经通过schema解析为 JSON,因此可以直接引用各个字段:{``{ steps.judge.output.content.correctness }}。
-
上传并运行 workflow
Elastic 9.4 提供了用于 workflow 的 REST API。我们使用三个端点:
-
POST /api/workflows:根据 YAML 创建 workflow。 -
POST /api/workflows/{id}/run:启动一次执行。 -
GET /api/workflows/executions/{id}:轮询查询,直到执行完成。
对于 35 个测试用例,该 workflow 需要几分钟完成。每次迭代都会执行一次语义搜索、一次用于生成答案的大语言模型(LLM)调用、另一次用于评判的大语言模型调用,以及一次索引操作。
如何读取和解读 RAG 评测结果
每个评分后的测试用例已经作为一个 document 存储在 eval-results 索引中,并包含类型化的评分字段。无需进行解析:我们只需要查询该索引,并计算每个指标的平均值。
35 个测试用例的聚合 RAG 评测分数
下面的柱状图展示了 35 个测试用例中每个指标的平均分(使用 Haiku 作为回答模型):

在这次使用 Claude Haiku 4.5 作为回答模型的评测中,结果如下:
-
correctness:0.74:大约有 70% 的问题回答正确。对于多跳 Wikipedia 问题来说,这是一个合理的表现,但仍有提升空间。
-
faithfulness:0.90:当 Haiku 能够回答时,它能够保持基于 passages 生成答案。幻觉并不是这里的主要问题。
-
context_relevance:0.86:大多数情况下,语义检索能够找到相关 passages,但一些多跳问题需要的 passages 并没有出现在前四个检索结果中。
faithfulness(0.90)与 correctness(0.74)之间的差距说明,模型并不是在编造内容,而是在较难的问题上没有找到正确答案。这是 retrieval 或推理问题,而不是幻觉问题。增加检索 passages 的数量,或者针对最困难的问题切换到更强的模型,都是可行的改进方向。
RAG 评测结果对生产环境的意义
我们构建了一个完全运行在 Elasticsearch 内部的完整评测流水线。知识库、评判列表、workflow 执行过程以及评分结果全部存储在同一个系统中。
workflow YAML 可以进行版本控制,可以重复执行,并且可以通过 API 触发。如果你更新了知识库或更换了回答模型,只需要重新运行相同的 pipeline,然后将新的评分结果与旧结果进行比较。
对于这个特定数据集,结果表明 Haiku 具有较高的忠实性,但在多跳问题上并不总能回答正确。是否足够满足你的需求,取决于你的具体使用场景以及你自己的数据。评测 pipeline 的价值在于提供数据,让你能够基于可靠依据做出决策,而不是凭感觉判断。
下一步
接下来,你可以:
-
扩展评判列表:添加符合实际生产流量的领域专属问题。
-
替换回答模型:比较不同模型在同一测试集上的表现。
-
调度 workflow:在新文档被索引后自动运行。
-
构建 Kibana 仪表盘 :基于
eval-results索引跟踪质量随时间的变化。
相关文章:
这篇内容对你有多大帮助?
原文:LLM-as-a-Judge in Elasticsearch Workflows: RAG evaluation - Elasticsearch Labs