BerriAI/litellm · error · Exception
`content` can only contain strings or openai content dicts
Error message
`content` can only contain strings or openai content dicts
What it means
Thrown by Bytez's `_adapt_string_only_content_to_lists` helper when a message's `content` list contains an element that is neither a `str` nor a `dict`. The helper normalizes mixed string/dict content into a uniform list of dicts; any other Python object (int, None, bytes, Pydantic model) is rejected before the request is sent.
Source
Thrown at litellm/llms/bytez/chat/transformation.py:442
content = message.get("content")
new_content = []
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:View on GitHub (pinned to 6c2dcb801b)
Solutions
- Serialize every content item to a plain str or dict before calling litellm (e.g. call `.model_dump()` on Pydantic parts).
- Check for None elements introduced by conditional appends (`parts.append(x if cond else None)`).
- Add a guard like `assert all(isinstance(p, (str, dict)) for p in content)` in dev builds.
- Inspect the exact message list with a debug print right before the completion call.
Example fix
// before content = ["describe", None, image_part_pydantic] // after content = ["describe", image_part_pydantic.model_dump()]
Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_content_items(items):
out = []
for it in items:
if isinstance(it, str):
out.append({"type": "text", "text": it})
elif isinstance(it, dict):
out.append(it)
elif hasattr(it, "model_dump"):
out.append(it.model_dump())
else:
raise TypeError(f"unsupported content item: {type(it)!r}")
return out Type guard
def is_valid_content_item(item) -> bool:
return isinstance(item, (str, dict)) Prevention
- Call .model_dump() on Pydantic message parts before passing them in.
- Avoid conditional appends that can insert None.
- Keep message construction in one typed builder function.
When it happens
Trigger: Passing `content: [None]`, `content: [123]`, or a list containing an unserialized object (e.g. a Pydantic message part or PIL Image) to a Bytez completion call. String content and dict content items are fine; everything else raises.
Common situations: Building messages programmatically and accidentally appending a non-serialized object or None; passing OpenAI SDK typed objects (e.g. `ChatCompletionContentPartImage`) directly instead of `.model_dump()` dicts.
Related errors
- Prop `{type}` is not supported
- Prop `{value_name}` is not a string
- Prop `type` is not a string
- Content must be a string
- Missing model or messages
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/df7f5e9cecf7855d.
Report an issue: GitHub.