BerriAI/litellm · error · OCIError

Each content item must be a dictionary

Error message

Each content item must be a dictionary

What it means

When converting an OpenAI-format message to OCI GENERIC format, multipart content must be a list of dictionaries (content parts). If any element of the content list is not a dict (e.g. a raw string, a Pydantic object, or None), OCIError(400) 'Each content item must be a dictionary' is raised before any request is sent. This is strict client-side validation of caller input.

Source

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

# ---------------------------------------------------------------------------
# Message building
# ---------------------------------------------------------------------------


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,

View on GitHub (pinned to 6c2dcb801b)

Solutions

  1. Make every element of the content list a dict with a 'type' key, e.g. {'type':'text','text':'...'} — hoist stray strings into {'type':'text','text':<string>}.
  2. If the whole content is plain text, pass it as a plain string instead of a list.
  3. Normalize messages from external frameworks through json.loads(json.dumps(...)) or an explicit mapper before calling litellm.

Example fix

# before
messages=[{'role':'user','content':['Describe this', {'type':'image_url','image_url':{'url':u}}]}]

# after
messages=[{'role':'user','content':[{'type':'text','text':'Describe this'}, {'type':'image_url','image_url':{'url':u}}]}]
Defensive patterns

Strategy: validation

Validate before calling

def normalize_content(content):
    if isinstance(content, str):
        return content
    parts = []
    for item in content:
        if isinstance(item, str):  # hoist stray strings
            item = {'type': 'text', 'text': item}
        assert isinstance(item, dict), f'content part must be dict, got {type(item)}'
        parts.append(item)
    return parts

messages = [{'role': 'user', 'content': normalize_content(raw_content)}]

Type guard

def is_valid_oci_content(content) -> bool:
    if isinstance(content, str):
        return True
    return isinstance(content, list) and all(isinstance(p, dict) for p in content)

Try / catch

from litellm.llms.oci.common_utils import OCIError

try:
    litellm.completion(model='oci/meta.llama-3.3-70b-instruct', messages=msgs)
except OCIError as e:
    if 'content item must be a dictionary' in str(e):
        msgs = repair_content_parts(msgs)  # your normalizer
    raise

Prevention

When it happens

Trigger: Calling completion with an oci/ GENERIC model (llama, grok, gemini on OCI) with content like [{'type':'text','text':'hi'}, 'plain string'] or content parts produced by another SDK as objects/NamedTuples rather than plain dicts.

Common situations: Mixing a plain string into a content-parts list by accident (list concatenation bug); passing messages serialized from a framework that emits non-dict part objects; copy-pasting multimodal examples that use a different part representation.

Related errors


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