{"record":{"id":"448173416330e397","repo":"deepset-ai/haystack","slug":"assistant-message-must-contain-at-least-one-text-o","errorCode":null,"errorMessage":"Assistant message must contain at least one text or tool call part.","messagePattern":"Assistant message must contain at least one text or tool call part\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"haystack/utils/jinja2_chat_extension.py","lineNumber":383,"sourceCode":"                )\n            return ChatMessage.from_user(meta=meta, name=name, content_parts=valid_parts)\n\n        if role == \"system\":\n            if not isinstance(parts[0], TextContent):\n                raise ValueError(\"System message must contain a text part.\")\n            text = parts[0].text\n            if len(parts) > 1:\n                raise ValueError(\"System message must contain only one text part.\")\n            return ChatMessage.from_system(meta=meta, name=name, text=text)\n\n        if role == \"assistant\":\n            texts = [part.text for part in parts if isinstance(part, TextContent)]\n            tool_calls = [part for part in parts if isinstance(part, ToolCall)]\n            reasoning = [part for part in parts if isinstance(part, ReasoningContent)]\n            if len(texts) > 1:\n                raise ValueError(\"Assistant message must contain one text part at most.\")\n            if len(texts) == 0 and len(tool_calls) == 0:\n                raise ValueError(\"Assistant message must contain at least one text or tool call part.\")\n            if len(parts) > len(texts) + len(tool_calls) + len(reasoning):\n                raise ValueError(\"Assistant message must contain only text, tool call or reasoning parts.\")\n            return ChatMessage.from_assistant(\n                meta=meta,\n                name=name,\n                text=texts[0] if texts else None,\n                tool_calls=tool_calls or None,\n                reasoning=reasoning[0] if reasoning else None,\n            )\n\n        if role == \"tool\":\n            tool_call_results = [part for part in parts if isinstance(part, ToolCallResult)]\n            if len(tool_call_results) == 0 or len(tool_call_results) > 1 or len(parts) > len(tool_call_results):\n                raise ValueError(\"Tool message must contain only one tool call result.\")\n\n            tool_result = tool_call_results[0].result\n            origin = tool_call_results[0].origin\n            error = tool_call_results[0].error","sourceCodeStart":365,"sourceCodeEnd":401,"githubUrl":"https://github.com/deepset-ai/haystack/blob/e318778c9bf60a1963e3b5f451359655dd696c30/haystack/utils/jinja2_chat_extension.py#L365-L401","documentation":"An assistant message built from a template ended up with no text and no tool call parts (only reasoning or nothing at all), so there is no valid content to construct the assistant ChatMessage from.","triggerScenarios":"An assistant block whose Jinja2 expressions all evaluate to empty (e.g. empty tool_calls variable and empty text); a conditional that emits only reasoning content; inserting messages that are not assistant text/tool-call messages.","commonSituations":"Optional LLM output variables that came back empty; templates expecting tool calls from a variable that was None; rendering assistant turns from an agent run that produced only reasoning.","solutions":["Ensure the assistant block produces at least one tool call or text part, e.g. guard empty variables with Jinja2 defaults.","Check the variable holding tool calls is populated and is a list of ToolCall objects.","Add a fallback text: {{ text or \"(empty response)\" }} inside the assistant block.","Skip rendering the assistant message entirely when there is no content ({% if ... %})."],"exampleFix":"// before\n{% if reasoning %}{{ reasoning }}{% endif %}  // only reasoning\n// after\n{{ text or \"(no answer)\" }}{{ tool_calls_json }}  // ensures text or tool call","handlingStrategy":"validation","validationCode":"from haystack.dataclasses import ChatMessage, TextContent, ToolCall\n\ndef validate_assistant_nonempty(msg: ChatMessage):\n    has_text = any(isinstance(p, TextContent) for p in msg.content_parts)\n    has_calls = any(isinstance(p, ToolCall) for p in msg.content_parts)\n    if not (has_text or has_calls):\n        raise TypeError(\"Assistant message needs at least one text or tool call part\")","typeGuard":"def assistant_has_content(msg: ChatMessage) -> bool:\n    from haystack.dataclasses import TextContent, ToolCall\n    return any(isinstance(p, (TextContent, ToolCall)) for p in msg.content_parts)","tryCatchPattern":"try:\n    messages = renderer.run(template=tpl, variables=vars)[\"messages\"]\nexcept ValueError as e:\n    if \"at least one text or tool call\" in str(e):\n        log.error(\"Assistant block empty; check tool_calls/text variables\")\n    raise","preventionTips":["Guard LLM output variables with Jinja2 fallbacks: {{ text or \"...\" }}.","Verify tool_calls variables are populated lists of ToolCall before rendering.","Wrap assistant blocks in {% if %} so empty turns are skipped.","Check upstream LLM/agent outputs for empty responses in tests."],"tags":["chat-template","message-validation","assistant-message"],"backgroundTag":"invalid-message-content-part","analyzedSha":"e318778c9bf60a1963e3b5f451359655dd696c30","analyzedAt":"2026-08-30T11:45:20.711Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}