deepset-ai/haystack · error · ValueError

system_prompt must define exactly one message block, found {

Error message

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

What it means

Raised when a system_prompt passed to Agent contains more than one Jinja2 message block. Like user_prompt, the system prompt must render to exactly one message so the Agent can build a valid chat.

Source

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

    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.
    """
    if _JINJA2_CHAT_TEMPLATE_RE.search(prompt):
        return prompt
    return f'{{% message role="{role}" %}}{prompt}{{% endmessage %}}'


def _render_prompt_messages(

View on GitHub (pinned to e318778c9b)

Solutions

  1. Reduce system_prompt to a single {% message role='system' %} block.
  2. Put user-turn content in user_prompt instead of system_prompt.
  3. Merge multiple system instructions into one message block separated by newlines.

Example fix

// before
system_prompt = "{% message role='system' %}A{% endmessage %}{% message role='system' %}B{% endmessage %}"
// after
system_prompt = "{% message role='system' %}A\nB{% endmessage %}"
Defensive patterns

Strategy: validation

Validate before calling

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

Try / catch

try:
    agent = Agent(gen, system_prompt=prompt)
except ValueError as e:
    if "system_prompt must define exactly one" in str(e):
        logging.error("system_prompt malformed: %s", e)
    raise

Prevention

When it happens

Trigger: Passing a system_prompt with two or more {% message %} blocks, e.g. a template containing both system and user turns.

Common situations: Reusing a full chat template as system_prompt; appending extra message blocks for examples; merging two system prompts by concatenation.

Related errors


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