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
- Use a GCS URI in the same project/region as the Veo call: image="gs://my-bucket/frame.png".
- Wrap base64 or remote bytes explicitly: image={"bytesBase64Encoded": b64, "mimeType": "image/png"}.
- For local files, open them and pass the file object: image=open("frame.png", "rb") so the transformer base64-encodes it.
- 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
- Convert http(s) URLs to bytesBase64Encoded dicts before passing them to Veo.
- Keep images in GCS buckets in the same project/region for the cheapest path (gcsUri).
- Wrap image inputs in a normalize_veo_image() helper at your app boundary.
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
- Invalid operation name format: {operation_name}. Expected fo
- vertex_project is required for Vertex AI video generation. S
- No operation name in Veo response: {response_data}
- Video generation is not complete yet. Please check status wi
- No video data found in completed operation
AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18).
Data as JSON: /api/errors/c11ecd63a95987fc.
Report an issue: GitHub.