deepset-ai/haystack · error · ValueError
User message must contain only TextContent, string, ImageCon
Error message
User message must contain only TextContent, string, ImageContent or FileContent parts.
What it means
A user-role message built from a chat template contained a content part that is not allowed for the user role. Only TextContent, plain strings, ImageContent, and FileContent are valid user parts; anything else (e.g. ToolCall, ToolCallResult, ReasoningContent) is rejected.
Source
Thrown at haystack/utils/jinja2_chat_extension.py:363
parts: list[ChatMessageContentT], role: str, meta: dict, name: str | None = None
) -> ChatMessage:
"""
Validate the parts of a chat message and build a ChatMessage object.
:param parts: Content parts of the message
:param role: The role of the message
:param meta: The metadata of the message
:param name: The optional name of the message
:return: A ChatMessage object
:raises ValueError: If content parts don't allow to build a valid ChatMessage object or the role is not
supported
"""
if role == "user":
valid_parts = [part for part in parts if isinstance(part, (TextContent, str, ImageContent, FileContent))]
if len(parts) != len(valid_parts):
raise ValueError(
"User message must contain only TextContent, string, ImageContent or FileContent parts."
)
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.")View on GitHub (pinned to e318778c9b)
Solutions
- Remove non-text/image/file parts from the user message in the template.
- Put tool call results in a 'tool' role message instead of a user message.
- If inserting mixed history with {% insert %}, ensure each inserted message has the correct role and parts.
- Convert unsupported parts to TextContent, e.g. render tool results as text.
Example fix
// before
user parts = [TextContent("hi"), ToolCall(id="1", ...)]
// after
user parts = [TextContent("hi")] // move ToolCall to assistant message Defensive patterns
Strategy: type-guard
Validate before calling
from haystack.dataclasses import ChatMessage, TextContent, ImageContent, FileContent
def validate_user_parts(msg: ChatMessage):
bad = [p for p in msg.content_parts if not isinstance(p, (TextContent, str, ImageContent, FileContent))]
if bad:
raise TypeError(f"User message has invalid parts: {[type(p).__name__ for p in bad]}") Type guard
def is_valid_user_part(p) -> bool:
from haystack.dataclasses import TextContent, ImageContent, FileContent
return isinstance(p, (TextContent, str, ImageContent, FileContent)) Try / catch
try:
messages = renderer.run(template=tpl, variables=vars)["messages"]
except ValueError as e:
if "User message must contain only" in str(e):
log.error("Non-user-role part in user message; inspect inserted messages")
raise Prevention
- Only insert user-role ChatMessages into user message blocks.
- Render tool results as text before placing them in user prompts.
- Check role/part compatibility when replaying conversation history.
- Assert part types in tests for every template path.
When it happens
Trigger: Inserting a ChatMessage with tool-call parts into a user message block via {% insert %}; serializing an assistant/tool message's parts into a user message; a custom content part type passed through the template.
Common situations: Trying to replay tool calls inside a user turn; converting conversation history where roles got misassigned; inserting document objects instead of their textual content into a user message.
Related errors
- System message must contain a text part.
- System message must contain only one text part.
- Assistant message must contain one text part at most.
- Assistant message must contain at least one text or tool cal
- Assistant message must contain only text, tool call or reaso
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/a80f3c13cf6a5ca6.
Report an issue: GitHub.