deepset-ai/haystack · error · ValueError

user_prompt message block must have role 'user', found role

Error message

user_prompt message block must have role 'user', found role '{roles[0]}'.

What it means

Raised when the first Jinja2 message block in user_prompt has a role other than 'user'. The Agent pipeline routes user_prompt through a builder expecting a user-role message, so any other role is rejected at init time.

Source

Thrown at haystack/components/agents/utils.py:257

# Regex to extract the role from a Jinja2 message block, e.g. {% message role="user" %}
_JINJA2_MESSAGE_ROLE_RE = re.compile(r'\{%\s*message\s+role\s*=\s*["\'](\w+)["\']')


def _validate_prompt_message_blocks(user_prompt: str | None, system_prompt: str | None) -> None:
    """
    Validate explicit Jinja2 message blocks in Agent prompts.

    :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.
    """

View on GitHub (pinned to e318778c9b)

Solutions

  1. Change the message block role in user_prompt to 'user'.
  2. Move system-role content into system_prompt instead.
  3. Verify the user_prompt/system_prompt arguments were not swapped.

Example fix

// before
user_prompt = "{% message role='system' %}You are helpful.{% endmessage %}"
// after
system_prompt = "{% message role='system' %}You are helpful.{% endmessage %}"
user_prompt = "{% message role='user' %}{{ query }}{% endmessage %}"
Defensive patterns

Strategy: validation

Validate before calling

roles = re.findall(r"{\%\s*message\s+role=['\"](\w+)['\"]", user_prompt)
assert not roles or roles[0] == "user", f"user_prompt role is {roles[0] if roles else None}"

Try / catch

try:
    agent = Agent(gen, user_prompt=prompt)
except ValueError as e:
    if "role 'user'" in str(e):
        logging.error("user_prompt wrong role: %s", e)
    raise

Prevention

When it happens

Trigger: Passing user_prompt like "{% message role='system' %}...{% endmessage %}" or role='assistant' to Agent's __init__.

Common situations: Copy-pasting a system message template into user_prompt; swapping user_prompt and system_prompt arguments; editing roles in a template without checking where it is used.

Related errors


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