{"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/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/oci/chat/generic.py#L54-L90","documentation":"When translating an OpenAI-format chat message to OCI GENERIC format, LiteLLM iterates multipart `content` arrays and requires every element to be a dict (an OpenAI content-part object). Encountering a non-dict item (e.g. a bare string) raises OCIError 400 before any network call. This is strict input validation: OCI GENERIC accepts typed content parts, so raw strings inside a list are rejected early.","triggerScenarios":"Calling litellm.completion(model=\"oci/generic...\", messages=[{\"role\":\"user\",\"content\":[\"just a string\"]}]) — a content list whose element is not a dict. Also triggered by frameworks that build content lists with mixed str/dict elements before forwarding to LiteLLM.","commonSituations":"Hand-built content arrays mixing strings and part objects; LangChain/custom wrappers that append plain text into a multimodal content list; migrating OpenAI calls (which tolerate lenient shapes) to OCI GENERIC models; data from user input spliced unvalidated into content.","solutions":["Wrap bare strings in a text part: [{\"type\":\"text\",\"text\":\"just a string\"}]","Or hoist the whole message to plain string content: {\"role\":\"user\",\"content\":\"just a string\"}","Validate/normalize your message builder so content lists only ever contain dicts with a type key","If a framework feeds you mixed lists, map str -> {\"type\":\"text\",\"text\":s} before calling litellm"],"exampleFix":"# before\nmessages = [{\"role\": \"user\", \"content\": [\"analyze this\"]}]\nlitellm.completion(model=\"oci/generic.my-model\", messages=messages)  # 400\n\n# after\nmessages = [{\"role\": \"user\", \"content\": [{\"type\": \"text\", \"text\": \"analyze this\"}]}]","handlingStrategy":"validation","validationCode":"def normalize_content(content):\n    if isinstance(content, str):\n        return content\n    return [\n        {\"type\": \"text\", \"text\": item} if isinstance(item, str) else item\n        for item in content\n    ]\n\nmessages = [{\"role\": \"user\", \"content\": normalize_content(msg[\"content\"])} for msg in messages]","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":null,"preventionTips":["Construct content lists only through one helper that always emits part dicts","Never splice raw user input directly into multipart content arrays"],"tags":["oci","request-validation","messages","content-parts","chat-completions"],"backgroundTag":"request-body-validation-failed","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}