deepset-ai/haystack · error · ValueError
system_prompt message block must have role 'system', found r
Error message
system_prompt message block must have role 'system', found role '{roles[0]}'. What it means
Raised when the first (only) message block in system_prompt has a role other than 'system'. The Agent expects the system prompt to render to a system-role ChatMessage.
Source
Thrown at haystack/components/agents/utils.py:265
:param user_prompt: Optional user prompt template.
:param system_prompt: Optional system prompt template.
:raises ValueError: If a prompt contains multiple message blocks or a literal block role is invalid.
"""
if user_prompt is not None:
message_blocks = _JINJA2_CHAT_TEMPLATE_RE.findall(user_prompt)
roles = _JINJA2_MESSAGE_ROLE_RE.findall(user_prompt)
if len(message_blocks) > 1:
raise ValueError(f"user_prompt must define exactly one message block, found {len(message_blocks)}.")
if roles and roles[0] != "user":
raise ValueError(f"user_prompt message block must have role 'user', found role '{roles[0]}'.")
if system_prompt is not None and _JINJA2_CHAT_TEMPLATE_RE.search(system_prompt):
message_blocks = _JINJA2_CHAT_TEMPLATE_RE.findall(system_prompt)
roles = _JINJA2_MESSAGE_ROLE_RE.findall(system_prompt)
if len(message_blocks) > 1:
raise ValueError(f"system_prompt must define exactly one message block, found {len(message_blocks)}.")
if roles and roles[0] != "system":
raise ValueError(f"system_prompt message block must have role 'system', found role '{roles[0]}'.")
def _template_for_role(prompt: str, role: str) -> str:
"""
Convert a prompt into a ChatPromptBuilder string template for the expected role.
:param prompt: Prompt template, with or without an explicit Jinja2 message block.
:param role: Role to use when wrapping a plain string prompt.
:returns: The original message-block template, or a plain string prompt wrapped in one message block.
"""
if _JINJA2_CHAT_TEMPLATE_RE.search(prompt):
return prompt
return f'{{% message role="{role}" %}}{prompt}{{% endmessage %}}'
def _render_prompt_messages(
*, prompt_builder: ChatPromptBuilder, expected_role: ChatRole, prompt_label: str, kwargs: dict[str, Any]
) -> list[ChatMessage]:View on GitHub (pinned to e318778c9b)
Solutions
- Change the message block role in system_prompt to 'system'.
- Move user-role content into user_prompt.
- Double-check argument order when constructing Agent to avoid swapping the two prompts.
Example fix
// before
system_prompt = "{% message role='user' %}{{ query }}{% endmessage %}"
// after
system_prompt = "{% message role='system' %}You are a helpful assistant.{% endmessage %}" Defensive patterns
Strategy: validation
Validate before calling
roles = re.findall(r"{\%\s*message\s+role=['\"](\w+)['\"]", system_prompt)
assert not roles or roles[0] == "system", f"system_prompt role is {roles[0] if roles else None}" Try / catch
try:
agent = Agent(gen, system_prompt=prompt)
except ValueError as e:
if "role 'system'" in str(e):
logging.error("system_prompt wrong role: %s", e)
raise Prevention
- Hardcode role='system' inside system_prompt templates.
- Do not pass user-turn templates as system_prompt.
- Verify prompts after refactoring template variables.
When it happens
Trigger: Passing system_prompt like "{% message role='user' %}...{% endmessage %}" to Agent's __init__.
Common situations: Swapping user_prompt and system_prompt arguments; using an instruction template with role='user' as the system prompt.
Related errors
- user_prompt message block must have role 'user', found role
- {prompt_label} must render to a {expected_role.value} messag
- user_prompt must define exactly one message block, found {le
- system_prompt must define exactly one message block, found {
- {prompt_label} must render to exactly one {expected_role.val
AI-assisted analysis of deepset-ai/haystack@e318778c9b (2026-08-30).
Data as JSON: /api/errors/cd7ddc405928bb18.
Report an issue: GitHub.