BerriAI/litellm · error · Exception
Content must be a string
Error message
Content must be a string
What it means
Thrown by Bytez's content normalization when a message's `content` field is not a str, dict, or list (e.g. an int, None, or bytes). The helper only understands string, single-dict, and list-of-parts shapes; anything else is rejected client-side before an HTTP request is made.
Source
Thrown at litellm/llms/bytez/chat/transformation.py:446
if isinstance(content, str):
new_content.append({"type": "text", "text": content})
elif isinstance(content, dict):
new_content.append(content)
elif isinstance(content, list):
new_content_items = []
for content_item in content:
if isinstance(content_item, str):
new_content_items.append({"type": "text", "text": content_item})
elif isinstance(content_item, dict):
new_content_items.append(content_item)
else:
raise Exception("`content` can only contain strings or openai content dicts")
new_content += new_content_items
else:
raise Exception("Content must be a string")
new_messages.append({"role": role, "content": new_content})
return new_messages
# TODO get this from the api instead of doing it here, will require backend work
def get_tokens_from_messages(messages: list[dict]):
total = 0
for message in messages:
content: list[dict] = message["content"]
for content_item in content:
type = content_item["type"]
if type == "text":
value: str = content_item["text"]
words = value.split(" ")View on GitHub (pinned to 6c2dcb801b)
Solutions
- Wrap non-string content in `str(...)` or `json.dumps(...)` before the call.
- Replace `content: None` with `content: ""` or omit the message.
- Validate message shape client-side with a typed message builder.
- Re-check the failing message index from the traceback and fix that specific entry.
Example fix
// before
messages = [{"role": "user", "content": user_id}] # int
// after
messages = [{"role": "user", "content": str(user_id)}] Defensive patterns
Strategy: validation
Validate before calling
def validate_message_shape(messages):
for i, m in enumerate(messages):
c = m.get("content")
if not isinstance(c, (str, dict, list)):
raise ValueError(f"messages[{i}].content must be str/dict/list, got {type(c).__name__}") Type guard
def has_valid_content(message) -> bool:
return isinstance(message.get("content"), (str, dict, list)) Prevention
- Always stringify computed values: str(x) or json.dumps(x).
- Use None content only where the provider documents it; Bytez does not accept it.
- Type your message builders (TypedDict/dataclass) so content is always str or list[parts].
When it happens
Trigger: Sending `messages=[{"role": "user", "content": 42}]` or `content: None` to a Bytez model. Also happens when tool/result messages are constructed with non-string payloads (e.g. raw JSON bytes or a number) that other providers tolerate.
Common situations: Interpolating a computed value into content without `str()` conversion; forwarding `None` content for assistant/tool messages; passing structured data (dict output of a function) as the whole content instead of `json.dumps(data)`.
Related errors
- Prop `{type}` is not supported
- Prop `{value_name}` is not a string
- `content` can only contain strings or openai content dicts
- BedrockException - {error_str} . Enable 'litellm.modify_para
- file_id and file_data are both None
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/ce769adfd6ac971b.
Report an issue: GitHub.