Skip to content

DocumentRecallEvaluator and DocumentNDCGEvaluator crash on list/dict meta comparison values; MRR and MAP accept them #13200

Closed

Description

@pcbeingused333

Describe the bug

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

from haystack import Document
from haystack.components.evaluators import (
    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"]})]]

for cls in (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"])
    except TypeError as e:
        print(cls.__name__, "TypeError:", e)

Output on main (c5e1335):

DocumentMRREvaluator 1.0
DocumentMAPEvaluator 1.0
DocumentRecallEvaluator TypeError: unhashable type: 'list'
DocumentNDCGEvaluator TypeError: unhashable type: 'list'

Additional context

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.

FAQ Check

System:

  • OS: Linux (WSL2)
  • Haystack version: main at c5e1335 (3.4.0-rc0)

Activity

  1. TemitopeKadri commented on Oct 11, 2026

    @TemitopeKadri
    Contributor

    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.

  2. pcbeingused333 commented on Oct 11, 2026

    @pcbeingused333
    ContributorAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions