deepset-ai/haystack · error · ValueError

user_prompt must define exactly one message block, found {le

Error message

user_prompt must define exactly one message block, found {len(message_blocks)}.

What it means

Raised by _validate_prompt_message_blocks when a user_prompt string contains more than one Jinja2 message block (e.g. multiple {% message %} tags). The Agent expects each prompt to define exactly one message block so it can map cleanly to a single ChatMessage.

Source

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

# Regex to detect the Jinja2 chat template syntax
_JINJA2_CHAT_TEMPLATE_RE = re.compile(r"\{%\s*message\s")
# 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.

View on GitHub (pinned to e318778c9b)

Solutions

  1. Keep only a single {% message %} block with role 'user' in user_prompt.
  2. Move any additional message blocks into system_prompt (for the system turn) or handle prior turns via conversation history.
  3. If dynamic rendering is needed, use Jinja variables inside one message block instead of multiple blocks.

Example fix

// before
user_prompt = "{% message role='user' %}Hi{% endmessage %}{% message role='user' %}Bye{% endmessage %}"
// after
user_prompt = "{% message role='user' %}Hi{% endmessage %}"
Defensive patterns

Strategy: validation

Validate before calling

import re
_JINJA2_CHAT_TEMPLATE_RE = re.compile(r"{\%\s*message\s+.*?\%}.*?{\%\s*endmessage\s*\%}", re.DOTALL)
blocks = _JINJA2_CHAT_TEMPLATE_RE.findall(user_prompt)
assert len(blocks) <= 1, f"user_prompt has {len(blocks)} message blocks"

Try / catch

try:
    agent = Agent(gen, user_prompt=prompt)
except ValueError as e:
    if "exactly one message block" in str(e):
        logging.error("Prompt template malformed: %s", e)
    raise

Prevention

When it happens

Trigger: Passing a user_prompt to Agent's __init__ whose string contains two or more {% message %}...{% endmessage %} blocks, e.g. a template covering both user and assistant turns.

Common situations: Copying a full chat template into user_prompt; concatenating multiple message snippets; migrating a whole chat renderer template instead of just the user turn.

Related errors


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