deepset-ai/haystack · error · ValueError
Assistant message must contain only text, tool call or reaso
Error message
Assistant message must contain only text, tool call or reasoning parts.
What it means
Assistant messages built from a template may only contain text, tool call, and reasoning parts. This error means some other content type (e.g. ImageContent, FileContent, ToolCallResult) appeared in an assistant message block.
Source
Thrown at haystack/utils/jinja2_chat_extension.py:385
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
return ChatMessage.from_tool(meta=meta, tool_result=tool_result, origin=origin, error=error)View on GitHub (pinned to e318778c9b)
Solutions
- Remove image/file/tool-result parts from assistant messages in the template.
- Place tool results in 'tool' role messages and images in 'user' role messages.
- If inserting history with {% insert %}, verify each message's role matches its parts.
- Convert unsupported parts to text before inserting into an assistant message.
Example fix
// before
assistant parts: [TextContent("ok"), ToolCallResult(result="42")]
// after
assistant parts: [TextContent("ok")] // ToolCallResult goes in a tool message Defensive patterns
Strategy: type-guard
Validate before calling
from haystack.dataclasses import ChatMessage, TextContent, ToolCall, ReasoningContent
def validate_assistant_parts(msg: ChatMessage):
bad = [p for p in msg.content_parts if not isinstance(p, (TextContent, ToolCall, ReasoningContent))]
if bad:
raise TypeError(f"Assistant message has invalid parts: {[type(p).__name__ for p in bad]}") Type guard
def is_valid_assistant_part(p) -> bool:
from haystack.dataclasses import TextContent, ToolCall, ReasoningContent
return isinstance(p, (TextContent, ToolCall, ReasoningContent)) Try / catch
try:
messages = renderer.run(template=tpl, variables=vars)["messages"]
except ValueError as e:
if "only text, tool call or reasoning" in str(e):
log.error("Foreign content part in assistant message; check inserted messages' roles")
raise Prevention
- Only insert assistant-role messages into assistant blocks.
- Route images to user messages and tool results to tool messages.
- Audit history-replay templates for role/part mismatches.
- Add tests covering multimodal and tool-heavy templates.
When it happens
Trigger: Inserting a user/tool message (carrying image or tool-result parts) into an assistant message block; mixing content part types across roles via {% insert %}; custom/unknown content part objects passed through the template.
Common situations: Replaying multimodal history where image parts got attributed to assistant turns; inserting tool results into assistant messages instead of tool messages; framework conversions that don't remap part types per role.
Related errors
- Assistant message must contain one text part at most.
- Assistant message must contain at least one text or tool cal
- User message must contain only TextContent, string, ImageCon
- System message must contain a text part.
- System message must contain only one text part.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/b5baa3efc3b39040.
Report an issue: GitHub.