You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
DocumentRecallEvaluator and DocumentNDCGEvaluator crash on list/dict meta comparison values; MRR and MAP accept them #13200
With document_comparison_field set to a meta.<key> whose value is a list or dict, DocumentRecallEvaluator and DocumentNDCGEvaluator crash with TypeError: unhashable type, while DocumentMRREvaluator and DocumentMAPEvaluator score the same input correctly.
Recall collects the comparison values into sets (_unique_comparison_values) and NDCG keys a dict by them (_build_relevance_map), so both need hashable values. MRR and MAP compare with in against a list, which works for any value with ==. List-valued metadata is common (source ids, tags, the _split_overlap that DocumentSplitter writes), so the four evaluators accept different inputs for the same document_comparison_field.
Error message
File "haystack/components/evaluators/document_ndcg.py", line 88, in _build_relevance_map
TypeError: unhashable type: 'list'
(dict values give unhashable type: 'dict'; for Recall the error comes from the set comprehension in _unique_comparison_values.)
Expected behavior
All four evaluators handle the same document_comparison_field values. Here, every one should score 1.0, like MRR and MAP already do.
To Reproduce
fromhaystackimportDocumentfromhaystack.components.evaluatorsimport (
DocumentMAPEvaluator,
DocumentMRREvaluator,
DocumentNDCGEvaluator,
DocumentRecallEvaluator,
)
ground_truth= [[Document(content="Paris is the capital of France.", meta={"source_ids": ["wiki-1", "wiki-2"]})]]
retrieved= [[Document(content="Paris is the capital of France.", meta={"source_ids": ["wiki-1", "wiki-2"]})]]
forclsin (DocumentMRREvaluator, DocumentMAPEvaluator, DocumentRecallEvaluator, DocumentNDCGEvaluator):
evaluator=cls(document_comparison_field="meta.source_ids")
try:
print(cls.__name__, evaluator.run(ground_truth_documents=ground_truth, retrieved_documents=retrieved)["score"])
exceptTypeErrorase:
print(cls.__name__, "TypeError:", e)
A small fix is to normalize unhashable comparison values to a hashable key in Recall and NDCG (e.g. recursively converting lists to tuples and dicts to sorted item tuples) before building the set / relevance map, leaving the equality semantics MRR and MAP already use unchanged. I'm happy to open a PR with regression tests for list and dict values in both evaluators if that approach works for you.
Thanks for the excellent report — I hit the same crash with list-valued meta and reproduced it locally.
I've opened #13214 implementing the normalization approach you suggested: both evaluators now convert the extracted comparison value through a small _make_hashable helper (lists/tuples → tuples, dicts/sets → frozensets) at the _get_comparison_value choke point, so equality semantics match what MRR and MAP already use. It includes regression tests for list- and dict-valued meta in both evaluators plus a release note.
Since you mentioned you might open a PR yourself: happy to iterate on this one, or close it in favor of yours — whichever you and the maintainers prefer.
Thanks @TemitopeKadri, #13214 covers it, so I'll leave the PR to you rather than open a competing one.
I ran the tests from my local branch against #13214 and they all pass (list and dict values, Recall and NDCG). Two small notes, neither blocking:
_make_hashable is duplicated in document_recall.py and document_ndcg.py. It could live in one place.
Converting lists to tuples makes ["a"] and ("a",) compare equal, while MRR/MAP keep them distinct. That's probably fine in practice, but worth knowing.
Also, the CLA check is still pending on the PR, so it'll need signing before it can be merged.
Activity
TemitopeKadri commentedon Oct 11, 2026
Thanks for the excellent report — I hit the same crash with list-valued
metaand reproduced it locally.I've opened #13214 implementing the normalization approach you suggested: both evaluators now convert the extracted comparison value through a small
_make_hashablehelper (lists/tuples → tuples, dicts/sets → frozensets) at the_get_comparison_valuechoke point, so equality semantics match what MRR and MAP already use. It includes regression tests for list- and dict-valued meta in both evaluators plus a release note.Since you mentioned you might open a PR yourself: happy to iterate on this one, or close it in favor of yours — whichever you and the maintainers prefer.
pcbeingused333 commentedon Oct 11, 2026
Thanks @TemitopeKadri, #13214 covers it, so I'll leave the PR to you rather than open a competing one.
I ran the tests from my local branch against #13214 and they all pass (list and dict values, Recall and NDCG). Two small notes, neither blocking:
_make_hashableis duplicated indocument_recall.pyanddocument_ndcg.py. It could live in one place.["a"]and("a",)compare equal, while MRR/MAP keep them distinct. That's probably fine in practice, but worth knowing.Also, the CLA check is still pending on the PR, so it'll need signing before it can be merged.