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

A content part with type "text" must carry its payload in a string `text` field for OCI GENERIC. If `text` is missing, None, or a non-string (number, dict, list), translation aborts with OCIError 400. Fail-fast validation mirroring the OpenAI content-part contract OCI maps onto.

Solutions

  1. Ensure every {"type":"text"} part has a string: coerce with str(value) when building the message
  2. Default empty strings explicitly: text=msg.get("text") or "" if an empty text part is intentional
  3. Validate message shape with a type guard/serializer before calling litellm
  4. Check for None coming from optional chain construction (e.g. data.get("text") when key absent)

Example fix

# before
content = [{"type": "text", "text": 12345}]

# after
content = [{"type": "text", "text": "12345"}]
Defensive patterns

Strategy: type-guard

Validate before calling

def coerce_text_parts(content: list) -> list:
    return [
        {**p, "text": str(p["text"])} if p.get("type") == "text" else p
        for p in content
    ]

Type guard

def is_valid_text_part(part) -> bool:
    return part.get("type") == "text" and isinstance(part.get("text"), str)

Prevention

When it happens

Trigger: messages=[{"role":"user","content":[{"type":"text"}]}] (missing text), [{"type":"text","text":123}] (int), or [{"type":"text","text":None}] against an oci/generic.* model. Raised locally in content adaptation before any HTTP request.

Common situations: Template code inserting numbers/None into text fields without str(); JSON where text serialized as a non-string; omitting the field when constructing parts programmatically; refactors renaming the field.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/9d57b85d762ad2da. Report an issue: GitHub.

Appendix: 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 77b7c6c40c)