run-llama/llama_index · error · ValueError

ChatMessage contains multiple blocks, use 'ChatMessage.block

Error message

ChatMessage contains multiple blocks, use 'ChatMessage.blocks' instead.

What it means

Raised by ChatMessage's backward-compat content setter when assigning content while blocks already contains multiple blocks, or a single non-TextBlock block. Setting .content is only allowed when blocks is empty or exactly one TextBlock; otherwise content would silently discard the other blocks.

Source

Thrown at llama-index-core/llama_index/core/base/llms/types.py:1233

        if ct is None and len(content_strs) == 1:
            return ""
        return ct

    @content.setter
    def content(self, content: str) -> None:
        """
        Keeps backward compatibility with the old `content` field.

        Raises:
            ValueError: if blocks contains more than a block, or a block that's not TextBlock.

        """
        if not self.blocks:
            self.blocks = [TextBlock(text=content)]
        elif len(self.blocks) == 1 and isinstance(self.blocks[0], TextBlock):
            self.blocks = [TextBlock(text=content)]
        else:
            raise ValueError(
                "ChatMessage contains multiple blocks, use 'ChatMessage.blocks' instead."
            )

    def __str__(self) -> str:
        return f"{self.role.value}: {self.content}"

    @classmethod
    def from_str(
        cls,
        content: str,
        role: Union[MessageRole, str] = MessageRole.USER,
        **kwargs: Any,
    ) -> Self:
        if isinstance(role, str):
            role = MessageRole(role)
        return cls(role=role, blocks=[TextBlock(text=content)], **kwargs)

    def _recursive_serialization(self, value: Any) -> Any:

View on GitHub (pinned to afd0fef371)

Solutions

  1. To update text on a multimodal message, replace the blocks list: msg.blocks = [TextBlock(text="new"), *msg.blocks[1:]].
  2. Construct with blocks only (no content kwarg) when using multiple or non-text blocks.
  3. For text-only messages use ChatMessage.from_str(content, role) which cannot hit this path.

Example fix

# before
msg = ChatMessage(blocks=[TextBlock(text="hi"), ImageBlock(image=img_bytes)], role=MessageRole.USER)
msg.content = "hi, what is this?"  # ValueError

# after
msg.blocks = [TextBlock(text="hi, what is this?"), ImageBlock(image=img_bytes)]
Defensive patterns

Strategy: type-guard

Validate before calling

def set_text(msg: ChatMessage, text: str) -> ChatMessage:
    if len(msg.blocks) > 1 or not isinstance(msg.blocks[0] if msg.blocks else None, (type(None),)) and not (len(msg.blocks) <= 1):
        msg.blocks = [TextBlock(text=text)] + [b for b in msg.blocks if not hasattr(b, "text")]
    else:
        msg.blocks = [TextBlock(text=text)]
    return msg

Type guard

def can_set_content(msg: ChatMessage) -> bool:
    return not msg.blocks or (len(msg.blocks) == 1 and hasattr(msg.blocks[0], "text"))

Prevention

When it happens

Trigger: msg = ChatMessage(blocks=[TextBlock(...), ImageBlock(...)]) then msg.content = "new text"; or blocks=[AudioBlock(...)] then assigning content. Also ChatMessage(content=..., blocks=[multiple]) at construction via the content field validator.

Common situations: Older single-field code (msg.content = ...) run against the multi-block ChatMessage API after a library upgrade; mutating a multimodal message's text while forgetting it carries image/audio blocks.

Related errors


AI-assisted analysis of run-llama/llama_index@afd0fef371 (2026-08-15). Data as JSON: /api/errors/3fe79f5163e7f137. Report an issue: GitHub.