BerriAI/litellm · error · ValueError

Unsupported image value '{image}'. Provide a GCS URI (gs://.

Error message

Unsupported image value '{image}'. Provide a GCS URI (gs://...), a dict with 'gcsUri' or 'bytesBase64Encoded'/'mimeType', or a binary file-like object.

What it means

Input-validation error in the Veo request transformer for the `image` parameter (used for image-to-video). The transformer accepts exactly three shapes: a dict already in Vertex format ({"gcsUri": ...} or {"bytesBase64Encoded": ..., "mimeType": ...}), a string starting with gs://, or a non-str/file-like object that gets base64-encoded. Any other string — an http(s) URL, a local file path, or arbitrary text — hits this raise before any API call is made.

Source

Thrown at litellm/llms/vertex_ai/videos/transformation.py:338

        instance_dict: Final[dict[str, object]] = {"prompt": prompt}
        params_copy: Final = video_create_optional_request_params.copy()

        # Check if user wants to provide full instance dict
        if "instances" in params_copy and isinstance(params_copy["instances"], dict):
            # Replace/merge with user-provided instance
            instance_dict.update(params_copy["instances"])
            params_copy.pop("instances")
        elif "image" in params_copy and params_copy["image"] is not None:
            image: Final = params_copy["image"]
            if isinstance(image, dict):
                # Already in Vertex format e.g. {"gcsUri": "gs://..."} or
                # {"bytesBase64Encoded": "...", "mimeType": "..."}
                image_data = image
            elif isinstance(image, str) and image.startswith("gs://"):
                # Bare GCS URI — Vertex AI accepts gcsUri natively, no download needed
                image_data = {"gcsUri": image}
            elif isinstance(image, str):
                raise ValueError(
                    f"Unsupported image value '{image}'. "
                    "Provide a GCS URI (gs://...), a dict with 'gcsUri' or "
                    "'bytesBase64Encoded'/'mimeType', or a binary file-like object."
                )
            else:
                # File-like object — encode to base64
                image_data = _convert_image_to_vertex_format(image)
            instance_dict["image"] = image_data
            params_copy.pop("image")

        # Extract a nested "parameters" block that map_openai_params may have placed
        # inside params_copy (e.g. from provider-specific pass-through).  Merging it
        # flat prevents the double-nesting bug:
        #   {"parameters": {"parameters": {...}}}  ← wrong
        #   {"parameters": {...}}                  ← correct
        nested_params: Final = params_copy.pop("parameters", None)
        vertex_params: Final[dict[str, object]] = {}
        if isinstance(nested_params, dict):

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Use a GCS URI in the same project/region as the Veo call: image="gs://my-bucket/frame.png".
  2. Wrap base64 or remote bytes explicitly: image={"bytesBase64Encoded": b64, "mimeType": "image/png"}.
  3. For local files, open them and pass the file object: image=open("frame.png", "rb") so the transformer base64-encodes it.
  4. For an http(s) URL, download the bytes yourself first, then pass the dict form.

Example fix

# before
litellm.video_generation(
    model="vertex_ai/veo-2.0-generate-001",
    prompt="animate this",
    image="https://example.com/frame.png",  # raises
)

# after
import base64, requests
b64 = base64.b64encode(requests.get("https://example.com/frame.png").content).decode()
litellm.video_generation(
    model="vertex_ai/veo-2.0-generate-001",
    prompt="animate this",
    image={"bytesBase64Encoded": b64, "mimeType": "image/png"},
)
Defensive patterns

Strategy: type-guard

Validate before calling

def normalize_veo_image(image):
    if isinstance(image, dict) and ("gcsUri" in image or "bytesBase64Encoded" in image):
        return image
    if isinstance(image, str) and image.startswith("gs://"):
        return {"gcsUri": image}
    if hasattr(image, "read"):  # file-like
        return {"bytesBase64Encoded": base64.b64encode(image.read()).decode(), "mimeType": "image/png"}
    raise ValueError("image must be gs:// URI, Vertex-format dict, or binary file object")

Type guard

def is_valid_veo_image(image) -> bool:
    if isinstance(image, dict):
        return "gcsUri" in image or "bytesBase64Encoded" in image
    if isinstance(image, str):
        return image.startswith("gs://")
    return hasattr(image, "read")

Try / catch

try:
    v = litellm.video_generation(model="vertex_ai/veo-2.0-generate-001", prompt=p, image=img)
except ValueError as e:
    if "Unsupported image value" in str(e):
        img = normalize_veo_image(img)
        v = litellm.video_generation(model="vertex_ai/veo-2.0-generate-001", prompt=p, image=img)
    else:
        raise

Prevention

When it happens

Trigger: Passing image="https://storage.googleapis.com/bucket/img.png" (http URL, not gs://), image="/tmp/frame.png" (local path), or a base64 string without wrapping it in {"bytesBase64Encoded": ...} to litellm.video_generation with a Veo model.

Common situations: Porting code from Gemini API where arbitrary image URLs were accepted; feeding model outputs or scraped URLs directly; assuming base64 strings are auto-detected.

Related errors


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