BerriAI/litellm · error · OCIError

Each content item must have a string `type` field

Error message

Each content item must have a string `type` field

What it means

In the OCI GENERIC message adapter, every content-part dict must carry a string 'type' field identifying it as text or image_url. If 'type' is missing, None, or not a string, OCIError(400) 'Each content item must have a string `type` field' is raised during request construction. The validation happens locally, so no tokens are spent.

Source

Thrown at litellm/llms/oci/chat/generic.py:76

def adapt_messages_to_generic_oci_standard_content_message(role: str, content: str | list) -> OCIMessage:
    """Convert a plain-text or multipart content message to OCI format."""
    new_content: Final[list[OCIContentPartUnion]] = []
    if isinstance(content, str):
        return OCIMessage(
            role=open_ai_to_generic_oci_role_map[role],
            content=[OCITextContentPart(text=content)],
            toolCalls=None,
            toolCallId=None,
        )

    for content_item in content:
        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))

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Add an explicit string 'type' to every part: 'text' or 'image_url'.
  2. Validate messages with a small pre-flight check (see defense) that asserts each part has isinstance(part.get('type'), str).
  3. Generate parts through one helper function so the discriminator is always set.

Example fix

# before
content=[{'text':'hello'}, {'type':'image_url','image_url':{'url':u}}]

# after
content=[{'type':'text','text':'hello'}, {'type':'image_url','image_url':{'url':u}}]
Defensive patterns

Strategy: type-guard

Validate before calling

for i, part in enumerate(content):
    assert isinstance(part.get('type'), str), f'content[{i}] missing string type: {part!r}'

Type guard

def has_string_type(part: object) -> bool:
    """True when part is a dict carrying a string 'type' discriminator."""
    return isinstance(part, dict) and isinstance(part.get('type'), str)

Try / catch

try:
    litellm.completion(model='oci/...', messages=msgs)
except OCIError as e:
    if 'string `type` field' in str(e):
        msgs = add_missing_types(msgs)
    raise

Prevention

When it happens

Trigger: Passing a content part like {'text':'hi'} (type omitted), {'type':None,...}, or a part where 'type' was set from an untyped variable that ended up non-string, in a message sent to an oci/ GENERIC model.

Common situations: Hand-building content parts and forgetting the discriminator; migrating code from an API that infers the part type from keys; data-driven prompts where a template leaves 'type' empty for some rows.

Related errors


AI-assisted analysis of BerriAI/litellm@6c2dcb801b (2026-08-15). Data as JSON: /api/errors/27e4abc2c6d9a100. Report an issue: GitHub.