{"record":{"id":"970da06530411ef0","repo":"BerriAI/litellm","slug":"prop-image-url-must-be-a-string-or-an-object-wit","errorCode":null,"errorMessage":"Prop `image_url` must be a string or an object with a `url` property","messagePattern":"Prop `image_url` must be a string or an object with a `url` property","errorType":"validation","errorClass":"OCIError","httpStatus":400,"severity":"error","filePath":"litellm/llms/oci/chat/generic.py","lineNumber":100,"sourceCode":"                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,\n                    message=\"Content item of type `text` must have a string `text` field\",\n                )\n            new_content.append(OCITextContentPart(text=text))\n\n        elif item_type == \"image_url\":\n            image_url = content_item.get(\"image_url\")\n            if isinstance(image_url, dict):\n                image_url = image_url.get(\"url\")\n            if not isinstance(image_url, str):\n                raise OCIError(\n                    status_code=400,\n                    message=\"Prop `image_url` must be a string or an object with a `url` property\",\n                )\n            new_content.append(OCIImageContentPart(imageUrl=OCIImageUrl(url=image_url)))\n\n    return OCIMessage(\n        role=open_ai_to_generic_oci_role_map[role],\n        content=new_content,\n        toolCalls=None,\n        toolCallId=None,\n    )\n\n\ndef adapt_messages_to_generic_oci_standard_tool_call(role: str, tool_calls: list) -> OCIMessage:\n    \"\"\"Convert an assistant tool-call message to OCI format.\"\"\"\n    tool_calls_formatted: Final = []\n    for tool_call in tool_calls:\n        if not isinstance(tool_call, dict):","sourceCodeStart":82,"sourceCodeEnd":118,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/oci/chat/generic.py#L82-L118","documentation":"For content parts of type \"image_url\", OCI GENERIC needs a URL string. LiteLLM accepts either the OpenAI object form {\"image_url\":{\"url\":...}} (unwrapping the url) or a plain string {\"image_url\":\"https://...\"}. If after unwrapping the value is not a string (missing url key, None, nested objects), it raises OCIError 400.","triggerScenarios":"Sending {\"type\":\"image_url\",\"image_url\":{}} (empty object), {\"image_url\":{\"data\":...}} (wrong key), {\"image_url\":None}, or a data-URI structured incorrectly to an oci/generic.* model. Raised client-side in adapt_messages_to_generic_oci_standard_content_message.","commonSituations":"Typos like {\"image_url\":{\"link\":...}} instead of url; constructing image parts where the URL comes from user input that is None; passing a bytes/PIL object instead of a base64 data URL; nested wrappers from other SDKs (e.g. {\"image_url\":{\"url\":{\"href\":...}}}).","solutions":["Use the canonical OpenAI shape: {\"type\":\"image_url\",\"image_url\":{\"url\":\"https://... or data:image/png;base64,....\"}}","For local images, base64-encode into a data URI: f\"data:image/png;base64,{b64str}\"","Check the url key actually resolves to a string where you build the part (log the value's type)","Unwrap extra nesting before sending: image_url = part[\"image_url\"][\"url\"] if isinstance(...) else part[\"image_url\"]"],"exampleFix":"# before\ncontent = [\n  {\"type\": \"text\", \"text\": \"what is this?\"},\n  {\"type\": \"image_url\", \"image_url\": {\"link\": \"https://x/img.png\"}},\n]\n\n# after\ncontent = [\n  {\"type\": \"text\", \"text\": \"what is this?\"},\n  {\"type\": \"image_url\", \"image_url\": {\"url\": \"https://x/img.png\"}},\n]","handlingStrategy":"type-guard","validationCode":"def coerce_image_parts(content: list) -> list:\n    out = []\n    for p in content:\n        if p.get(\"type\") == \"image_url\":\n            url = p.get(\"image_url\")\n            if isinstance(url, dict):\n                url = url.get(\"url\")\n            p = {**p, \"image_url\": {\"url\": url}}\n        out.append(p)\n    return out","typeGuard":"def is_valid_image_part(part) -> bool:\n    if part.get(\"type\") != \"image_url\":\n        return True  # not this validator's concern\n    u = part.get(\"image_url\")\n    return isinstance(u, str) or (isinstance(u, dict) and isinstance(u.get(\"url\"), str))","tryCatchPattern":null,"preventionTips":["Encode local images with a helper that always returns {\"image_url\": {\"url\": data_uri}}","Reject None/bytes image sources at your input boundary with a clear message"],"tags":["oci","vision","image-url","request-validation","multimodal"],"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"}