diff --git a/haystack/components/routers/llm_messages_router.py b/haystack/components/routers/llm_messages_router.py index a47a7771614..498a12fbadc 100644 --- a/haystack/components/routers/llm_messages_router.py +++ b/haystack/components/routers/llm_messages_router.py @@ -16,6 +16,15 @@ _RESERVED_OUTPUT_NAMES = ("chat_generator_text", "unmatched") +def _chat_generator_text(generator_result: dict[str, Any]) -> str: + """Return the first reply text from a chat generator, or an empty string if none is available.""" + replies = generator_result.get("replies") or [] + if not replies: + return "" + text = getattr(replies[0], "text", None) + return text if isinstance(text, str) else "" + + @component class LLMMessagesRouter: """ @@ -158,7 +167,7 @@ def run(self, messages: list[ChatMessage]) -> dict[str, str | list[ChatMessage]] with _trace_chat_generator_run(self._chat_generator, {"messages": messages_for_inference}) as span: generator_result = self._chat_generator.run(messages=messages_for_inference) span.set_content_tag("haystack.component.output", generator_result) - chat_generator_text = generator_result["replies"][0].text + chat_generator_text = _chat_generator_text(generator_result) output = {"chat_generator_text": chat_generator_text} @@ -207,7 +216,7 @@ async def run_async(self, messages: list[ChatMessage]) -> dict[str, str | list[C with _trace_chat_generator_run(self._chat_generator, {"messages": messages_for_inference}) as span: generator_result = await _execute_component_async(self._chat_generator, messages=messages_for_inference) span.set_content_tag("haystack.component.output", generator_result) - chat_generator_text = generator_result["replies"][0].text + chat_generator_text = _chat_generator_text(generator_result) output = {"chat_generator_text": chat_generator_text} diff --git a/releasenotes/notes/fix-llm-messages-router-empty-reply-a9c6d15e4b802f37.yaml b/releasenotes/notes/fix-llm-messages-router-empty-reply-a9c6d15e4b802f37.yaml new file mode 100644 index 00000000000..22c7e0eb27d --- /dev/null +++ b/releasenotes/notes/fix-llm-messages-router-empty-reply-a9c6d15e4b802f37.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + ``LLMMessagesRouter`` now routes messages to ``unmatched`` when the chat generator returns no replies + or a reply with no text, instead of raising ``IndexError`` or ``TypeError``. diff --git a/test/components/routers/test_llm_messages_router.py b/test/components/routers/test_llm_messages_router.py index 4feac3fb625..a477f252682 100644 --- a/test/components/routers/test_llm_messages_router.py +++ b/test/components/routers/test_llm_messages_router.py @@ -74,6 +74,30 @@ def test_run_input_errors(self): with pytest.raises(ValueError): router.run([ChatMessage.from_system("You are a helpful assistant.")]) + def test_run_empty_replies_routes_unmatched(self): + chat_generator = Mock(spec=["run"]) + chat_generator.run.return_value = {"replies": []} + router = LLMMessagesRouter( + chat_generator=chat_generator, output_names=["safe", "unsafe"], output_patterns=["safe", "unsafe"] + ) + messages = [ChatMessage.from_user("Hello")] + result = router.run(messages) + assert result["chat_generator_text"] == "" + assert result["unmatched"] == messages + assert "safe" not in result + assert "unsafe" not in result + + def test_run_none_reply_text_routes_unmatched(self): + chat_generator = Mock(spec=["run"]) + chat_generator.run.return_value = {"replies": [ChatMessage.from_assistant(text=None)]} + router = LLMMessagesRouter( + chat_generator=chat_generator, output_names=["safe", "unsafe"], output_patterns=["safe", "unsafe"] + ) + messages = [ChatMessage.from_user("Hello")] + result = router.run(messages) + assert result["chat_generator_text"] == "" + assert result["unmatched"] == messages + def test_run_no_warm_up_with_unwarmable_chat_generator(self): chat_generator = Mock(spec=["run"]) chat_generator.run.return_value = {"replies": [ChatMessage.from_assistant("safe")]}