Haystack Evaluators 组件 API 全解:从答案匹配到 LLM 评审的九种评测器
【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack
本文基于 Haystack 仓库haystack/components/evaluators模块的官方 API 参考(对应 version 2.21 文档),逐一拆解 9 个内置评测组件的输入输出契约、关键参数与默认值,并结合仓库源码(如 llm_evaluator.py、document_ndcg.py)说明其底层实现:prompt 模板如何组装、JSON 输出如何校验、文档级指标(Recall/MRR/MAP/NDCG)如何按位置折扣计算,以及 LLM 型评测器如何继承LLMEvaluator的失败处理机制。读完本文,你能够为 RAG 管线的检索与生成两个环节分别选配合适的评测器,并在 Pipeline 中以可序列化的方式接入它们。
1. Evaluators 模块总览
Evaluators 是一组用于评测管线或单个组件的 Haystack 组件,覆盖 RAG 应用评测的三类需求:
| 类别 | 组件 | 评估对象 | 是否需要 LLM/模型 |
|---|---|---|---|
| 答案级(字符串) | AnswerExactMatchEvaluator | 预测答案 vs 标准答案,精确匹配 | 否 |
| 答案级(语义) | SASEvaluator | 预测答案 vs 标准答案,语义相似度 | 是(HuggingFace 模型) |
| 文档级(检索) | DocumentRecallEvaluator/DocumentMRREvaluator/DocumentMAPEvaluator/DocumentNDCGEvaluator | 检索文档 vs 标准答案文档,按排序位置计算 | 否 |
| LLM 型(通用) | LLMEvaluator | 任意列表输入,按自定义指令打分 | 是(ChatGenerator) |
| LLM 型(专用) | ContextRelevanceEvaluator/FaithfulnessEvaluator | 上下文相关性 / 答案忠实度 | 是(ChatGenerator) |
从源码结构看,该模块通过init.py 中的_import_structure字典 +LazyImporter实现惰性导入:只有真正 import 某个评测器时才加载对应模块,避免了sentence-transformers、tqdm等依赖在无关场景被强制加载。因此from haystack.components.evaluators import DocumentMAPEvaluator与from haystack.components.evaluators.document_map import DocumentMAPEvaluator两种写法均可。
2. 答案级评测器
2.1 AnswerExactMatchEvaluator:精确匹配基线
最轻量的评测器:检查每个预测答案是否逐一精确等于标准答案(源码 answer_exact_match.py)。
from haystack.components.evaluators import AnswerExactMatchEvaluator evaluator = AnswerExactMatchEvaluator() result = evaluator.run( ground_truth_answers=["Berlin", "Paris"], predicted_answers=["Berlin", "Lyon"], ) print(result["individual_scores"]) # [1, 0] print(result["score"]) # 0.5run签名(输出类型@component.output_types(individual_scores=list[int], score=float)):
def run(ground_truth_answers: list[str], predicted_answers: list[str]) -> dict[str, Any]- 约束:
ground_truth_answers与predicted_answers长度必须一致,否则抛ValueError; - 返回
individual_scores(0/1 列表,1 表示该预测命中标准答案之一)与score(0.0–1.0,命中比例)。
源码实现为逐元素zip(strict=True)比较后求平均(answer_exact_match.py),不做任何大小写/空白归一化——它是严格的"字符串相等",适合作为快速回归基线,而非语义质量指标。
2.2 SASEvaluator:语义答案相似度
SASEvaluator计算预测答案与标准答案之间的Semantic Answer Similarity(SAS),通常用于 RAG 管线中评估生成答案质量(源码 sas_evaluator.py)。它基于 HuggingFace 模型库的预训练模型,可为Bi-Encoder或Cross-Encoder,由model参数决定:
from haystack.components.evaluators.sas_evaluator import SASEvaluator evaluator = SASEvaluator(model="cross-encoder/ms-marco-MiniLM-L-6-v2") evaluator.warm_up() ground_truths = [ "A construction budget of US $2.3 billion", "The Eiffel Tower, completed in 1889, symbolizes Paris's cultural magnificence.", "The Meiji Restoration in 1868 transformed Japan into a modernized world power.", ] predictions = [ "A construction budget of US $2.3 billion", "The Eiffel Tower, completed in 1889, symbolizes Paris's cultural magnificence.", "The Meiji Restoration in 1868 transformed Japan into a modernized world power.", ] result = evaluator.run( ground_truth_answers=ground_truths, predicted_answers=predictions ) print(result["score"]) # 0.9999673763910929 print(result["individual_scores"]) # [0.9999765157699585, 0.999968409538269, 0.9999572038650513]__init__参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
model | sentence-transformers/paraphrase-multilingual-mpnet-base-v2 | SentenceTransformers 语义相似度模型,可为 HF 模型名或本地路径 |
batch_size | 32 | 一次编码的预测-标签对数量 |
device | None | 模型加载设备;None时自动选择默认设备 |
token | Secret.from_env_var(["HF_API_TOKEN", "HF_TOKEN"], strict=False) | HuggingFace Bearer 授权令牌 |
run签名:
@component.output_types(score=float, individual_scores=list[float]) def run(ground_truth_answers: list[str], predicted_answers: list[str]) -> dict[str, float | list[float]]两个列表长度必须相同;predicted_answers中不允许出现None;空输入直接返回{"score": 0.0, "individual_scores": [0.0]}。
源码中的关键实现细节(sas_evaluator.py):
warm_up()通过AutoConfig.from_pretrained读取模型架构,若架构以ForSequenceClassification结尾则判定为 Cross-Encoder 并加载CrossEncoder,否则加载SentenceTransformer。Bi-Encoder 与 Cross-Encoder 的相似度计算方式不同:前者分别编码两侧后逐对计算余弦相似度,后者直接对句子对打分;- Cross-Encoder 返回的原始 logits 未必归一化,源码会在分数大于 1 时用
expit(sigmoid)将其压回 0–1; score是所有individual_scores的均值(np_mean)。
注意 SASEvaluator 依赖sentence-transformers>=5.0.0,仓库中通过LazyImport延迟导入,未安装时会提示Run 'pip install "sentence-transformers>=5.0.0"'(sas_evaluator.py)。
3. 文档级检索评测器
文档级评测器输入均为list[list[Document]]——外层列表对应每个问题,内层列表是该问题的标准答案文档(ground_truth_documents)或检索结果(retrieved_documents),两侧外层列表长度必须一致。所有指标输出统一为score(各问题得分均值)+individual_scores(每个问题 0.0–1.0 的分数)。
3.1 DocumentRecallEvaluator:召回率
RecallMode枚举定义两种打分模式:
SINGLE_HIT("single_hit",默认):只要检索命中任一标准答案文档即记 1,否则记 0;MULTI_HIT("multi_hit"):按命中比例给分,分数为 0.0–1.0 之间的连续值。
from haystack import Document from haystack.components.evaluators import DocumentRecallEvaluator evaluator = DocumentRecallEvaluator() result = evaluator.run( ground_truth_documents=[ [Document(content="France")], [Document(content="9th century"), Document(content="9th")], ], retrieved_documents=[ [Document(content="France")], [Document(content="9th century"), Document(content="10th century"), Document(content="9th")], ], ) print(result["individual_scores"]) # [1.0, 1.0] print(result["score"]) # 1.0__init__接受mode: Union[str, RecallMode] = RecallMode.SINGLE_HIT;传入字符串时经RecallMode.from_str转换,未知取值抛ValueError(document_recall.py)。
从源码结构看,当前仓库的实现有两个文档中未展开的深化点:
- 比较字段可配置:
__init__新增document_comparison_field参数(默认"content"),可取"content"、"id"或meta.前缀的嵌套键(如"meta.file_id"、"meta.source.url"),由_get_comparison_value统一解析(document_recall.py); - 按比较值去重计分:
_unique_comparison_values对两侧取值去重后做集合运算,multi_hit模式即|交集| / |truths|;任一侧无有效比较值时记录 warning 并计 0 分(document_recall.py)。
3.2 DocumentMRREvaluator:平均倒数排名
MRR 衡量第一个相关文档被排在多高的位置:对每个问题,找到第一个命中标准答案集合的检索位置 k,得分为1/k;未命中则 0。
from haystack import Document from haystack.components.evaluators import DocumentMRREvaluator evaluator = DocumentMRREvaluator() result = evaluator.run( ground_truth_documents=[ [Document(content="France")], [Document(content="9th century"), Document(content="9th")], ], retrieved_documents=[ [Document(content="France")], [Document(content="9th century"), Document(content="10th century"), Document(content="9th")], ], ) print(result["individual_scores"]) # [1.0, 1.0] print(result["score"]) # 1.0与文档描述一致,DocumentMRREvaluator不对其输入做归一化——文档的空白、大小写差异会导致漏匹配。官方建议先用DocumentCleaner组件清洗归一化后再送入该评测器。
3.3 DocumentMAPEvaluator:平均精度均值
MAP 同时衡量"是否召回"和"排得多靠前":对每个问题,沿检索列表扫描,每命中一个新标准答案就在该位置累加已命中相关数 / 当前排名,最后除以相关文档总数得到该问题的 Average Precision(AP);score为各问题 AP 的均值。
from haystack import Document from haystack.components.evaluators import DocumentMAPEvaluator evaluator = DocumentMAPEvaluator() result = evaluator.run( ground_truth_documents=[ [Document(content="France")], [Document(content="9th century"), Document(content="9th")], ], retrieved_documents=[ [Document(content="France")], [Document(content="9th century"), Document(content="10th century"), Document(content="9th")], ], ) print(result["individual_scores"]) # [1.0, 0.8333333333333333] print(result["score"]) # 0.9166666666666666以第二个问题验证:命中的排名为 1 和 3,AP = (1/1 + 2/3) / 2 = 0.8333。
源码(document_map.py)用一个"未记功的 ground truth 值列表"实现按值去重——同一相关文档重复检索只记一次功,防止 AP 被刷高;标准答案中比较值为None(如meta.键缺失)的文档不计入分母。该实现同样不对其输入做归一化,建议配合DocumentCleaner使用;且同样支持上文 3.1 所述的document_comparison_field配置(默认按content比较)。
3.4 DocumentNDCGEvaluator:归一化折损累积增益
NDCG 是四个文档指标中最精细的:它支持分级相关性——若标准答案文档带有score(相关性分数),计算直接使用这些分数;否则假定全部标准答案为二元相关(document_ndcg.py)。
from haystack import Document from haystack.components.evaluators import DocumentNDCGEvaluator evaluator = DocumentNDCGEvaluator() result = evaluator.run( ground_truth_documents=[[Document(content="France", score=1.0), Document(content="Paris", score=0.5)]], retrieved_documents=[[Document(content="France"), Document(content="Germany"), Document(content="Paris")]], ) print(result["individual_scores"]) # [0.8869] print(result["score"]) # 0.8869run的三个静态/实例辅助方法与校验规则:
validate_inputs(gt_docs, ret_docs)(静态方法):任一输入为空、两侧外层长度不一致,或某个问题的标准答案列表中混用了带score与不带score的文档(要求"全无或全有"),均抛ValueError;calculate_dcg(gt_docs, ret_docs):沿检索列表累加相关性 / log2(位置 + 1)(源码用i + 2补偿 0 起始下标,document_ndcg.py)。命中的相关值被pop出映射表,保证同一相关值至多记一次功,重复检索无法使 DCG 超过 IDCG;calculate_idcg(gt_docs):把标准答案的相关性按降序排列后累加同样的折损项。当idcg > 0时 NDCG = DCG / IDCG,否则记 0。
从源码结构看,_build_relevance_map会把比较值相同的多份标准答案折叠为"保留最高相关性"的单条,使 DCG 与 IDCG 对同一相关集计分一致(document_ndcg.py);同样支持document_comparison_field(content/id/meta.<key>嵌套键)。
3.5 检索指标选型小结
- 只关心"相关文档是否被召回" →
DocumentRecallEvaluator(multi_hit看召回比例,single_hit看是否命中); - 只关心"最相关的那篇排多靠前" →
DocumentMRREvaluator; - 关心"所有相关文档整体排得多靠前" →
DocumentMAPEvaluator; - 有分级相关性标注、想兼顾相关度与位置 →
DocumentNDCGEvaluator。
4. LLM 型评测器
4.1 LLMEvaluator:通用 LLM 评审基类
LLMEvaluator用 LLM 依据"指令 + few-shot 示例"评测任意列表输入。默认 API 要求环境变量OPENAI_API_KEY;输出为字典,键results对应一组结果,每个结果是一个字典,值取 0(FALSE)或 1(TRUE)。
from haystack.components.evaluators import LLMEvaluator evaluator = LLMEvaluator( instructions="Is this answer problematic for children?", inputs=[("predicted_answers", list[str])], outputs=["score"], examples=[ {"inputs": {"predicted_answers": "Damn, this is straight outta hell!!!"}, "outputs": {"score": 1}}, {"inputs": {"predicted_answers": "Football is the most popular sport."}, "outputs": {"score": 0}}, ], ) predicted_answers = [ "Football is the most popular sport with around 4 billion followers worldwide", "Python language was created by Guido van Rossum.", ] results = evaluator.run(predicted_answers=predicted_answers) print(results) # {'results': [{'score': 0}, {'score': 0}]}__init__参数:
| 参数 | 说明 |
|---|---|
instructions | 评测指令,应是能对输入用"是/否"式回答的问题 |
inputs | 输入声明列表,每项为(输入名, 列表类型)元组;类型必须是 list,这些名称同时成为组件的输入 socket |
outputs | 评测结果字典的键名列表 |
examples | few-shot 示例,每项为含"inputs"与"outputs"两个字典的字典 |
progress_bar | 评估时是否显示进度条(默认True) |
raise_on_failure | API 调用失败时是否抛异常(默认True,keyword-only) |
chat_generator | 自定义ChatGenerator实例;None时默认创建 JSON 模式的OpenAIChatGenerator |
关于chat_generator的重要约束:LLM 必须被配置为返回 JSON 对象。例如使用OpenAIChatGenerator时应在generation_kwargs传{"response_format": {"type": "json_object"}}。从源码看,默认生成器的generation_kwargs实际还固定了"seed": 42以保证评测可复现(llm_evaluator.py)。
run(**inputs)行为与返回:
- 校验:期望的输入名必须全部出现、所有输入必须是 list、各 list 长度一致,否则
validate_input_parameters抛ValueError; - 逐条输入渲染 prompt → 调用 chat generator → 用
_parse_dict_from_json解析并校验期望键。单条失败时,raise_on_failure=True抛ValueError,否则该条结果记为None并累计错误、最终输出 warning 统计(llm_evaluator.py); - 返回
{"results": [...]}。当 API 为 OpenAI 且响应带meta时,元数据(如 token 用量)随输出返回——当前仓库源码的@component.output_types已声明第二个输出meta(llm_evaluator.py),version 2.21 文档尚未体现这一点,实际仓库版本还新增了run_async、warm_up/close资源生命周期方法。
prepare_template生成的 prompt 模板格式(固定骨架):
Instructions: <instructions> Generate the response in JSON format with the following keys: <list of output keys> Consider the instructions and the examples below to determine those values. Examples: <examples> Inputs: <inputs> Outputs:inputs段由输入名拼成{"name": {{ name }}}的 jinja 占位符,最终经PromptBuilder渲染(llm_evaluator.py)。
序列化:to_dict/from_dict完整保存instructions、inputs(元组转为[name, 类型字符串]列表以便序列化)、outputs、examples与chat_generator(经component_to_dict连同生成器一起序列化);反序列化时恢复元组结构并就地反序列化 chat generator(llm_evaluator.py)。这意味着LLMEvaluator可作为 Pipeline 节点随管线整体持久化。
4.2 ContextRelevanceEvaluator:上下文相关性
继承自LLMEvaluator(context_relevance.py)。它让 LLM 把上下文拆分为多条陈述,逐条判断是否与问题相关:每个上下文得二值分 1/0,同时输出被判定相关的原文陈述(relevant_statements),并给出全部问题的平均分。
from haystack.components.evaluators import ContextRelevanceEvaluator questions = ["Who created the Python language?", "Why does Java needs a JVM?", "Is C++ better than Python?"] contexts = [ [( "Python, created by Guido van Rossum in the late 1980s, is a high-level general-purpose programming " "language. Its design philosophy emphasizes code readability, and its language constructs aim to help " "programmers write clear, logical code for both small and large-scale software projects." )], [( "Java is a high-level, class-based, object-oriented programming language that is designed to have as few " "implementation dependencies as possible. The JVM has two primary functions: to allow Java programs to run" "on any device or operating system (known as the 'write once, run anywhere' principle), and to manage and" "optimize program memory." )], [( "C++ is a general-purpose programming language created by Bjarne Stroustrup as an extension of the C " "programming language." )], ] evaluator = ContextRelevanceEvaluator() result = evaluator.run(questions=questions, contexts=contexts) print(result["score"]) # 0.67 print(result["individual_scores"]) # [1,1,0] print(result["results"]) # [{ # 'relevant_statements': ['Python, created by Guido van Rossum in the late 1980s.'], # 'score': 1.0 # }, # { # 'relevant_statements': ['The JVM has two primary functions: to allow Java programs to run on any device or # operating system (known as the "write once, run anywhere" principle), and to manage and # optimize program memory'], # 'score': 1.0 # }, # { # 'relevant_statements': [], # 'score': 0.0 # }]__init__参数:
examples:可选 few-shot 示例。每项须为含"inputs"和"outputs"的字典;"inputs"含"questions"与"contexts"键,"outputs"含"relevant_statements"键。不传时使用内置默认示例——源码中的_DEFAULT_EXAMPLES共 3 条,覆盖"单条相关陈述"与"多条无关上下文得空列表"两种情形(context_relevance.py),示例格式如下:
[{ "inputs": { "questions": "What is the capital of Italy?", "contexts": ["Rome is the capital of Italy."], }, "outputs": { "relevant_statements": ["Rome is the capital of Italy."], }, }]progress_bar(默认True):评估时显示进度条;raise_on_failure(默认True):API 调用失败时抛异常;chat_generator:自定义 LLM,同样必须配置为返回 JSON(OpenAIChatGenerator传{"response_format": {"type": "json_object"}}于generation_kwargs)。
run签名与输入/输出:
@component.output_types(score=float, results=list[dict[str, Any]]) def run(**inputs) -> dict[str, Any]- 输入:
questions(问题列表)、contexts(嵌套列表,每个内层列表对应一个问题的上下文); - 输出:
score(所有问题上下文相关性的平均分)、results(每个上下文的relevant_statements与score字典列表)。
辅助方法(继承自LLMEvaluator并被文档逐一列出):validate_init_parameters(静态方法,校验 inputs 为(str, list 类型)元组列表、outputs 为字符串列表、examples 为含inputs/outputs的字典列表)、validate_input_parameters(静态方法,校验期望输入齐全、输入均为 list 且等长)、is_valid_json_and_has_expected_keys(LLM 输出必须是含期望键的合法 JSON;raise_on_failure=True时违例抛ValueError,为False时发 warning 并返回False)。to_dict/from_dict提供组件级序列化。
从源码结构看,当前仓库版本中results的每个条目还附带status字段(如'status': 'evaluated',见 context_relevance.py 的 docstring 示例),version 2.21 文档未提及,以实际仓库版本行为为准。
4.3 FaithfulnessEvaluator:答案忠实度
评估生成答案能否从给定上下文中被推断出来:LLM 把答案拆成若干陈述,逐条判断是否可由上下文推出,单条得 0/1,答案总分 = 可推断陈述的比例(0.0–1.0),即衡量"无中生有"(幻觉)程度。
from haystack.components.evaluators import FaithfulnessEvaluator questions = ["Who created the Python language?"] contexts = [ [( "Python, created by Guido van Rossum in the late 1980s, is a high-level general-purpose programming " "language. Its design philosophy emphasizes code readability, and its language constructs aim to help " "programmers write clear, logical code for both small and large software projects." )], ] predicted_answers = [ "Python is a high-level general-purpose programming language that was created by George Lucas." ] evaluator = FaithfulnessEvaluator() result = evaluator.run(questions=questions, contexts=contexts, predicted_answers=predicted_answers) print(result["individual_scores"]) # [0.5] print(result["score"]) # 0.5 print(result["results"]) # [{'statements': ['Python is a high-level general-purpose programming language.', # 'Python was created by George Lucas.'], 'statement_scores': [1, 0], 'score': 0.5}]__init__参数与ContextRelevanceEvaluator完全同构(examples/progress_bar/raise_on_failure/chat_generator),区别在 few-shot 格式:"inputs"需含"questions"、"contexts"、"predicted_answers"三个键,"outputs"含"statements"与"statement_scores"两个键。官方示例格式:
[{ "inputs": { "questions": "What is the capital of Italy?", "contexts": ["Rome is the capital of Italy."], "predicted_answers": "Rome is the capital of Italy with more than 4 million inhabitants.", }, "outputs": { "statements": ["Rome is the capital of Italy.", "Rome has more than 4 million inhabitants."], "statement_scores": [1, 0], }, }]run签名与输出:
@component.output_types(individual_scores=list[int], score=float, results=list[dict[str, Any]]) def run(**inputs) -> dict[str, Any]- 输入:
questions、contexts(与问题对应的嵌套上下文列表)、predicted_answers; - 输出:
score(所有答案忠实度均分)、individual_scores(每个答案的忠实度分)、results(每个答案的statements与statement_scores)。
to_dict/from_dict、validate_init_parameters、validate_input_parameters、is_valid_json_and_has_expected_keys的语义与ContextRelevanceEvaluator相同(同为LLMEvaluator子类的共享契约),此处不再赘述。
5. 在 Pipeline 中使用与序列化
Evaluators 是标准@component,其run的输出 socket 由@component.output_types声明(如文档级评测器的score、individual_scores,LLM 评测器的results),因此可以直接连接在管线中——典型做法是把检索评测器接在 Retriever 输出之后、把忠实度评测器接在 ChatGenerator 之后,用离线数据集逐条对账。
所有文档级/答案级评测器与 LLM 型评测器都实现to_dict/from_dict(LLM 型评测器还会把chat_generator一并序列化)。这保证评测节点可随 Pipeline 一起持久化、再部署。
6. 关键行为约束速查
综合文档与源码,以下约束在写评测脚本时必须注意:
- 等长契约:所有
run都要求两侧外层列表等长,违反即抛ValueError(答案级与文档级评测器一致); - NDCG 的 score 一致性:同一问题的标准答案文档必须"全部带
score或全部不带",否则validate_inputs直接拒绝; - 文档级评测器不做归一化:MAP/MRR/Recall/NDCG 均按比较值(默认
doc.content)精确匹配,建议前置DocumentCleaner;当前仓库版本可通过document_comparison_field改为按id或meta键比较; - LLM 型评测器的 JSON 契约:自定义
chat_generator必须能输出 JSON 对象;解析失败的处理由raise_on_failure决定(抛异常或该条记None并 warning); - 依赖与密钥:默认 LLM 路径需要
OPENAI_API_KEY;SASEvaluator 需要sentence-transformers,私有模型需要HF_API_TOKEN/HF_TOKEN; - 失败隔离:
raise_on_failure=False时单条失败不会中断批量评测,适合大规模离线跑批(源码中失败条目计入errors并在日志中给出x / y失败比例统计,见 llm_evaluator.py)。
相关源码与测试可继续深入:各评测器实现位于 haystack/components/evaluators/,对应测试位于 test/components/evaluators/,本仓库版本 API 的完整文档位于 docs-website/reference_versioned_docs/version-2.21/haystack-api/evaluators_api.md。
【免费下载链接】haystackOpen-source AI orchestration framework for building context-engineered, production-ready LLM applications. Design modular pipelines and agent workflows with explicit control over retrieval, routing, memory, and generation. Built for scalable agents, RAG, multimodal applications, semantic search, and conversational systems.项目地址: https://gitcode.com/GitHub_Trending/ha/haystack
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考