{"record":{"id":"1c5fd484f7d46bbb","repo":"BerriAI/litellm","slug":"unsupported-image-type-type-image-expected-by","errorCode":null,"errorMessage":"Unsupported image type: {type(image)}. Expected bytes, str (URL or file path), or file-like object.","messagePattern":"Unsupported image type: (.+?)\\. Expected bytes, str \\(URL or file path\\), or file-like object\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"litellm/llms/black_forest_labs/image_edit/transformation.py","lineNumber":226,"sourceCode":"                response: Final = safe_get(litellm.module_level_client, image, timeout=60.0)\n                response.raise_for_status()\n                return response.content\n            else:\n                raise ValueError(\n                    \"Unsupported image input: plain string values that are not URLs are not accepted. \"\n                    \"Provide image bytes or a file-like object.\"\n                )\n        elif hasattr(image, \"read\"):\n            # File-like object\n            pos: Final = getattr(image, \"tell\", lambda: 0)()\n            if hasattr(image, \"seek\"):\n                image.seek(0)\n            data: Final = image.read()\n            if hasattr(image, \"seek\"):\n                image.seek(pos)\n            return data\n        else:\n            raise ValueError(\n                f\"Unsupported image type: {type(image)}. Expected bytes, str (URL or file path), or file-like object.\"\n            )\n\n    def transform_image_edit_request(\n        self,\n        model: str,\n        prompt: str | None,\n        image: FileTypes | None,\n        image_edit_optional_request_params: dict,\n        litellm_params: GenericLiteLLMParams,\n        headers: dict,\n    ) -> tuple[dict, RequestFiles]:\n        \"\"\"\n        Transform OpenAI-style request to Black Forest Labs request format.\n\n        BFL uses JSON body with base64-encoded images, not multipart/form-data.\n        \"\"\"\n        # Read and encode image","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/llms/black_forest_labs/image_edit/transformation.py#L208-L244","documentation":"The final else-branch of `_read_image_bytes` in the BFL image-edit transformer: the `image` argument was neither `bytes`, a `list`, a `str`, nor an object exposing `.read()` (file-like). LiteLLM therefore has no way to extract raw bytes and raises this ValueError. Note the message mentions 'file path' but the str branch above only actually supports http(s) URLs.","triggerScenarios":"Passing a `pathlib.Path` object (has no `.read()` attr - `Path.read_bytes` exists but `read` does not); a PIL `Image.Image`; a `numpy.ndarray`; an `io.StringIO`; a generator; `None` accidentally forwarded from an optional field; a boto3 StreamingBody (no `.read` until iterated, though most have read - generators and Path are the classic hits).","commonSituations":"Pipelines that load images with Pillow and pass the PIL object straight through; numpy-based preprocessing (cv2/np arrays); Windows/Linux portability code using pathlib; an upstream optional param defaulting to None being forwarded unconditionally.","solutions":["PIL image: `buf = io.BytesIO(); pil_img.save(buf, format=\"PNG\"); image = buf.getvalue()`.","numpy/cv2 frame: `image = cv2.imencode(\".png\", frame)[1].tobytes()`.","pathlib.Path: `image = path.read_bytes()` (the Path object itself is rejected).","If the value can legitimately be absent, branch on it (`if image is not None`) instead of forwarding None."],"exampleFix":"# before\nfrom PIL import Image\nimg = Image.open(\"cat.png\")\nresp = litellm.image_edit(model=\"flux-kontext-pro\", image=img, prompt=\"...\")  # raises\n\n# after\nimport io\nfrom PIL import Image\nbuf = io.BytesIO()\nImage.open(\"cat.png\").save(buf, format=\"PNG\")\nresp = litellm.image_edit(model=\"flux-kontext-pro\", image=buf.getvalue(), prompt=\"...\")","handlingStrategy":"type-guard","validationCode":"import io\n\ndef coerce_to_bytes(image) -> bytes:\n    if isinstance(image, (bytes, bytearray)):\n        return bytes(image)\n    if hasattr(image, \"read\"):\n        return image.read()\n    if image.__class__.__name__ == \"Image\":            # PIL\n        buf = io.BytesIO(); image.save(buf, format=\"PNG\"); return buf.getvalue()\n    if hasattr(image, \"tobytes\"):                       # numpy\n        return image.tobytes()\n    if hasattr(image, \"read_bytes\"):                    # pathlib.Path\n        return image.read_bytes()\n    raise TypeError(f\"cannot convert {type(image)} to image bytes\")\n\nimage = coerce_to_bytes(image)  # before litellm.image_edit(...)","typeGuard":"from typing import Any\n\ndef is_bfl_supported_image(x: Any) -> bool:\n    if isinstance(x, (bytes, bytearray, list)):\n        return True\n    if isinstance(x, str):\n        return x.startswith((\"http://\", \"https://\"))\n    return hasattr(x, \"read\") and callable(x.read)","tryCatchPattern":null,"preventionTips":["Convert PIL/numpy/Path objects to bytes at your application boundary, not deep in call sites.","Never forward an unvalidated optional value - branch on None explicitly.","Add a unit test asserting your pipeline passes only bytes/file-like/URL into litellm."],"tags":["litellm","black-forest-labs","flux","image-edit","input-validation","type-mismatch","pillow","numpy"],"backgroundTag":"unsupported-input-type","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}