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

  1. Change the message block role in system_prompt to 'system'.
  2. Move user-role content into user_prompt.
  3. 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

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


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