deepset-ai/haystack · error · ValueError

For OpenAI compatibility, a `ChatMessage` with a `ToolCallRe

Error message

For OpenAI compatibility, a `ChatMessage` with a `ToolCallResult` cannot contain any other content.

What it means

The OpenAI Responses API models tool results as standalone function_call_output items with no mixed content. haystack therefore rejects ChatMessages that contain a ToolCallResult together with any other content part (except a single one).

Source

Thrown at haystack/components/generators/chat/openai_responses.py:1001

                "filename": part.filename or "filename",
                "file_data": f"data:{part.mime_type or 'application/pdf'};base64,{part.base64_data}",
            }
        raise ValueError(f"Unsupported content type: {type(part)}")

    text_contents = message.texts
    tool_calls = message.tool_calls
    tool_call_results = message.tool_call_results
    images = message.images
    reasonings = message.reasonings
    files = message.files

    if not any([text_contents, tool_calls, tool_call_results, images, reasonings, files]):
        raise ValueError(
            """A `ChatMessage` must contain at least one `TextContent`, `ToolCall`, `ToolCallResult`,
              `ImageContent`, `FileContent`, or `ReasoningContent`."""
        )
    if len(tool_call_results) > 0 and len(message._content) > 1:
        raise ValueError(
            "For OpenAI compatibility, a `ChatMessage` with a `ToolCallResult` cannot contain any other content."
        )

    formatted_messages: list[dict[str, Any]] = []
    openai_msg: dict[str, Any] = {"role": message._role.value}
    if message._name is not None:
        openai_msg["name"] = message._name

    # user message
    if message._role.value == "user":
        content = [convert_part(part) for part in message._content]
        openai_msg["content"] = content
        return [openai_msg]

    # tool message
    if tool_call_results:
        formatted_tool_results = []
        for result in tool_call_results:

View on GitHub (pinned to e318778c9b)

Solutions

  1. Put the ToolCallResult in its own ChatMessage with no other content
  2. Move accompanying text into a separate assistant/user message
  3. Rebuild the message: ChatMessage from only the ToolCallResult content

Example fix

// before
msg = ChatMessage(_role=role, _content=[tool_result, TextContent(text="done")])
// after
msg_tool = ChatMessage(_role=role, _content=[tool_result])
msg_text = ChatMessage.from_user("done")
Defensive patterns

Strategy: validation

Validate before calling

from haystack.dataclasses import ToolCallResult
for m in messages:
    if any(isinstance(p, ToolCallResult) for p in m._content) and len(m._content) > 1:
        raise ValueError("ToolCallResult must be alone in its message")

Type guard

def is_tool_result_only(m) -> bool:
    from haystack.dataclasses import ToolCallResult
    return len(m._content) == 1 and isinstance(m._content[0], ToolCallResult)

Try / catch

try:
    gen.run(messages=messages)
except ValueError as e:
    if "ToolCallResult" in str(e):
        messages = split_tool_results_into_own_messages(messages)

Prevention

When it happens

Trigger: Appending a ToolCallResult to a message that also has TextContent/ImageContent/etc., then passing it to OpenAIResponsesChatGenerator.

Common situations: Merging tool output and commentary in one message (a habit from other APIs like Anthropic or openai chat completions where tool role messages can mix).

Related errors


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