feat(videos): add variant parameter to video content download (#21955)

openai videos models support the features to download variants.
See more details here: https://developers.openai.com/api/docs/guides/video-generation#use-image-references.
Plumb variant (e.g. "thumbnail", "spritesheet") through the full
video content download chain: avideo_content → video_content →
video_content_handler → transform_video_content_request. OpenAI
appends ?variant=<value> to the GET URL; other providers accept
the parameter in their signature but ignore it.
This commit is contained in:
Lei Nie
2026-02-23 14:45:19 -08:00
committed by GitHub
parent 0ca9869b99
commit 4ff2cfd8d6
8 changed files with 100 additions and 6 deletions
@@ -118,10 +118,11 @@ class BaseVideoConfig(ABC):
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
variant: Optional[str] = None,
) -> Tuple[str, Dict]:
"""
Transform the video content request into a URL and data/params
Returns:
Tuple[str, Dict]: (url, params) for the video content request
"""
@@ -5400,6 +5400,7 @@ class BaseLLMHTTPHandler:
api_key: Optional[str] = None,
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
_is_async: bool = False,
variant: Optional[str] = None,
) -> Union[bytes, Coroutine[Any, Any, bytes]]:
"""
Handle video content download requests.
@@ -5415,6 +5416,7 @@ class BaseLLMHTTPHandler:
extra_headers=extra_headers,
api_key=api_key,
client=client,
variant=variant,
)
if client is None or not isinstance(client, HTTPHandler):
@@ -5446,6 +5448,7 @@ class BaseLLMHTTPHandler:
api_base=api_base,
litellm_params=litellm_params,
headers=headers,
variant=variant,
)
try:
@@ -5488,6 +5491,7 @@ class BaseLLMHTTPHandler:
extra_headers: Optional[Dict[str, Any]] = None,
api_key: Optional[str] = None,
client: Optional[Union[HTTPHandler, AsyncHTTPHandler]] = None,
variant: Optional[str] = None,
) -> bytes:
"""
Async version of the video content download handler.
@@ -5522,6 +5526,7 @@ class BaseLLMHTTPHandler:
api_base=api_base,
litellm_params=litellm_params,
headers=headers,
variant=variant,
)
try:
+2 -1
View File
@@ -393,10 +393,11 @@ class GeminiVideoConfig(BaseVideoConfig):
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
variant: Optional[str] = None,
) -> Tuple[str, Dict]:
"""
Transform the video content request for Veo API.
For Veo, we need to:
1. Get operation status to extract video URI
2. Return download URL for the video
+7 -3
View File
@@ -172,18 +172,22 @@ class OpenAIVideoConfig(BaseVideoConfig):
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
variant: Optional[str] = None,
) -> Tuple[str, Dict]:
"""
Transform the video content request for OpenAI API.
OpenAI API expects the following request:
- GET /v1/videos/{video_id}/content
- GET /v1/videos/{video_id}/content?variant=thumbnail
"""
original_video_id = extract_original_video_id(video_id)
# Construct the URL for video content download
url = f"{api_base.rstrip('/')}/{original_video_id}/content"
if variant is not None:
url = f"{url}?variant={variant}"
# No additional data needed for GET content request
data: Dict[str, Any] = {}
@@ -310,10 +310,11 @@ class RunwayMLVideoConfig(BaseVideoConfig):
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
variant: Optional[str] = None,
) -> Tuple[str, Dict]:
"""
Transform the video content request for RunwayML API.
RunwayML doesn't have a separate content download endpoint.
The video URL is returned in the task output field.
We'll retrieve the task and extract the video URL.
@@ -455,6 +455,7 @@ class VertexAIVideoConfig(BaseVideoConfig, VertexBase):
api_base: str,
litellm_params: GenericLiteLLMParams,
headers: dict,
variant: Optional[str] = None,
) -> Tuple[str, Dict]:
"""
Transform the video content request for Veo API.
+4
View File
@@ -273,6 +273,7 @@ def video_content(
video_id: str,
timeout: Optional[float] = None,
custom_llm_provider: Optional[str] = None,
variant: Optional[str] = None,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@@ -367,6 +368,7 @@ def video_content(
extra_headers=extra_headers,
client=kwargs.get("client"),
_is_async=_is_async,
variant=variant,
)
except Exception as e:
@@ -385,6 +387,7 @@ async def avideo_content(
video_id: str,
timeout: Optional[float] = None,
custom_llm_provider: Optional[str] = None,
variant: Optional[str] = None,
# Use the following arguments if you need to pass additional parameters to the API that aren't available via kwargs.
# The extra values given here take precedence over values defined on the client or passed to this method.
extra_headers: Optional[Dict[str, Any]] = None,
@@ -422,6 +425,7 @@ async def avideo_content(
video_id=video_id,
timeout=timeout,
custom_llm_provider=custom_llm_provider,
variant=variant,
extra_headers=extra_headers,
extra_query=extra_query,
extra_body=extra_body,
@@ -801,6 +801,83 @@ def test_openai_transform_video_content_request_empty_params():
assert params == {}
@pytest.mark.parametrize(
"variant,expected_suffix",
[
("thumbnail", "?variant=thumbnail"),
("spritesheet", "?variant=spritesheet"),
],
)
def test_openai_transform_video_content_request_with_variant(variant, expected_suffix):
"""OpenAI content transform should append ?variant= when variant is provided."""
config = OpenAIVideoConfig()
url, params = config.transform_video_content_request(
video_id="video_123",
api_base="https://api.openai.com/v1/videos",
litellm_params={},
headers={},
variant=variant,
)
assert url == f"https://api.openai.com/v1/videos/video_123/content{expected_suffix}"
assert params == {}
def test_openai_transform_video_content_request_variant_none_no_query_param():
"""OpenAI content transform should NOT append ?variant= when variant is None."""
config = OpenAIVideoConfig()
url, params = config.transform_video_content_request(
video_id="video_123",
api_base="https://api.openai.com/v1/videos",
litellm_params={},
headers={},
variant=None,
)
assert "variant" not in url
assert url == "https://api.openai.com/v1/videos/video_123/content"
def test_video_content_handler_passes_variant_to_url():
"""HTTP handler should pass variant through to the final URL."""
from litellm.llms.custom_httpx.http_handler import HTTPHandler
from litellm.types.router import GenericLiteLLMParams
if hasattr(litellm, "in_memory_llm_clients_cache"):
litellm.in_memory_llm_clients_cache.flush_cache()
handler = BaseLLMHTTPHandler()
config = OpenAIVideoConfig()
mock_client = MagicMock(spec=HTTPHandler)
mock_response = MagicMock()
mock_response.content = b"thumbnail-bytes"
mock_client.get.return_value = mock_response
with patch(
"litellm.llms.custom_httpx.llm_http_handler._get_httpx_client",
return_value=mock_client,
):
result = handler.video_content_handler(
video_id="video_abc",
video_content_provider_config=config,
custom_llm_provider="openai",
litellm_params=GenericLiteLLMParams(
api_base="https://api.openai.com/v1"
),
logging_obj=MagicMock(),
timeout=5.0,
api_key="sk-test",
client=mock_client,
_is_async=False,
variant="thumbnail",
)
assert result == b"thumbnail-bytes"
called_url = mock_client.get.call_args.kwargs["url"]
assert called_url == "https://api.openai.com/v1/videos/video_abc/content?variant=thumbnail"
def test_video_content_handler_uses_get_for_openai():
"""HTTP handler must use GET (not POST) for OpenAI content download."""
from litellm.llms.custom_httpx.http_handler import HTTPHandler