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

  1. Open file handles in binary mode: open(path, 'rb') returns the BufferedReader the handler accepts
  2. Wrap in-memory data with io.BytesIO(data) (convert bytearray with bytes() first)
  3. 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

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


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/4bf132e1d6426f2b. Report an issue: GitHub.