run-llama/llama_index · error · ValueError
Invalid message content: {message.content!s}
Error message
Invalid message content: {message.content!s} What it means
Raised by BaseLLM.convert_chat_messages when a ChatMessage's content is neither a str nor a List (of blocks). The message content contract is string or block-list; anything else (int, dict, None, custom object) hits this branch with the offending value echoed via !s.
Source
Thrown at llama-index-core/llama_index/core/base/llms/base.py:84
return {"class_name": self.class_name(), **self.metadata.model_dump()}
def convert_chat_messages(self, messages: Sequence[ChatMessage]) -> List[Any]:
"""Convert chat messages to an LLM specific message format."""
converted_messages = []
for message in messages:
if isinstance(message.content, str):
converted_messages.append(message)
elif isinstance(message.content, List):
content_string = ""
for block in message.content:
if isinstance(block, TextBlock):
content_string += block.text
else:
raise ValueError("LLM only supports text inputs")
message.content = content_string
converted_messages.append(message)
else:
raise ValueError(f"Invalid message content: {message.content!s}")
return converted_messages
@abstractmethod
def chat(self, messages: Sequence[ChatMessage], **kwargs: Any) -> ChatResponse:
"""
Chat endpoint for LLM.
Args:
messages (Sequence[ChatMessage]):
Sequence of chat messages.
kwargs (Any):
Additional keyword arguments to pass to the LLM.
Returns:
ChatResponse: Chat response from the LLM.
Examples:View on GitHub (pinned to afd0fef371)
Solutions
- Ensure every ChatMessage content is a str: coerce with str(...) at construction.
- For rich content, build a list of typed blocks: [TextBlock(text=...), ImageBlock(...)].
- Validate messages before calling chat (see type guard below) and reject/normalize bad ones at your boundary.
Example fix
# before
msg = ChatMessage(role=MessageRole.USER, content=payload["text"] if "text" in payload else None)
# after
text = str(payload.get("text", ""))
msg = ChatMessage(role=MessageRole.USER, content=text) Defensive patterns
Strategy: type-guard
Validate before calling
msgs = [m if isinstance(m.content, (str, list)) and m.content else m.model_copy(update={"content": str(m.content or "")}) for m in msgs] Type guard
def is_valid_content(c: Any) -> bool:
return isinstance(c, (str, list)) Prevention
- Always construct ChatMessage content from str(...) of external data.
- Never forward None/dict payloads into message content.
When it happens
Trigger: Calling llm.chat with ChatMessage(content=123), content=None (non-defaulted), content={"text": ...}, or any non-str/non-list object; programmatic message builders that pass through unvalidated payloads.
Common situations: Passing parsed JSON or numbers from an API straight into ChatMessage; a None content slipping through when optional fields are forwarded; refactors changing content from str to dict.
Related errors
- LLM only supports text inputs
- Unexpected type: {type(choice)}
- Unexpected type: {type(query)}
- embeddings_cache must be of type BaseKVStore
- image_node.image is neither a string or None.
AI-assisted analysis of run-llama/llama_index@afd0fef371 (2026-08-15).
Data as JSON: /api/errors/e016da976e1a8d24.
Report an issue: GitHub.