BerriAI/litellm · error · ValueError
Unsupported image type for OpenRouter image edit.
Error message
Unsupported image type for OpenRouter image edit.
What it means
LiteLLM's OpenRouter image-edit handler reads the image argument via _read_image_bytes(), which accepts exactly three types: raw bytes, io.BytesIO, and io.BufferedReader (a file opened in binary mode). Any other object falls through to raise ValueError('Unsupported image type for OpenRouter image edit.') before any network call is made.
Source
Thrown at litellm/llms/openrouter/image_edit/transformation.py:367
model_response._hidden_params["model"] = response_json.get("model", model)
def _read_image_bytes(self, image: FileTypes) -> bytes:
"""Read raw bytes from various image input types."""
if isinstance(image, bytes):
return image
if isinstance(image, BytesIO):
current_pos = image.tell()
image.seek(0)
data = image.read()
image.seek(current_pos)
return data
if isinstance(image, BufferedReader):
current_pos = image.tell()
image.seek(0)
data = image.read()
image.seek(current_pos)
return data
raise ValueError("Unsupported image type for OpenRouter image edit.")
View on GitHub (pinned to 77b7c6c40c)
Solutions
- Open file handles in binary mode: open(path, 'rb') returns the BufferedReader the handler accepts
- Wrap in-memory data with io.BytesIO(data) (convert bytearray with bytes() first)
- Convert PIL images by saving into an io.BytesIO buffer, e.g. img.save(buf, format='PNG')
Example fix
// before
resp = litellm.image_edit(model="openrouter/google/gemini-2.5-flash-image-preview", image="/tmp/cat.png", prompt="add a hat")
// after
import io
with open("/tmp/cat.png", "rb") as f: # BufferedReader: accepted
resp = litellm.image_edit(model="openrouter/google/gemini-2.5-flash-image-preview", image=f, prompt="add a hat")
# or for in-memory bytes (also accepted directly):
resp = litellm.image_edit(model="openrouter/...", image=io.BytesIO(png_bytes), prompt="add a hat") Defensive patterns
Strategy: validation
Validate before calling
import io
def to_supported_image(image):
"""Adapt any common image input to a type OpenRouter image_edit accepts."""
if isinstance(image, (bytes, io.BytesIO, io.BufferedReader)):
return image
if isinstance(image, bytearray):
return io.BytesIO(bytes(image))
if isinstance(image, str):
return open(image, "rb")
raise TypeError(f"Cannot adapt {type(image).__name__} for OpenRouter image edit") Type guard
import io
from typing import TypeGuard
def is_supported_image(image: object) -> TypeGuard[bytes | io.BytesIO | io.BufferedReader]:
return isinstance(image, (bytes, io.BytesIO, io.BufferedReader)) Try / catch
Optionally wrap the call in try/except ValueError and re-raise with a message that includes type(image).__name__ so the caller sees exactly which input type was rejected.
Prevention
- Normalize all image arguments through one helper that returns bytes, io.BytesIO, or open(path,'rb')
- Never pass PIL Image objects, path strings, or bytearray directly to openrouter image_edit
- Add unit tests asserting your image inputs are one of the three supported types
When it happens
Trigger: Passing image as a filesystem path string, a bytearray, a PIL.Image.Image, a tempfile.SpooledTemporaryFile in text mode, or any custom file-like object to litellm.image_edit() with an openrouter/* model. Note plain bytes ARE accepted; only the listed stream/bytes types are.
Common situations: Switching from OpenAI's image_edit (which accepts paths or PIL objects in some wrappers) to an openrouter model; passing ImageFile received from a web framework; assuming bytearray behaves like bytes.
Related errors
- Max recursion depth {max_depth} reached while reading image
- Unsupported image input: plain string values that are not UR
- Unsupported image type: {type(image)}. Expected bytes, str (
- Error parsing OpenRouter response: {e}
- Error transforming OpenRouter image edit response: {e}
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/4bf132e1d6426f2b.
Report an issue: GitHub.