BerriAI/litellm · error · OCIError
Content item of type `text` must have a string `text` field
Error message
Content item of type `text` must have a string `text` field
What it means
For content parts with type 'text', the OCI GENERIC adapter requires the 'text' field to be a string. A text part whose 'text' is missing, None, a number, or a list raises OCIError(400) "Content item of type `text` must have a string `text` field" before the request is sent.
Source
Thrown at litellm/llms/oci/chat/generic.py:89
if not isinstance(content_item, dict):
raise OCIError(status_code=400, message="Each content item must be a dictionary")
item_type = content_item.get("type")
if not isinstance(item_type, str):
raise OCIError(
status_code=400,
message="Each content item must have a string `type` field",
)
if item_type not in ["text", "image_url"]:
raise OCIError(
status_code=400,
message=f"Content type `{item_type}` is not supported by OCI",
)
if item_type == "text":
text = content_item.get("text")
if not isinstance(text, str):
raise OCIError(
status_code=400,
message="Content item of type `text` must have a string `text` field",
)
new_content.append(OCITextContentPart(text=text))
elif item_type == "image_url":
image_url = content_item.get("image_url")
if isinstance(image_url, dict):
image_url = image_url.get("url")
if not isinstance(image_url, str):
raise OCIError(
status_code=400,
message="Prop `image_url` must be a string or an object with a `url` property",
)
new_content.append(OCIImageContentPart(imageUrl=OCIImageUrl(url=image_url)))
return OCIMessage(
role=open_ai_to_generic_oci_role_map[role],View on GitHub (pinned to 6c2dcb801b)
Solutions
- Coerce text values to str and skip empty parts: if v is not None: parts.append({'type':'text','text':str(v)}).
- Add a pre-flight assertion that every type=='text' part has isinstance(part['text'], str).
- Fix the upstream data source so prompt text is always a plain string.
Example fix
# before
{'type':'text','text': None}
# after
# build only when a value exists
parts = [{'type':'text','text': str(v)} for v in values if v is not None] Defensive patterns
Strategy: type-guard
Validate before calling
content = [
{'type': 'text', 'text': str(p['text'])}
for p in content
if p.get('type') == 'text' and p.get('text') is not None
] Type guard
def is_valid_text_part(part: object) -> bool:
return (
isinstance(part, dict)
and part.get('type') == 'text'
and isinstance(part.get('text'), str)
) Prevention
- Coerce prompt variables with str() and skip None values when assembling parts.
- Add CI tests that every generated text part passes a type guard.
When it happens
Trigger: Sending {'type':'text','text':None} or {'type':'text','text':123} to an oci/ GENERIC model; building parts from template variables where a value can be None; f-string parts accidentally wrapped in a list.
Common situations: Optional prompt fields defaulting to None and still emitted as parts; data from JSON where text is sometimes numeric; a refactor changing text from str to a richer object.
Related errors
- Each content item must have a string `type` field
- Prop `image_url` must be a string or an object with a `url`
- Message `content` must be a string or list of content parts
- Tool result message `content` must be a string
- Each content item must be a dictionary
AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15).
Data as JSON: /api/errors/9d57b85d762ad2da.
Report an issue: GitHub.