{"record":{"id":"f52120bf76b1db22","repo":"BerriAI/litellm","slug":"each-content-item-must-be-a-dictionary","errorCode":null,"errorMessage":"Each content item must be a dictionary","messagePattern":"Each content item must be a dictionary","errorType":"validation","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":72,"sourceCode":"# ---------------------------------------------------------------------------\n# Message building\n# ---------------------------------------------------------------------------\n\n\ndef adapt_messages_to_generic_oci_standard_content_message(role: str, content: str | list) -> OCIMessage:\n    \"\"\"Convert a plain-text or multipart content message to OCI format.\"\"\"\n    new_content: Final[list[OCIContentPartUnion]] = []\n    if isinstance(content, str):\n        return OCIMessage(\n            role=open_ai_to_generic_oci_role_map[role],\n            content=[OCITextContentPart(text=content)],\n            toolCalls=None,\n            toolCallId=None,\n        )\n\n    for content_item in content:\n        if not isinstance(content_item, dict):\n            raise OCIError(status_code=400, message=\"Each content item must be a dictionary\")\n\n        item_type = content_item.get(\"type\")\n        if not isinstance(item_type, str):\n            raise OCIError(\n                status_code=400,\n                message=\"Each content item must have a string `type` field\",\n            )\n        if item_type not in [\"text\", \"image_url\"]:\n            raise OCIError(\n                status_code=400,\n                message=f\"Content type `{item_type}` is not supported by OCI\",\n            )\n\n        if item_type == \"text\":\n            text = content_item.get(\"text\")\n            if not isinstance(text, str):\n                raise OCIError(\n                    status_code=400,","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/llms/oci/chat/generic.py#L54-L90","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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>}.","If the whole content is plain text, pass it as a plain string instead of a list.","Normalize messages from external frameworks through json.loads(json.dumps(...)) or an explicit mapper before calling litellm."],"exampleFix":"# before\nmessages=[{'role':'user','content':['Describe this', {'type':'image_url','image_url':{'url':u}}]}]\n\n# after\nmessages=[{'role':'user','content':[{'type':'text','text':'Describe this'}, {'type':'image_url','image_url':{'url':u}}]}]","handlingStrategy":"validation","validationCode":"def normalize_content(content):\n    if isinstance(content, str):\n        return content\n    parts = []\n    for item in content:\n        if isinstance(item, str):  # hoist stray strings\n            item = {'type': 'text', 'text': item}\n        assert isinstance(item, dict), f'content part must be dict, got {type(item)}'\n        parts.append(item)\n    return parts\n\nmessages = [{'role': 'user', 'content': normalize_content(raw_content)}]","typeGuard":"def is_valid_oci_content(content) -> bool:\n    if isinstance(content, str):\n        return True\n    return isinstance(content, list) and all(isinstance(p, dict) for p in content)","tryCatchPattern":"from litellm.llms.oci.common_utils import OCIError\n\ntry:\n    litellm.completion(model='oci/meta.llama-3.3-70b-instruct', messages=msgs)\nexcept OCIError as e:\n    if 'content item must be a dictionary' in str(e):\n        msgs = repair_content_parts(msgs)  # your normalizer\n    raise","preventionTips":["Build content parts through one helper so shape is uniform everywhere.","json.loads(json.dumps(parts)) when parts come from external frameworks to force plain dicts.","Unit-test the message builder against the OCI validation rules."],"tags":["oci","input-validation","openai-compat","messages"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}