From 58d3455a2ef08fe32f543f0079199508fb5bb690 Mon Sep 17 00:00:00 2001 From: sclfcz Date: Fri, 25 Sep 2026 23:39:54 +0800 Subject: [PATCH 1/2] fix(MetaFieldRanker): apply missing_meta when no document has the field When every document lacks the ranking field, run() returned the input documents unchanged, bypassing the configured missing_meta policy: a batch with one rated document dropped the unrated ones, while an entirely unrated batch passed through intact even with missing_meta="drop". Apply the policy in that branch as well (drop -> empty list, top/bottom keep the documents, as before) and keep the warning that explains the situation. --- haystack/components/rankers/meta_field.py | 4 +++- test/components/rankers/test_metafield.py | 19 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/haystack/components/rankers/meta_field.py b/haystack/components/rankers/meta_field.py index b5ce8923942..cde5d5efbaa 100644 --- a/haystack/components/rankers/meta_field.py +++ b/haystack/components/rankers/meta_field.py @@ -264,10 +264,12 @@ def run( "The parameter is currently set to '{meta_field}', but none of the provided " "Documents with IDs {document_ids} have this meta key.\n" "Set to the name of a field that is present within the provided Documents.\n" - "Returning the of the original Documents since there are no values to rank.", + "Applying the configured policy instead of ranking.", meta_field=self.meta_field, document_ids=",".join([doc.id for doc in deduplicated_documents]), ) + if self.missing_meta == "drop": + return {"documents": []} return {"documents": deduplicated_documents[:top_k]} if len(docs_missing_meta_field) > 0: diff --git a/test/components/rankers/test_metafield.py b/test/components/rankers/test_metafield.py index a998ab4cb5b..e65dffb700d 100644 --- a/test/components/rankers/test_metafield.py +++ b/test/components/rankers/test_metafield.py @@ -338,3 +338,22 @@ def test_none_meta_value_is_handled_as_missing_meta(self, missing_meta, expected ] output = ranker.run(documents=docs_before) assert [doc.id for doc in output["documents"]] == expected_ids + + +def test_missing_meta_drop_when_no_document_has_the_field(): + """The missing_meta policy applies even when no document has the field. + + The ranker returned every input document in that case, so + missing_meta="drop" silently stopped filtering: a batch with no rated + document kept the unrated ones. + """ + docs = [Document(id="a", content="a"), Document(id="b", content="b")] + + assert ( + MetaFieldRanker(meta_field="rating", missing_meta="drop").run(docs)["documents"] + == [] + ) + + for policy in ("bottom", "top"): + ranker = MetaFieldRanker(meta_field="rating", missing_meta=policy) + assert [d.id for d in ranker.run(docs)["documents"]] == ["a", "b"] From f7694023a8c68278e16893e5badcfe6ad2d6f36b Mon Sep 17 00:00:00 2001 From: anakin87 Date: Mon, 28 Sep 2026 14:59:39 +0200 Subject: [PATCH 2/2] fixes --- haystack/components/rankers/meta_field.py | 3 +- ...-ranker-missing-meta-99197f6d6faaf8e3.yaml | 5 +++ test/components/rankers/test_metafield.py | 32 +++++++++---------- 3 files changed, 22 insertions(+), 18 deletions(-) create mode 100644 releasenotes/notes/fix-metafield-ranker-missing-meta-99197f6d6faaf8e3.yaml diff --git a/haystack/components/rankers/meta_field.py b/haystack/components/rankers/meta_field.py index cde5d5efbaa..c3b3475560a 100644 --- a/haystack/components/rankers/meta_field.py +++ b/haystack/components/rankers/meta_field.py @@ -258,7 +258,6 @@ def run( docs_with_meta_field = [doc for doc in deduplicated_documents if doc.meta.get(self.meta_field) is not None] docs_missing_meta_field = [doc for doc in deduplicated_documents if doc.meta.get(self.meta_field) is None] - # If all docs are missing self.meta_field return original documents if len(docs_with_meta_field) == 0: logger.warning( "The parameter is currently set to '{meta_field}', but none of the provided " @@ -268,7 +267,7 @@ def run( meta_field=self.meta_field, document_ids=",".join([doc.id for doc in deduplicated_documents]), ) - if self.missing_meta == "drop": + if missing_meta == "drop": return {"documents": []} return {"documents": deduplicated_documents[:top_k]} diff --git a/releasenotes/notes/fix-metafield-ranker-missing-meta-99197f6d6faaf8e3.yaml b/releasenotes/notes/fix-metafield-ranker-missing-meta-99197f6d6faaf8e3.yaml new file mode 100644 index 00000000000..e1e1f7b4646 --- /dev/null +++ b/releasenotes/notes/fix-metafield-ranker-missing-meta-99197f6d6faaf8e3.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + Fix ``MetaFieldRanker`` to return no documents when ``missing_meta="drop"`` + and all documents lack the ranking field or have a ``None`` value. diff --git a/test/components/rankers/test_metafield.py b/test/components/rankers/test_metafield.py index e65dffb700d..6e25f871c89 100644 --- a/test/components/rankers/test_metafield.py +++ b/test/components/rankers/test_metafield.py @@ -339,21 +339,21 @@ def test_none_meta_value_is_handled_as_missing_meta(self, missing_meta, expected output = ranker.run(documents=docs_before) assert [doc.id for doc in output["documents"]] == expected_ids - -def test_missing_meta_drop_when_no_document_has_the_field(): - """The missing_meta policy applies even when no document has the field. - - The ranker returned every input document in that case, so - missing_meta="drop" silently stopped filtering: a batch with no rated - document kept the unrated ones. - """ - docs = [Document(id="a", content="a"), Document(id="b", content="b")] - - assert ( - MetaFieldRanker(meta_field="rating", missing_meta="drop").run(docs)["documents"] - == [] + @pytest.mark.parametrize( + ("meta", "init_missing_meta", "run_missing_meta", "expected_ids"), + [ + ({}, "drop", None, []), + ({}, "top", None, ["a", "b"]), + ({}, "bottom", None, ["a", "b"]), + ({}, "bottom", "drop", []), + ({}, "drop", "top", ["a", "b"]), + ({"rating": None}, "drop", None, []), + ], ) + def test_missing_meta_when_all_values_are_missing(self, meta, init_missing_meta, run_missing_meta, expected_ids): + ranker = MetaFieldRanker(meta_field="rating", missing_meta=init_missing_meta) + docs = [Document(id="a", content="a", meta=meta), Document(id="b", content="b", meta=meta)] + + output = ranker.run(documents=docs, missing_meta=run_missing_meta) - for policy in ("bottom", "top"): - ranker = MetaFieldRanker(meta_field="rating", missing_meta=policy) - assert [d.id for d in ranker.run(docs)["documents"]] == ["a", "b"] + assert [doc.id for doc in output["documents"]] == expected_ids