deepset-ai/haystack · error · ValueError

Message template produced content that couldn't be parsed…

Error message

Message template produced content that couldn't be parsed into any message parts. Content: {content!r}

What it means

ValueError raised by `_build_chat_message_json` when a {% message %} template body renders to content that, after splitting on the internal sentinel tags, yields no parseable message parts. This means the template produced nothing recognizable (empty or only whitespace/non-part text) for the message.

Solutions

  1. Ensure the message body always renders at least one content part (text, image link, or tool part sentinel)
  2. Guard empty variables with a Jinja default filter: {{ optional | default('fallback text') }}
  3. Check that custom preprocessing/postprocessing does not strip or alter the sentinel tags produced by the extension

Example fix

// before
{% message role='user' %}{{ maybe_empty }}{% endmessage %}
// after
{% message role='user' %}{{ maybe_empty | default('No question provided') }}{% endmessage %}
Defensive patterns

Strategy: try-catch

Validate before calling

def message_body_is_renderable(template_body: str) -> bool:
    return bool(template_body.strip())

Type guard

null

Try / catch

try:
    messages = rendered_chat_prompt  # runs _build_chat_message_json internally
except ValueError as e:
    if "couldn't be parsed into any message parts" in str(e):
        messages = [fallback_message]
    else:
        raise

Prevention

When it happens

Trigger: Rendering a {% message %} block whose body is empty or evaluates to an empty string (e.g. `{% message role='user' %}{{ optional }}{% endmessage %}` where optional is '' or None), or content that escapes the sentinel tagging so no parts match.

Common situations: Conditionally empty variables in the template body; templates where all content is inside {% if %} that evaluates false; overriding/sanitization that strips the sentinel tags so parsing finds no parts.

Related errors


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

Appendix: source

Thrown at haystack/utils/jinja2_chat_extension.py:242

        """
        Build a ChatMessage object from template content and serialize it to a JSON string.

        This method is called by Jinja2 when processing a `{% message %}` tag.
        It takes the rendered content from the template, converts XML blocks into ChatMessageContentT objects,
        creates a ChatMessage object and serializes it to a JSON string.

        :param role: The role of the message
        :param name: Optional name for the message sender
        :param meta: Optional metadata dictionary
        :param caller: Callable that returns the rendered content
        :return: A JSON string representation of the ChatMessage object
        """

        content = caller()
        start_tag, end_tag = _sentinel_tags(getattr(self.environment, _NONCE_ATTR))
        parts = self._parse_content_parts(content, start_tag, end_tag)
        if not parts:
            raise ValueError(
                f"Message template produced content that couldn't be parsed into any message parts. "
                f"Content: {_redact_nonce(content, start_tag, end_tag)!r}"
            )

        chat_message = self._validate_build_chat_message(parts=parts, role=role, meta=meta, name=name)

        return json.dumps(chat_message.to_dict()) + "\n"

    def _build_inserted_messages_json(
        self,
        messages: list[ChatMessage] | ChatMessage,
        caller: Callable[[], str],  # noqa: ARG002
    ) -> str:
        """
        Expand a list of ChatMessage objects into newline-separated JSON, one message per line.

        This method is called by Jinja2 when processing an `{% insert %}` tag. It produces the same JSON-line format
        as `_build_chat_message_json`, so the messages are parsed back into ChatMessage objects by the

View on GitHub (pinned to e318778c9b)