Files
deepset-ai--haystack/test/components/evaluators/test_document_recall.py
T
wehub-resource-sync c56bef871b
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
Docker image release / Build base image (push) Has been cancelled
Sync docs with Docusaurus / sync (push) Has been cancelled
Tests / Check if changed (push) Has been cancelled
Tests / format (push) Has been cancelled
Tests / check-imports (push) Has been cancelled
Tests / Unit / macos-latest (push) Has been cancelled
Tests / Unit / ubuntu-latest (push) Has been cancelled
Tests / Unit / windows-latest (push) Has been cancelled
Tests / mypy (push) Has been cancelled
Tests / Integration / ubuntu-latest (push) Has been cancelled
Tests / Integration / macos-latest (push) Has been cancelled
Tests / Integration / windows-latest (push) Has been cancelled
Tests / notify-slack-on-failure (push) Has been cancelled
Tests / Mark tests as completed (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

268 lines
12 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
import pytest
from haystack import default_from_dict
from haystack.components.evaluators.document_recall import DocumentRecallEvaluator, RecallMode
from haystack.dataclasses import Document
def test_init_with_unknown_mode_string():
with pytest.raises(ValueError):
DocumentRecallEvaluator(mode="unknown_mode")
def test_init_with_string_mode():
evaluator = DocumentRecallEvaluator(mode="single_hit")
assert evaluator.mode == RecallMode.SINGLE_HIT
evaluator = DocumentRecallEvaluator(mode="multi_hit")
assert evaluator.mode == RecallMode.MULTI_HIT
def test_init_default_comparison_field():
evaluator = DocumentRecallEvaluator()
assert evaluator.document_comparison_field == "content"
def test_run_with_id_comparison():
evaluator = DocumentRecallEvaluator(mode=RecallMode.SINGLE_HIT, document_comparison_field="id")
result = evaluator.run(
ground_truth_documents=[[Document(id="doc1", content="foo")], [Document(id="doc2", content="bar")]],
retrieved_documents=[[Document(id="doc1", content="different")], [Document(id="wrong", content="bar")]],
)
assert result == {"individual_scores": [1.0, 0.0], "score": 0.5}
def test_run_with_meta_comparison():
evaluator = DocumentRecallEvaluator(mode=RecallMode.MULTI_HIT, document_comparison_field="meta.file_id")
result = evaluator.run(
ground_truth_documents=[
[Document(content="x", meta={"file_id": "a"}), Document(content="y", meta={"file_id": "b"})]
],
retrieved_documents=[
[Document(content="z", meta={"file_id": "a"}), Document(content="w", meta={"file_id": "c"})]
],
)
assert result == {"individual_scores": [0.5], "score": 0.5}
def test_run_with_nested_meta_comparison():
evaluator = DocumentRecallEvaluator(mode=RecallMode.MULTI_HIT, document_comparison_field="meta.source.url")
result = evaluator.run(
ground_truth_documents=[
[
Document(content="x", meta={"source": {"url": "https://a.com"}}),
Document(content="y", meta={"source": {"url": "https://b.com"}}),
]
],
retrieved_documents=[
[
Document(content="z", meta={"source": {"url": "https://a.com"}}),
Document(content="w", meta={"source": {"url": "https://c.com"}}),
]
],
)
assert result == {"individual_scores": [0.5], "score": 0.5}
class TestDocumentRecallEvaluatorSingleHit:
@pytest.fixture
def evaluator(self):
return DocumentRecallEvaluator(mode=RecallMode.SINGLE_HIT)
def test_run_with_all_matching(self, evaluator):
result = evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [1.0, 1.0], "score": 1.0}
def test_run_with_no_matching(self, evaluator):
result = evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Paris")], [Document(content="London")]],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [0.0, 0.0], "score": 0.0}
def test_run_with_partial_matching(self, evaluator):
result = evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Berlin")], [Document(content="London")]],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [1.0, 0.0], "score": 0.5}
def test_run_with_complex_data(self, evaluator):
result = evaluator.run(
ground_truth_documents=[
[Document(content="France")],
[Document(content="9th century"), Document(content="9th")],
[Document(content="classical music"), Document(content="classical")],
[Document(content="11th century"), Document(content="the 11th")],
[Document(content="Denmark, Iceland and Norway")],
[Document(content="10th century"), Document(content="10th")],
],
retrieved_documents=[
[Document(content="France")],
[Document(content="9th century"), Document(content="10th century"), Document(content="9th")],
[Document(content="classical"), Document(content="rock music"), Document(content="dubstep")],
[Document(content="11th"), Document(content="the 11th"), Document(content="11th century")],
[Document(content="Denmark"), Document(content="Norway"), Document(content="Iceland")],
[
Document(content="10th century"),
Document(content="the first half of the 10th century"),
Document(content="10th"),
Document(content="10th"),
],
],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [1, 1, 1, 1, 0, 1], "score": 0.8333333333333334}
def test_run_with_different_lengths(self, evaluator):
with pytest.raises(ValueError):
evaluator.run(
ground_truth_documents=[[Document(content="Berlin")]],
retrieved_documents=[[Document(content="Berlin")], [Document(content="London")]],
)
with pytest.raises(ValueError):
evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Berlin")]],
)
def test_to_dict(self, evaluator):
data = evaluator.to_dict()
assert data == {
"type": "haystack.components.evaluators.document_recall.DocumentRecallEvaluator",
"init_parameters": {"mode": "single_hit", "document_comparison_field": "content"},
}
def test_from_dict(self):
data = {
"type": "haystack.components.evaluators.document_recall.DocumentRecallEvaluator",
"init_parameters": {"mode": "single_hit"},
}
new_evaluator = default_from_dict(DocumentRecallEvaluator, data)
assert new_evaluator.mode == RecallMode.SINGLE_HIT
class TestDocumentRecallEvaluatorMultiHit:
@pytest.fixture
def evaluator(self):
return DocumentRecallEvaluator(mode=RecallMode.MULTI_HIT)
def test_run_with_all_matching(self, evaluator):
result = evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [1.0, 1.0], "score": 1.0}
def test_run_with_no_matching(self, evaluator):
result = evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Paris")], [Document(content="London")]],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [0.0, 0.0], "score": 0.0}
def test_run_with_partial_matching(self, evaluator):
result = evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Berlin")], [Document(content="London")]],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [1.0, 0.0], "score": 0.5}
def test_run_with_complex_data(self, evaluator):
result = evaluator.run(
ground_truth_documents=[
[Document(content="France")],
[Document(content="9th century"), Document(content="9th")],
[Document(content="classical music"), Document(content="classical")],
[Document(content="11th century"), Document(content="the 11th")],
[
Document(content="Denmark"),
Document(content="Iceland"),
Document(content="Norway"),
Document(content="Denmark, Iceland and Norway"),
],
[Document(content="10th century"), Document(content="10th")],
],
retrieved_documents=[
[Document(content="France")],
[Document(content="9th century"), Document(content="10th century"), Document(content="9th")],
[Document(content="classical"), Document(content="rock music"), Document(content="dubstep")],
[Document(content="11th"), Document(content="the 11th"), Document(content="11th century")],
[Document(content="Denmark"), Document(content="Norway"), Document(content="Iceland")],
[
Document(content="10th century"),
Document(content="the first half of the 10th century"),
Document(content="10th"),
Document(content="10th"),
],
],
)
assert all(isinstance(individual_score, float) for individual_score in result["individual_scores"])
assert result == {"individual_scores": [1.0, 1.0, 0.5, 1.0, 0.75, 1.0], "score": 0.875}
def test_run_with_different_lengths(self, evaluator):
with pytest.raises(ValueError):
evaluator.run(
ground_truth_documents=[[Document(content="Berlin")]],
retrieved_documents=[[Document(content="Berlin")], [Document(content="London")]],
)
with pytest.raises(ValueError):
evaluator.run(
ground_truth_documents=[[Document(content="Berlin")], [Document(content="Paris")]],
retrieved_documents=[[Document(content="Berlin")]],
)
def test_to_dict(self, evaluator):
data = evaluator.to_dict()
assert data == {
"type": "haystack.components.evaluators.document_recall.DocumentRecallEvaluator",
"init_parameters": {"mode": "multi_hit", "document_comparison_field": "content"},
}
def test_from_dict(self):
data = {
"type": "haystack.components.evaluators.document_recall.DocumentRecallEvaluator",
"init_parameters": {"mode": "multi_hit"},
}
new_evaluator = default_from_dict(DocumentRecallEvaluator, data)
assert new_evaluator.mode == RecallMode.MULTI_HIT
def test_empty_ground_truth_documents(self, evaluator):
ground_truth_documents = [[]]
retrieved_documents = [[Document(content="test")]]
score = evaluator.run(ground_truth_documents, retrieved_documents)
assert score == {"individual_scores": [0.0], "score": 0.0}
def test_empty_retrieved_documents(self, evaluator):
ground_truth_documents = [[Document(content="test")]]
retrieved_documents = [[]]
score = evaluator.run(ground_truth_documents, retrieved_documents)
assert score == {"individual_scores": [0.0], "score": 0.0}
def test_empty_string_ground_truth_documents(self, evaluator):
ground_truth_documents = [[Document(content="")]]
retrieved_documents = [[Document(content="test")]]
score = evaluator.run(ground_truth_documents, retrieved_documents)
assert score == {"individual_scores": [0.0], "score": 0.0}
def test_empty_string_retrieved_documents(self, evaluator):
ground_truth_documents = [[Document(content="test")]]
retrieved_documents = [[Document(content="")]]
score = evaluator.run(ground_truth_documents, retrieved_documents)
assert score == {"individual_scores": [0.0], "score": 0.0}