{"record":{"id":"f204737776107278","repo":"BerriAI/litellm","slug":"unable-to-determine-content-type-from-url-url","errorCode":null,"errorMessage":"Unable to determine content type from URL: {url}. Response content-type: {current_content_type}","messagePattern":"Unable to determine content type from URL: (.+?)\\. Response content-type: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/litellm_core_utils/prompt_templates/common_utils.py","lineNumber":1233,"sourceCode":"        if inferred_type:\n            return inferred_type\n\n    # Try to detect from binary content signature (magic bytes)\n    if content:\n        detected_type: Final = get_image_type(content[:100])\n        if detected_type:\n            type_to_mime: Final = {\n                \"png\": \"image/png\",\n                \"jpeg\": \"image/jpeg\",\n                \"gif\": \"image/gif\",\n                \"webp\": \"image/webp\",\n                \"heic\": \"image/heic\",\n            }\n            if detected_type in type_to_mime:\n                return type_to_mime[detected_type]\n\n    # If all fallbacks failed, raise error\n    raise ValueError(f\"Unable to determine content type from URL: {url}. Response content-type: {current_content_type}\")\n\n\ndef get_tool_call_names(tools: list[ChatCompletionToolParam]) -> list[str]:\n    \"\"\"\n    Get tool call names from tools\n    \"\"\"\n    tool_call_names: Final[list[str]] = []\n    for tool in tools:\n        if tool.get(\"type\") == \"function\":\n            tool_call_name = tool.get(\"function\", {}).get(\"name\")\n            if tool_call_name:\n                tool_call_names.append(tool_call_name)\n    return tool_call_names\n\n\ndef is_function_call(optional_params: dict) -> bool:\n    \"\"\"\n    Checks if the optional params contain the function call","sourceCodeStart":1215,"sourceCodeEnd":1251,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/litellm/litellm_core_utils/prompt_templates/common_utils.py#L1215-L1251","documentation":"When litellm downloads an image from a URL (for vision inputs), it tries several strategies to determine the content type: the HTTP response's Content-Type header, magic-byte sniffing of the downloaded bytes (png/jpeg/gif/webp/heic detection), and filename hints. If all fallbacks fail — the header is generic/missing and the bytes don't match a known image signature — it raises ValueError with the URL and the header value it saw.","triggerScenarios":"Passing image_url pointing to non-image content (HTML error page, SVG without a sniffable signature, login-redirect page); servers returning 'application/octet-stream' or 'text/html' with unrecognized bytes; exotic image formats not in the png/jpeg/gif/webp/heic sniff table.","commonSituations":"Expired/signed S3 or CDN URLs that redirect to an HTML login page; SVGs (content-type image/svg+xml but no magic bytes match in the table); WebP variants or AVIF images unsupported by the sniffer; misconfigured static servers omitting Content-Type.","solutions":["Verify the URL actually returns image bytes: curl -I <url> and check Content-Type; open it in a browser.","Convert the image to PNG/JPEG and re-host it where the server sets a correct Content-Type.","For SVGs, rasterize to PNG first — most vision models don't accept SVG anyway.","Refresh expired signed URLs; ensure auth cookies/headers aren't required for the fetch.","If the format is supported but the server header is wrong, embed the image as base64 data URL with an explicit mime type."],"exampleFix":"// before\n{'type':'image_url','image_url':{'url':'https://cdn.example.com/chart.svg'}}\n\n# after (rasterize + embed base64)\nimport base64\npng_b64 = base64.b64encode(open('chart.png','rb').read()).decode()\n{'type':'image_url','image_url':{'url':f'data:image/png;base64,{png_b64}'}}","handlingStrategy":"validation","validationCode":"import urllib.request\n\ndef url_serves_image(url: str) -> bool:\n    req = urllib.request.Request(url, method='GET', headers={'Range': 'bytes=0-15'})\n    with urllib.request.urlopen(req, timeout=10) as r:\n        ctype = r.headers.get('Content-Type', '')\n        magic = r.read(16)\n    if ctype.startswith('image/') and 'svg' not in ctype:\n        return True\n    return magic[:8] in (b'\\x89PNG\\r\\n\\x1a\\n', b'\\xff\\xd8\\xff') or magic[:4] == b'RIFF'","typeGuard":null,"tryCatchPattern":"try:\n    resp = litellm.completion(model=m, messages=[{'role':'user','content':[{'type':'image_url','image_url':{'url':u}},'describe this']}])\nexcept ValueError as e:\n    if 'Unable to determine content type' in str(e):\n        u = to_base64_data_url(u)  # download, convert to PNG, embed as data URL\n        resp = litellm.completion(model=m, messages=[{'role':'user','content':[{'type':'image_url','image_url':{'url':u}},'describe this']}])\n    else:\n        raise","preventionTips":["Pre-validate image URLs with a HEAD request before sending them to the model.","Rasterize SVGs and exotic formats to PNG before upload.","Refresh signed URLs at request time; don't cache them beyond their expiry."],"tags":["vision","images","content-type","url-fetch"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}