Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 18 additions & 4 deletions haystack/components/rankers/meta_field_grouping_ranker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@

from collections import defaultdict

from haystack import Document, component
from haystack import Document, component, logging
from haystack.utils.misc import _deduplicate_documents

logger = logging.getLogger(__name__)


@component
class MetaFieldGroupingRanker:
Expand Down Expand Up @@ -117,9 +119,21 @@ def run(self, documents: list[Document]) -> dict[str, list[Document]]:
for docs in subgroups.values():
if sort_field:
# Sort by the field value, placing documents with a missing value last.
# The (is_missing, value) tuple ensures that only actual field values are
# compared, making the sort work for numbers, strings, and other types.
docs.sort(key=lambda d: (d.meta.get(sort_field) is None, d.meta.get(sort_field)))
# The (is_missing, value) tuple keeps documents with a missing value out of the
# value comparison, but two present values of mutually non-comparable types
# (e.g. an int and a str) would still raise a TypeError. In that case we keep the
# group's insertion order instead of crashing, mirroring MetaFieldRanker.
try:
docs.sort(key=lambda d: (d.meta.get(sort_field) is None, d.meta.get(sort_field)))
except TypeError as error:
logger.warning(
"Tried to sort Documents with IDs {document_ids}, but got TypeError with the "
"message: {error}\nKeeping the original order of the Documents in this group "
"since sorting by '{sort_field}' is not possible.",
document_ids=",".join([doc.id for doc in docs]),
error=error,
sort_field=sort_field,
)
ordered_docs.extend(docs)

ordered_docs.extend(no_group_docs)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
fixes:
- |
Fixed ``MetaFieldGroupingRanker`` crashing with an uncaught ``TypeError`` when a group contained
``sort_docs_by`` values of mutually non-comparable types (for example an ``int`` and a ``str``). The
sort is now wrapped in a ``try``/``except``, so in that case the group's original insertion order is
kept and a warning is logged, mirroring the behavior of ``MetaFieldRanker``.
17 changes: 17 additions & 0 deletions test/components/rankers/test_meta_field_grouping_ranker.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,23 @@ def test_run_metadata_with_different_data_types(self) -> None:
assert result["documents"][1].meta["group"] == 42
assert result["documents"][2].meta["group"] is True

def test_run_sort_docs_by_mixed_uncomparable_types(self) -> None:
"""
Test that the ranker does not crash when a group's sort_docs_by values have mutually
non-comparable present types (e.g. int and str), keeping the group's insertion order instead.
"""
docs_with_mixed_sort_values = [
Document(content="int value", meta={"group": "g1", "split_id": 3}),
Document(content="str value", meta={"group": "g1", "split_id": "10"}),
]
sample_ranker = MetaFieldGroupingRanker(group_by="group", sort_docs_by="split_id")
result = sample_ranker.run(documents=docs_with_mixed_sort_values)
assert "documents" in result
assert len(result["documents"]) == 2
# Insertion order is preserved because the values cannot be compared.
assert result["documents"][0].content == "int value"
assert result["documents"][1].content == "str value"

def test_run_deduplicates_documents(self) -> None:
"""
Test that duplicate documents are removed before grouping.
Expand Down
Loading