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
- To update text on a multimodal message, replace the blocks list: msg.blocks = [TextBlock(text="new"), *msg.blocks[1:]].
- Construct with blocks only (no content kwarg) when using multiple or non-text blocks.
- 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
- After migrating to the blocks API, mutate msg.blocks instead of msg.content.
- Use ChatMessage.from_str for pure-text messages.
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
- LLM only supports text inputs
- Invalid message content: {message.content!s}
- Could not format attribute {attribute_name} with value {temp
- CitableBlock content must contain exactly one block when pro
- PandasQueryEngine has been moved to `llama-index-experimenta
AI-assisted analysis of run-llama/llama_index@afd0fef371 (2026-08-15).
Data as JSON: /api/errors/3fe79f5163e7f137.
Report an issue: GitHub.