diff --git a/haystack/components/builders/answer_builder.py b/haystack/components/builders/answer_builder.py index 78d3ed08d23..a25d3f25ccc 100644 --- a/haystack/components/builders/answer_builder.py +++ b/haystack/components/builders/answer_builder.py @@ -223,13 +223,14 @@ def run( ) for idx in doc_idxs: - try: - doc = documents[idx] - except IndexError: + # An explicit bounds check is needed because references are 1-based: a reference like [0] + # yields idx = -1, which Python would otherwise silently resolve to the last document. + if not 0 <= idx < len(documents): logger.warning( "Document index '{index}' referenced in Generator output is out of range. ", index=idx + 1 ) continue + doc = documents[idx] doc_meta: dict[str, Any] = dict(doc.meta or {}) doc_meta["source_index"] = idx + 1 diff --git a/releasenotes/notes/answer-builder-zero-reference-out-of-range-64aab56f8b5f65c3.yaml b/releasenotes/notes/answer-builder-zero-reference-out-of-range-64aab56f8b5f65c3.yaml new file mode 100644 index 00000000000..db8512d1fd7 --- /dev/null +++ b/releasenotes/notes/answer-builder-zero-reference-out-of-range-64aab56f8b5f65c3.yaml @@ -0,0 +1,7 @@ +--- +fixes: + - | + AnswerBuilder now treats a ``[0]`` document reference as out of range instead of silently attaching + the last document. References are 1-based, so ``[0]`` produced a negative index that bypassed the + out-of-range guard, marking an unrelated document as ``referenced`` with a wrong ``source_index``. + The same applied to expanded reference ranges starting at 0 (e.g. ``[0-1]``). diff --git a/test/components/builders/test_answer_builder.py b/test/components/builders/test_answer_builder.py index e5bc20d3d92..dfe847b5300 100644 --- a/test/components/builders/test_answer_builder.py +++ b/test/components/builders/test_answer_builder.py @@ -186,6 +186,38 @@ def test_run_with_documents_with_reference_pattern_and_no_match(self, caplog): assert len(answers[0].documents) == 0 assert "Document index '3' referenced in Generator output is out of range." in caplog.text + def test_run_with_documents_with_zero_reference(self, caplog): + # References are 1-based, so [0] is out of range and must not silently + # resolve to the last document via a negative index. + component = AnswerBuilder(reference_pattern="\\[(\\d+)\\]") + with caplog.at_level(logging.WARNING): + output = component.run( + query="test query", + replies=["Answer: AnswerString[0]"], + meta=[{}], + documents=[Document(content="test doc 1"), Document(content="test doc 2")], + ) + answers = output["answers"] + assert len(answers) == 1 + assert len(answers[0].documents) == 0 + assert "Document index '0' referenced in Generator output is out of range." in caplog.text + + def test_run_with_zero_reference_in_expanded_range(self, caplog): + component = AnswerBuilder(reference_pattern="\\[([\\d\\s,-]+)\\]", expand_reference_ranges=True) + with caplog.at_level(logging.WARNING): + output = component.run( + query="test query", + replies=["Answer: AnswerString[0-1]"], + meta=[{}], + documents=[Document(content="test doc 1"), Document(content="test doc 2")], + ) + answers = output["answers"] + assert len(answers) == 1 + assert len(answers[0].documents) == 1 + assert answers[0].documents[0].content == "test doc 1" + assert answers[0].documents[0].meta["referenced"] is True + assert "Document index '0' referenced in Generator output is out of range." in caplog.text + def test_run_with_reference_pattern_set_at_runtime(self): component = AnswerBuilder(reference_pattern="unused pattern") output = component.run(