Retriever changes alter which evidence reaches a search result or RAG prompt, and aggregate evaluation makes those changes measurable across a labeled query set. Haystack can calculate recall and mean reciprocal rank from captured retrieval results before a new retriever configuration is adopted.
Haystack's DocumentRecallEvaluator reports whether each query retrieved a labeled document in its default single-hit mode. DocumentMRREvaluator also considers the first relevant document's position, so a match at rank two receives a lower reciprocal-rank score than a match at rank one.
Ground-truth and retrieved document lists must use the same query order. Comparing stable Document.id values avoids treating harmless content edits as different labels, while retaining the retriever's original result order preserves the ranking signal measured by MRR.
Related: How to run a pipeline in Haystack
Related: How to set retriever top_k in Haystack
from haystack import Document, Pipeline from haystack.components.evaluators import DocumentMRREvaluator, DocumentRecallEvaluator def document(document_id: str) -> Document: return Document(id=document_id) ground_truth_documents = [ [document("refund-policy")], [document("support-hours")], [document("shipping-region")], ] retrieved_documents = [ [document("refund-policy"), document("support-hours")], [document("shipping-region"), document("support-hours")], [document("refund-policy"), document("support-hours")], ]
The retrieved_documents value represents saved output from the retriever under evaluation. Its document order remains unchanged, and both outer lists use the same query order.
evaluation_pipeline = Pipeline() evaluation_pipeline.add_component( "recall", DocumentRecallEvaluator(document_comparison_field="id") )
The default single_hit mode scores a query as 1.0 when any labeled document is retrieved. The multi_hit mode instead reflects how many labeled documents were retrieved for each query.
evaluation_pipeline.add_component( "mrr", DocumentMRREvaluator(document_comparison_field="id") )
evaluator_inputs = { "ground_truth_documents": ground_truth_documents, "retrieved_documents": retrieved_documents, } results = evaluation_pipeline.run( {"recall": evaluator_inputs, "mrr": evaluator_inputs} )
print("recall by query:", results["recall"]["individual_scores"]) print("MRR by query:", results["mrr"]["individual_scores"]) print(f"mean recall: {results['recall']['score']:.2f}") print(f"mean MRR: {results['mrr']['score']:.2f}")
$ python retrieval_evaluation.py recall by query: [1.0, 1.0, 0.0] MRR by query: [1.0, 0.5, 0.0] mean recall: 0.67 mean MRR: 0.50
The second query receives full recall because its labeled document was retrieved, but its MRR is 0.5 because that document appears at rank two. Both scores are 0.0 for the third query because its label is absent.