BerriAI/litellm · error · OCIError
Message `content` must be a string or list of content parts
Error message
Message `content` must be a string or list of content parts
What it means
For system/user/assistant messages that carry content, the OCI GENERIC adapter accepts content as either a plain string or a list of content-part dicts. Any other type (dict, int, None-handled-elsewhere objects, bytes) raises OCIError(400) 'Message `content` must be a string or list of content parts' client-side.
Source
Thrown at litellm/llms/oci/chat/generic.py:187
def adapt_messages_to_generic_oci_standard(
messages: list[AllMessageValues],
) -> list[OCIMessage]:
"""Convert an OpenAI-format message array to OCI GENERIC format."""
new_messages: Final = []
for message in messages:
role = message["role"]
content = message.get("content")
tool_calls = message.get("tool_calls")
tool_call_id = message.get("tool_call_id")
if role == "assistant" and tool_calls is not None:
if not isinstance(tool_calls, list):
raise OCIError(status_code=400, message="Message `tool_calls` must be a list")
new_messages.append(adapt_messages_to_generic_oci_standard_tool_call(role, tool_calls))
elif role in ["system", "user", "assistant"] and content is not None:
if not isinstance(content, (str, list)):
raise OCIError(
status_code=400,
message="Message `content` must be a string or list of content parts",
)
new_messages.append(adapt_messages_to_generic_oci_standard_content_message(role, content))
elif role == "tool":
if not isinstance(tool_call_id, str):
raise OCIError(
status_code=400,
message="Tool result message must have a string `tool_call_id`",
)
if not isinstance(content, str):
raise OCIError(
status_code=400,
message="Tool result message `content` must be a string",
)
new_messages.append(adapt_messages_to_generic_oci_standard_tool_response(role, tool_call_id, content))
View on GitHub (pinned to 6c2dcb801b)
Solutions
- Use 'content': 'plain text' or 'content': [{'type':'text','text':'...'}, ...].
- Coerce unknown content: str() scalars, or wrap dicts into part lists, before calling litellm.
- Add a pre-flight type check on every message's content field.
Example fix
# before
{'role':'user','content': {'text':'hello'}}
# after
{'role':'user','content': 'hello'}
# or
{'role':'user','content': [{'type':'text','text':'hello'}]} Defensive patterns
Strategy: type-guard
Validate before calling
def normalize_message_content(msg):
content = msg.get('content')
if isinstance(content, dict):
msg['content'] = [content] if set(content) & {'type', 'text', 'image_url'} else str(content)
elif not isinstance(content, (str, list, type(None))):
msg['content'] = str(content)
return msg Type guard
def message_content_is_valid(msg: object) -> bool:
if not isinstance(msg, dict):
return False
c = msg.get('content')
return c is None or isinstance(c, (str, list)) Prevention
- Validate the whole messages array with a shared checker before every provider call.
- Keep prompt data as strings end-to-end; convert structured data to text at build time.
When it happens
Trigger: Sending {'role':'user','content': {'text':'hi'}} (dict instead of list of parts), content as a number, or content as raw bytes to an oci/ GENERIC model.
Common situations: Data-driven prompts where content is sometimes a single object; YAML/JSON configs parsed into dicts and passed through unmodified; content accidentally left as a template object or lazy proxy.
Related errors
- Each content item must be a dictionary
- Content item of type `text` must have a string `text` field
- Tool result message `content` must be a string
- Each content item must have a string `type` field
- Content type `{item_type}` is not supported by OCI
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/bb10f59ed862b7bc.
Report an issue: GitHub.