deepset-ai/haystack · error · ValueError

Assistant message must contain at least one text or tool cal

Error message

Assistant message must contain at least one text or tool call part.

What it means

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.

Source

Thrown at haystack/utils/jinja2_chat_extension.py:383

                )
            return ChatMessage.from_user(meta=meta, name=name, content_parts=valid_parts)

        if role == "system":
            if not isinstance(parts[0], TextContent):
                raise ValueError("System message must contain a text part.")
            text = parts[0].text
            if len(parts) > 1:
                raise ValueError("System message must contain only one text part.")
            return ChatMessage.from_system(meta=meta, name=name, text=text)

        if role == "assistant":
            texts = [part.text for part in parts if isinstance(part, TextContent)]
            tool_calls = [part for part in parts if isinstance(part, ToolCall)]
            reasoning = [part for part in parts if isinstance(part, ReasoningContent)]
            if len(texts) > 1:
                raise ValueError("Assistant message must contain one text part at most.")
            if len(texts) == 0 and len(tool_calls) == 0:
                raise ValueError("Assistant message must contain at least one text or tool call part.")
            if len(parts) > len(texts) + len(tool_calls) + len(reasoning):
                raise ValueError("Assistant message must contain only text, tool call or reasoning parts.")
            return ChatMessage.from_assistant(
                meta=meta,
                name=name,
                text=texts[0] if texts else None,
                tool_calls=tool_calls or None,
                reasoning=reasoning[0] if reasoning else None,
            )

        if role == "tool":
            tool_call_results = [part for part in parts if isinstance(part, ToolCallResult)]
            if len(tool_call_results) == 0 or len(tool_call_results) > 1 or len(parts) > len(tool_call_results):
                raise ValueError("Tool message must contain only one tool call result.")

            tool_result = tool_call_results[0].result
            origin = tool_call_results[0].origin
            error = tool_call_results[0].error

View on GitHub (pinned to e318778c9b)

Solutions

  1. Ensure the assistant block produces at least one tool call or text part, e.g. guard empty variables with Jinja2 defaults.
  2. Check the variable holding tool calls is populated and is a list of ToolCall objects.
  3. Add a fallback text: {{ text or "(empty response)" }} inside the assistant block.
  4. Skip rendering the assistant message entirely when there is no content ({% if ... %}).

Example fix

// before
{% if reasoning %}{{ reasoning }}{% endif %}  // only reasoning
// after
{{ text or "(no answer)" }}{{ tool_calls_json }}  // ensures text or tool call
Defensive patterns

Strategy: validation

Validate before calling

from haystack.dataclasses import ChatMessage, TextContent, ToolCall

def validate_assistant_nonempty(msg: ChatMessage):
    has_text = any(isinstance(p, TextContent) for p in msg.content_parts)
    has_calls = any(isinstance(p, ToolCall) for p in msg.content_parts)
    if not (has_text or has_calls):
        raise TypeError("Assistant message needs at least one text or tool call part")

Type guard

def assistant_has_content(msg: ChatMessage) -> bool:
    from haystack.dataclasses import TextContent, ToolCall
    return any(isinstance(p, (TextContent, ToolCall)) for p in msg.content_parts)

Try / catch

try:
    messages = renderer.run(template=tpl, variables=vars)["messages"]
except ValueError as e:
    if "at least one text or tool call" in str(e):
        log.error("Assistant block empty; check tool_calls/text variables")
    raise

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30). Data as JSON: /api/errors/448173416330e397. Report an issue: GitHub.