deepset-ai/haystack · error · ValueError
Assistant message must contain one text part at most.
Error message
Assistant message must contain one text part at most.
What it means
Assistant messages built from a template may include at most one TextContent part (alongside tool calls and reasoning). This error means two or more text parts were produced for one assistant message.
Solutions
- Merge the text into a single part: join strings before rendering or use {{ text_a ~ text_b }}.
- Insert only one text ChatMessage into the assistant block, or pre-merge messages.
- Split into multiple assistant ChatMessages if multiple texts are genuinely needed.
- Move extra text into tool call arguments or a following user/system message as appropriate.
Example fix
// before
{% insert %}{{ assistant_msg }}{% endinsert %}Extra text
// after
{{ assistant_msg.text ~ "\nExtra text" }} // single text part Defensive patterns
Strategy: validation
Validate before calling
from haystack.dataclasses import ChatMessage, TextContent
def validate_assistant_text_count(msg: ChatMessage):
texts = [p for p in msg.content_parts if isinstance(p, TextContent)]
if len(texts) > 1:
raise TypeError("Assistant message has more than one text part") Type guard
def has_at_most_one_text(msg: ChatMessage) -> bool:
return sum(isinstance(p, TextContent) for p in msg.content_parts) <= 1 Try / catch
try:
messages = renderer.run(template=tpl, variables=vars)["messages"]
except ValueError as e:
if "one text part at most" in str(e):
log.error("Assistant block produced multiple text parts; merge before rendering")
raise Prevention
- Join multiple assistant texts into one string before rendering.
- Insert at most one text ChatMessage per assistant block.
- Avoid emitting text both directly and via {% insert %} in the same block.
- Test templates that combine static text with inserted assistant messages.
When it happens
Trigger: An assistant block containing multiple text segments and/or multiple {% insert %} calls each yielding a text part; inserting a list of assistant messages into a single assistant message block.
Common situations: Templates that emit answer text plus an inserted explanation message into the same assistant turn; loops inside assistant blocks; converting multi-part assistant outputs from other frameworks.
Related errors
- Assistant message must contain at least one text or tool…
- Assistant message must contain only text, tool call or…
- System message must contain a text part.
- System message must contain only one text part.
- Tool message must contain only one tool call result.
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/da1088427a1854a3.
Report an issue: GitHub.
Appendix: source
Thrown at haystack/utils/jinja2_chat_extension.py:381
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.")
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].resultView on GitHub (pinned to e318778c9b)