deepset-ai/haystack · error · ValueError

Tool message must contain only one tool call result.

Error message

Tool message must contain only one tool call result.

What it means

A tool-role message built from a template must contain exactly one ToolCallResult part and nothing else. This error fires when the tool message has zero tool call results, more than one, or contains additional parts.

Source

Thrown at haystack/utils/jinja2_chat_extension.py:397

            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

            return ChatMessage.from_tool(meta=meta, tool_result=tool_result, origin=origin, error=error)

        raise ValueError(f"Unsupported role: {role}")


@pass_environment
def templatize_part(environment: Any, value: ChatMessageContentT) -> "_TemplatizedPart":
    """
    Jinja filter to convert a ChatMessageContentT object into a JSON string wrapped in sentinel content tags.

    :param environment: The Jinja2 environment
    :param value: The ChatMessageContentT object to convert
    :return: A `_TemplatizedPart` holding a JSON string wrapped in special XML content tags

View on GitHub (pinned to e318778c9b)

Solutions

  1. Insert exactly one ToolCallResult message per tool message block via {% insert %}.
  2. Split multiple tool results into separate tool messages, one per result.
  3. Remove extra text or other parts from tool messages; put explanations in user/assistant messages.
  4. Verify the variable inserted holds a single ChatMessage with role 'tool', not a list.

Example fix

// before
{% insert %}{{ tool_results }}{% endinsert %}  // list of 2 results
// after
{% for r in tool_results %}tool message with {% insert %}{{ r }}{% endinsert %}{% endfor %}  // one per message
Defensive patterns

Strategy: validation

Validate before calling

from haystack.dataclasses import ChatMessage, ToolCallResult

def validate_tool_message(msg: ChatMessage):
    results = [p for p in msg.content_parts if isinstance(p, ToolCallResult)]
    if len(results) != 1 or len(msg.content_parts) != 1:
        raise TypeError("Tool message must contain exactly one ToolCallResult part")

Type guard

def is_single_tool_result(msg: ChatMessage) -> bool:
    parts = msg.content_parts
    return len(parts) == 1 and isinstance(parts[0], ToolCallResult)

Try / catch

try:
    messages = renderer.run(template=tpl, variables=vars)["messages"]
except ValueError as e:
    if "Tool message must contain only one tool call result" in str(e):
        log.error("Tool block invalid; ensure one ToolCallResult per tool message")
    raise

Prevention

When it happens

Trigger: A tool message block with no {% insert %} of a ToolCallResult; inserting a list of multiple tool results into one tool message; mixing tool results with text parts in the same tool message.

Common situations: Batching several tool outputs into a single tool turn; forgetting to insert the ToolCallResult and only writing text; converting agent frameworks where one tool turn carries multiple results.

Related errors


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