mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-28 08:20:19 +00:00
Fix: Passing of image and parameters in videos api
This commit is contained in:
@@ -119,6 +119,12 @@ class VertexAIVideoConfig(BaseVideoConfig, VertexBase):
|
||||
# Map input_reference to image (will be processed in transform_video_create_request)
|
||||
if "input_reference" in video_create_optional_params:
|
||||
mapped_params["image"] = video_create_optional_params["input_reference"]
|
||||
elif "image" in video_create_optional_params:
|
||||
mapped_params["image"] = video_create_optional_params["image"]
|
||||
|
||||
# Pass through a provider-specific parameters block if provided directly
|
||||
if "parameters" in video_create_optional_params:
|
||||
mapped_params["parameters"] = video_create_optional_params["parameters"]
|
||||
|
||||
# Map size to aspectRatio
|
||||
if "size" in video_create_optional_params:
|
||||
@@ -270,7 +276,17 @@ class VertexAIVideoConfig(BaseVideoConfig, VertexBase):
|
||||
instance_dict.update(params_copy["instances"])
|
||||
params_copy.pop("instances")
|
||||
elif "image" in params_copy and params_copy["image"] is not None:
|
||||
image_data = _convert_image_to_vertex_format(params_copy["image"])
|
||||
image = 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}
|
||||
else:
|
||||
# File-like object — encode to base64
|
||||
image_data = _convert_image_to_vertex_format(image)
|
||||
instance_dict["image"] = image_data
|
||||
params_copy.pop("image")
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from typing import Any, Dict, List, Literal, Optional
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from pydantic import BaseModel
|
||||
from typing_extensions import TypedDict
|
||||
|
||||
from litellm.types.utils import FileTypes
|
||||
|
||||
|
||||
@@ -72,6 +73,8 @@ class VideoCreateOptionalRequestParams(TypedDict, total=False):
|
||||
Params here: https://platform.openai.com/docs/api-reference/videos/create
|
||||
"""
|
||||
input_reference: Optional[FileTypes] # File reference for input image
|
||||
image: Optional[Any] # Image for image-to-video; dict with gcsUri/bytesBase64Encoded, or file-like object
|
||||
parameters: Optional[Dict[str, Any]] # Provider-specific parameters block passed directly to the API
|
||||
model: Optional[str]
|
||||
seconds: Optional[str]
|
||||
size: Optional[str]
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
"""
|
||||
Tests for Vertex AI (Veo) video generation transformation.
|
||||
"""
|
||||
import base64
|
||||
import json
|
||||
import os
|
||||
import pytest
|
||||
from unittest.mock import Mock, MagicMock, patch
|
||||
from unittest.mock import MagicMock, Mock, patch
|
||||
|
||||
import httpx
|
||||
import base64
|
||||
import pytest
|
||||
|
||||
from litellm.llms.vertex_ai.videos.transformation import (
|
||||
VertexAIVideoConfig,
|
||||
_convert_image_to_vertex_format,
|
||||
)
|
||||
from litellm.types.videos.main import VideoObject
|
||||
from litellm.types.router import GenericLiteLLMParams
|
||||
from litellm.types.videos.main import VideoObject
|
||||
|
||||
|
||||
class TestVertexAIVideoConfig:
|
||||
@@ -548,3 +549,170 @@ class TestConvertImageToVertexFormat:
|
||||
decoded = base64.b64decode(result["bytesBase64Encoded"])
|
||||
assert decoded == fake_image_data
|
||||
|
||||
|
||||
class TestImageAndParametersPassthrough:
|
||||
"""
|
||||
Tests that image (gcsUri / bare gs:// / file-like) and a pre-built
|
||||
parameters dict are correctly forwarded through map_openai_params and
|
||||
transform_video_create_request.
|
||||
"""
|
||||
|
||||
def setup_method(self):
|
||||
self.config = VertexAIVideoConfig()
|
||||
self.api_base = (
|
||||
"https://us-central1-aiplatform.googleapis.com/v1/projects/"
|
||||
"test-project/locations/us-central1/publishers/google/models/veo-002"
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# map_openai_params #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_map_openai_params_passes_image_dict(self):
|
||||
"""image dict (gcsUri format) is forwarded as-is."""
|
||||
image = {"gcsUri": "gs://my-bucket/boardwalk.jpg"}
|
||||
mapped = self.config.map_openai_params(
|
||||
video_create_optional_params={"image": image},
|
||||
model="veo-002",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["image"] == image
|
||||
|
||||
def test_map_openai_params_passes_parameters_dict(self):
|
||||
"""A pre-built parameters dict is forwarded as-is."""
|
||||
params = {"sampleCount": 1, "videoLengthSeconds": 5, "aspectRatio": "16:9"}
|
||||
mapped = self.config.map_openai_params(
|
||||
video_create_optional_params={"parameters": params},
|
||||
model="veo-002",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["parameters"] == params
|
||||
|
||||
def test_map_openai_params_input_reference_takes_priority_over_image(self):
|
||||
"""input_reference wins over a directly passed image key."""
|
||||
mock_file = Mock()
|
||||
image_dict = {"gcsUri": "gs://my-bucket/other.jpg"}
|
||||
mapped = self.config.map_openai_params(
|
||||
video_create_optional_params={
|
||||
"input_reference": mock_file,
|
||||
"image": image_dict,
|
||||
},
|
||||
model="veo-002",
|
||||
drop_params=False,
|
||||
)
|
||||
assert mapped["image"] is mock_file
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# transform_video_create_request – image forms #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_transform_request_image_gcs_uri_dict(self):
|
||||
"""image passed as {"gcsUri": "gs://..."} is placed in instances as-is."""
|
||||
image = {"gcsUri": "gs://my-bucket/boardwalk.jpg"}
|
||||
data, _, url = self.config.transform_video_create_request(
|
||||
model="veo-002",
|
||||
prompt="Cinematic drone shot",
|
||||
api_base=self.api_base,
|
||||
video_create_optional_request_params={"image": image},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert data["instances"][0]["image"] == image
|
||||
assert url.endswith(":predictLongRunning")
|
||||
|
||||
def test_transform_request_image_bare_gs_uri_string(self):
|
||||
"""A bare gs:// string is wrapped in {"gcsUri": ...} without downloading."""
|
||||
gs_uri = "gs://my-bucket/boardwalk.jpg"
|
||||
data, _, _ = self.config.transform_video_create_request(
|
||||
model="veo-002",
|
||||
prompt="Cinematic drone shot",
|
||||
api_base=self.api_base,
|
||||
video_create_optional_request_params={"image": gs_uri},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert data["instances"][0]["image"] == {"gcsUri": gs_uri}
|
||||
|
||||
def test_transform_request_image_bytes_base64_dict(self):
|
||||
"""image already in bytesBase64Encoded format is passed through unchanged."""
|
||||
image = {"bytesBase64Encoded": "abc123", "mimeType": "image/jpeg"}
|
||||
data, _, _ = self.config.transform_video_create_request(
|
||||
model="veo-002",
|
||||
prompt="Cinematic drone shot",
|
||||
api_base=self.api_base,
|
||||
video_create_optional_request_params={"image": image},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert data["instances"][0]["image"] == image
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# transform_video_create_request – parameters dict #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_transform_request_parameters_dict_not_double_nested(self):
|
||||
"""A pre-built parameters dict becomes request_data["parameters"] directly."""
|
||||
params = {"sampleCount": 1, "videoLengthSeconds": 5, "aspectRatio": "16:9"}
|
||||
data, _, _ = self.config.transform_video_create_request(
|
||||
model="veo-002",
|
||||
prompt="Cinematic drone shot",
|
||||
api_base=self.api_base,
|
||||
video_create_optional_request_params={"parameters": params},
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
assert data["parameters"] == params
|
||||
# Must NOT be double-nested
|
||||
assert "parameters" not in data["parameters"]
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# Full user scenario #
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
def test_transform_request_full_user_scenario(self):
|
||||
"""
|
||||
Reproduces the exact user request:
|
||||
image: {"gcsUri": "gs://your-bucket-name/path/to/boardwalk.jpg"}
|
||||
parameters: {"sampleCount": 1, "videoLengthSeconds": 5,
|
||||
"aspectRatio": "16:9", "storageUri": "gs://test/outputs/"}
|
||||
"""
|
||||
image = {"gcsUri": "gs://your-bucket-name/path/to/boardwalk.jpg"}
|
||||
parameters = {
|
||||
"sampleCount": 1,
|
||||
"videoLengthSeconds": 5,
|
||||
"aspectRatio": "16:9",
|
||||
"storageUri": "gs://test/outputs/",
|
||||
}
|
||||
|
||||
# Simulate the full pipeline: map_openai_params → transform_video_create_request
|
||||
mapped = self.config.map_openai_params(
|
||||
video_create_optional_params={"image": image, "parameters": parameters},
|
||||
model="veo-3.1-generate-preview",
|
||||
drop_params=False,
|
||||
)
|
||||
|
||||
data, _, url = self.config.transform_video_create_request(
|
||||
model="veo-3.1-generate-preview",
|
||||
prompt="Cinematic drone shot moving forward along the beach boardwalk",
|
||||
api_base=self.api_base,
|
||||
video_create_optional_request_params=mapped,
|
||||
litellm_params=GenericLiteLLMParams(),
|
||||
headers={},
|
||||
)
|
||||
|
||||
# instances contains prompt + image
|
||||
assert len(data["instances"]) == 1
|
||||
instance = data["instances"][0]
|
||||
assert instance["prompt"] == "Cinematic drone shot moving forward along the beach boardwalk"
|
||||
assert instance["image"] == image
|
||||
|
||||
# parameters block is correct and not double-nested
|
||||
assert data["parameters"] == parameters
|
||||
assert "parameters" not in data["parameters"]
|
||||
|
||||
assert url.endswith(":predictLongRunning")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user