[Feat] Add create character endpoints and other new videos Endpoints#23737
[Feat] Add create character endpoints and other new videos Endpoints#23737Sameerlite merged 18 commits intomainfrom
Conversation
Support missing base64 padding in managed character/video IDs so copied encoded IDs still decode to the original upstream character ID. Made-with: Cursor
Use typed character response models and video multipart helpers so /videos/characters forwards uploaded MP4 files with video/* content type. Made-with: Cursor
Avoid the temporary Any alias and use a concrete FileTypes import compatible with type checks. Made-with: Cursor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR adds four new video-related endpoints to LiteLLM — create character, get character, edit video, and extend video — wiring them end-to-end from the FastAPI proxy layer through the router, the Key concerns found:
Confidence Score: 3/5
|
| Filename | Overview |
|---|---|
| litellm/llms/openai/videos/transformation.py | Implements all 8 new transform methods for OpenAI; good character-ID decoding and MIME-type detection. Critical issue: model_id is hard-coded as None when encoding the returned video ID in edit/extension responses, breaking multi-deployment router resolution for follow-up requests. |
| litellm/llms/custom_httpx/llm_http_handler.py | Adds sync and async handlers for character create/get, video edit, and video extension; all four handler pairs correctly call raise_for_status() before parsing the response, consistent with existing error handling patterns. |
| litellm/proxy/video_endpoints/endpoints.py | Adds 4 new proxy endpoints; video_create_character uses _read_request_body correctly, but video_edit and video_extension use raw orjson.loads without error handling (will 500 on invalid JSON instead of 400), and both silently accept empty video_id strings. |
| litellm/types/videos/main.py | Adds CharacterObject, VideoEditRequestParams, VideoExtensionRequestParams; fragile FileTypes import changed to openai.types.audio.transcription_create_params (unrelated OpenAI internal module) with a # type: ignore, risking ImportError on OpenAI package updates. |
| litellm/types/videos/utils.py | Adds encode/decode helpers for character IDs mirroring the video ID pattern; also fixes a pre-existing base64 padding issue in decode_video_id_with_provider. The replace-based prefix stripping is safe here since standard base64 cannot contain underscores. |
| litellm/videos/main.py | Adds avideo_create_character, video_create_character, avideo_get_character, video_get_character, avideo_edit, video_edit, avideo_extension, video_extension; pattern is consistent with existing video functions. Provider is correctly auto-detected from encoded IDs in edit/extension. |
Sequence Diagram
sequenceDiagram
participant Client
participant ProxyEndpoint as Proxy Endpoint<br/>(endpoints.py)
participant Router as LLM Router<br/>(router.py)
participant LiteLLMSDK as LiteLLM SDK<br/>(videos/main.py)
participant HTTPHandler as BaseLLMHTTPHandler<br/>(llm_http_handler.py)
participant ProviderConfig as OpenAIVideoConfig<br/>(transformation.py)
participant Provider as OpenAI API
Note over Client,Provider: Create Character Flow
Client->>ProxyEndpoint: POST /v1/videos/characters (multipart: video + name)
ProxyEndpoint->>ProxyEndpoint: _read_request_body → data{name, video, custom_llm_provider}
ProxyEndpoint->>Router: avideo_create_character(**data)
Router->>LiteLLMSDK: video_create_character(name, video, ...)
LiteLLMSDK->>HTTPHandler: video_create_character_handler(...)
HTTPHandler->>ProviderConfig: transform_video_create_character_request(name, video, ...)
ProviderConfig-->>HTTPHandler: (url, files_list)
HTTPHandler->>Provider: POST /videos/characters (multipart)
Provider-->>HTTPHandler: { id, object, created_at, name }
HTTPHandler->>ProviderConfig: transform_video_create_character_response(...)
ProviderConfig-->>HTTPHandler: CharacterObject
HTTPHandler-->>ProxyEndpoint: CharacterObject
ProxyEndpoint->>ProxyEndpoint: encode_character_id_in_response() if target_model_names
ProxyEndpoint-->>Client: CharacterObject { id: "character_<base64>" }
Note over Client,Provider: Video Edit/Extension Flow
Client->>ProxyEndpoint: POST /v1/videos/edits { video: {id}, prompt }
ProxyEndpoint->>ProxyEndpoint: orjson.loads(body) → data
ProxyEndpoint->>ProxyEndpoint: decode_video_id_with_provider(video.id) → provider, model_id
ProxyEndpoint->>Router: avideo_edit(**data)
Router->>LiteLLMSDK: video_edit(video_id, prompt, ...)
LiteLLMSDK->>HTTPHandler: video_edit_handler(...)
HTTPHandler->>ProviderConfig: transform_video_edit_request(prompt, video_id, ...)
ProviderConfig->>ProviderConfig: extract_original_video_id(video_id)
ProviderConfig-->>HTTPHandler: (url, { prompt, video: {id: raw_id} })
HTTPHandler->>Provider: POST /videos/edits
Provider-->>HTTPHandler: VideoObject
HTTPHandler->>ProviderConfig: transform_video_edit_response(...)
ProviderConfig->>ProviderConfig: encode_video_id_with_provider(id, provider, None ⚠️)
ProviderConfig-->>HTTPHandler: VideoObject { id: "video_<base64, no model_id>" }
HTTPHandler-->>ProxyEndpoint: VideoObject
ProxyEndpoint-->>Client: VideoObject
Last reviewed commit: 1a6eb01
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| ) -> Tuple[str, list]: | ||
| """ | ||
| Transform the video create character request into a URL and files list (multipart). | ||
|
|
||
| Returns: | ||
| Tuple[str, list]: (url, files_list) for the multipart POST request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_create_character_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| ) -> CharacterObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_get_character_request( | ||
| self, | ||
| character_id: str, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| ) -> Tuple[str, Dict]: | ||
| """ | ||
| Transform the video get character request into a URL and params. | ||
|
|
||
| Returns: | ||
| Tuple[str, Dict]: (url, params) for the GET request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_get_character_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| ) -> CharacterObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_edit_request( | ||
| self, | ||
| prompt: str, | ||
| video_id: str, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| extra_body: Optional[Dict[str, Any]] = None, | ||
| ) -> Tuple[str, Dict]: | ||
| """ | ||
| Transform the video edit request into a URL and JSON data. | ||
|
|
||
| Returns: | ||
| Tuple[str, Dict]: (url, data) for the POST request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_edit_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| custom_llm_provider: Optional[str] = None, | ||
| ) -> VideoObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_extension_request( | ||
| self, | ||
| prompt: str, | ||
| video_id: str, | ||
| seconds: str, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| extra_body: Optional[Dict[str, Any]] = None, | ||
| ) -> Tuple[str, Dict]: | ||
| """ | ||
| Transform the video extension request into a URL and JSON data. | ||
|
|
||
| Returns: | ||
| Tuple[str, Dict]: (url, data) for the POST request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_extension_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| custom_llm_provider: Optional[str] = None, | ||
| ) -> VideoObject: | ||
| pass | ||
|
|
||
| def get_error_class( |
There was a problem hiding this comment.
New abstract methods break third-party provider backward compatibility
Eight new @abstractmethod entries have been added to BaseVideoConfig. Python's ABC enforcement means any third-party class that subclasses BaseVideoConfig without implementing all eight methods will now fail to instantiate at import time (raising TypeError: Can't instantiate abstract class ... with abstract method(s) ...).
The internal providers (Gemini, RunwayML, VertexAI) work around this by adding stubs that raise NotImplementedError, but external integrators who have their own BaseVideoConfig subclasses will have their production systems broken the moment they upgrade LiteLLM.
The project's custom rule explicitly warns against backward-incompatible changes. Consider using concrete (non-abstract) base methods that raise NotImplementedError instead:
def transform_video_create_character_request(self, name, video, api_base, litellm_params, headers):
raise NotImplementedError("video create character is not supported for this provider")
def transform_video_create_character_response(self, raw_response, logging_obj):
raise NotImplementedError("video create character is not supported for this provider")
# ... same pattern for all 8 new methodsThis keeps BaseVideoConfig concrete and gives a clear runtime error only when the unsupported operation is actually called, rather than failing at instantiation time.
|
|
||
|
|
||
| class CharacterObject(BaseModel): | ||
| """Represents a character created from a video.""" | ||
|
|
||
| id: str | ||
| object: Literal["character"] = "character" | ||
| created_at: int | ||
| name: str | ||
| _hidden_params: Dict[str, Any] = {} | ||
|
|
||
| def __contains__(self, key): | ||
| return hasattr(self, key) | ||
|
|
There was a problem hiding this comment.
_hidden_params is a shared mutable class-level default
In Pydantic v2 BaseModel, attributes whose names begin with _ are not model fields — they are ordinary Python class attributes. As written, _hidden_params: Dict[str, Any] = {} is a single dict shared by all CharacterObject instances:
a = CharacterObject(id="c1", object="character", created_at=1, name="A")
b = CharacterObject(id="c2", object="character", created_at=2, name="B")
a._hidden_params["key"] = "value"
print(b._hidden_params) # {"key": "value"} — shared reference!This is the classic mutable class-level default bug. The correct Pydantic approach is PrivateAttr:
| class CharacterObject(BaseModel): | |
| """Represents a character created from a video.""" | |
| id: str | |
| object: Literal["character"] = "character" | |
| created_at: int | |
| name: str | |
| _hidden_params: Dict[str, Any] = {} | |
| def __contains__(self, key): | |
| return hasattr(self, key) | |
| _hidden_params: Dict[str, Any] = PrivateAttr(default_factory=dict) |
You'll also need to add from pydantic import PrivateAttr to the imports.
| api_key="", | ||
| additional_args={ | ||
| "complete_input_dict": {"name": name}, | ||
| "api_base": url, | ||
| "headers": headers, | ||
| }, | ||
| ) | ||
|
|
||
| try: | ||
| response = sync_httpx_client.post( | ||
| url=url, |
There was a problem hiding this comment.
No HTTP error check before parsing response body
All four new sync/async handler pairs (video_create_character_handler, video_get_character_handler, video_edit_handler, video_extension_handler) call transform_*_response(raw_response=response, ...) immediately after the HTTP call, without first checking whether the response was successful.
Because httpx does not raise on 4xx/5xx by default, a provider error response (e.g., {"error": "unauthorized"}) will be passed directly to CharacterObject(**raw_response.json()) or VideoObject(**raw_response.json()), causing a Pydantic ValidationError rather than a meaningful HTTP error. The subsequent except Exception as e handler may not translate this into an actionable error message for callers.
Add response.raise_for_status() before the transform call in all four new handler implementations (both sync and async variants). Example for video_create_character_handler:
response = sync_httpx_client.post(
url=url,
headers=headers,
files=files_list,
timeout=timeout,
)
response.raise_for_status() # raises httpx.HTTPStatusError on 4xx/5xx
return video_provider_config.transform_video_create_character_response(
raw_response=response,
logging_obj=logging_obj,
)This same fix applies to:
async_video_create_character_handlervideo_get_character_handler/async_video_get_character_handlervideo_edit_handler/async_video_edit_handlervideo_extension_handler/async_video_extension_handler
| "avideo_status", | ||
| "avideo_content", | ||
| "avideo_remix", | ||
| "avideo_edit", | ||
| "avideo_extension", |
There was a problem hiding this comment.
avideo_create_character and avideo_get_character not included in router-first routing
The block that enables router-assisted model resolution (when an encoded ID embeds a model_id) lists avideo_edit and avideo_extension but omits avideo_create_character and avideo_get_character.
For avideo_get_character, the endpoint decodes the character_id to extract model_id_from_decoded and already calls llm_router.resolve_model_name_from_model_id inline (in endpoints.py), so the model is resolved before the router dispatch. However, avideo_create_character never resolves via the router even when a target_model_names-derived model is set. If there are multiple deployments of the same model, the router should still be given the chance to pick the right one. Consistency with the other video endpoints suggests these two should also be listed here:
"avideo_edit",
"avideo_extension",
"avideo_create_character", # add
"avideo_get_character", # add… issues - Remove duplicate DecodedCharacterId TypedDict from litellm/types/videos/main.py - Remove dead LITELLM_MANAGED_VIDEO_CHARACTER_COMPLETE_STR constant from litellm/types/utils.py - Add FastAPI Form validation for name field in video_create_character endpoint Made-with: Cursor
31198b4 to
1ccf67d
Compare
…n video handlers Add response.raise_for_status() before transform_*_response() calls in all eight video character/edit/extension handler methods (sync and async): - video_create_character_handler / async_video_create_character_handler - video_get_character_handler / async_video_get_character_handler - video_edit_handler / async_video_edit_handler - video_extension_handler / async_video_extension_handler Without these checks, httpx does not raise on 4xx/5xx responses, so provider errors (e.g., 401 Unauthorized) pass directly to Pydantic model constructors, causing ValidationError instead of meaningful HTTP errors. The raise_for_status() ensures the exception handler receives proper HTTPStatusError for translation into actionable messages. Made-with: Cursor
…r in router-first routing Add avideo_create_character and avideo_get_character to the list of video endpoints that use router-first routing when a model is provided (either from decoded IDs or target_model_names). Previously only avideo_edit and avideo_extension were in the router-first block. This ensures both character endpoints benefit from multi-deployment load balancing and model resolution, making them consistent with the other video operations. This allows: - avideo_create_character: Router picks among multiple deployments when target_model_names is set - avideo_get_character: Router assists with multi-model environments for consistency Made-with: Cursor
- Clear examples for SDK and proxy usage - Feature highlights: router support, encoding, error handling - Best practices for character uploads and prompting - Available from LiteLLM v1.83.0+ - Troubleshooting guide for common issues Made-with: Cursor
- Add curl examples for avideo_edit and avideo_extension APIs - Explain how LiteLLM encodes/decodes managed character IDs - Show metadata included in character IDs (provider, model_id) - Detail transparent router-first routing benefits Made-with: Cursor
…tion test Add avideo_create_character, avideo_get_character, avideo_edit, and avideo_extension to the skip condition since Azure video calls don't use initialize_azure_sdk_client. Tests now properly skip with expected behavior instead of failing: - test_ensure_initialize_azure_sdk_client_always_used[avideo_create_character] ✓ - test_ensure_initialize_azure_sdk_client_always_used[avideo_get_character] ✓ - test_ensure_initialize_azure_sdk_client_always_used[avideo_edit] ✓ - test_ensure_initialize_azure_sdk_client_always_used[avideo_extension] ✓ Made-with: Cursor
| @@ -1,10 +1,9 @@ | |||
| from typing import Any, Dict, List, Literal, Optional | |||
|
|
|||
| from openai.types.audio.transcription_create_params import FileTypes # type: ignore | |||
There was a problem hiding this comment.
FileTypes imported from unrelated OpenAI audio module
FileTypes is now imported from openai.types.audio.transcription_create_params, which is an OpenAI-internal module intended for audio transcription — entirely unrelated to video. The original import was from litellm.types.utils import FileTypes. The # type: ignore comment is a signal that this import is already fragile.
OpenAI has historically reorganized internal module paths between minor versions. If that happens here, this import will break at runtime with an ImportError. Prefer the stable litellm-internal source:
| from openai.types.audio.transcription_create_params import FileTypes # type: ignore | |
| from litellm.types.utils import FileTypes # type: ignore |
| ) -> VideoObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_create_character_request( | ||
| self, | ||
| name: str, | ||
| video: Any, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| ) -> Tuple[str, list]: | ||
| """ | ||
| Transform the video create character request into a URL and files list (multipart). | ||
|
|
||
| Returns: | ||
| Tuple[str, list]: (url, files_list) for the multipart POST request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_create_character_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| ) -> CharacterObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_get_character_request( | ||
| self, | ||
| character_id: str, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| ) -> Tuple[str, Dict]: | ||
| """ | ||
| Transform the video get character request into a URL and params. | ||
|
|
||
| Returns: | ||
| Tuple[str, Dict]: (url, params) for the GET request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_get_character_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| ) -> CharacterObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_edit_request( | ||
| self, | ||
| prompt: str, | ||
| video_id: str, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| extra_body: Optional[Dict[str, Any]] = None, | ||
| ) -> Tuple[str, Dict]: | ||
| """ | ||
| Transform the video edit request into a URL and JSON data. | ||
|
|
||
| Returns: | ||
| Tuple[str, Dict]: (url, data) for the POST request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_edit_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| custom_llm_provider: Optional[str] = None, | ||
| ) -> VideoObject: | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_extension_request( | ||
| self, | ||
| prompt: str, | ||
| video_id: str, | ||
| seconds: str, | ||
| api_base: str, | ||
| litellm_params: GenericLiteLLMParams, | ||
| headers: dict, | ||
| extra_body: Optional[Dict[str, Any]] = None, | ||
| ) -> Tuple[str, Dict]: | ||
| """ | ||
| Transform the video extension request into a URL and JSON data. | ||
|
|
||
| Returns: | ||
| Tuple[str, Dict]: (url, data) for the POST request | ||
| """ | ||
| pass | ||
|
|
||
| @abstractmethod | ||
| def transform_video_extension_response( | ||
| self, | ||
| raw_response: httpx.Response, | ||
| logging_obj: LiteLLMLoggingObj, | ||
| custom_llm_provider: Optional[str] = None, | ||
| ) -> VideoObject: | ||
| pass | ||
|
|
||
| def get_error_class( |
There was a problem hiding this comment.
New abstract methods break third-party BaseVideoConfig subclasses
Eight new @abstractmethod entries have been added to BaseVideoConfig. Any third-party class that subclasses BaseVideoConfig without implementing all eight methods will now fail to instantiate at import time with a TypeError: Can't instantiate abstract class. The internal providers (Gemini, RunwayML, VertexAI) work around this by adding raise NotImplementedError(...) stubs, but external integrators will silently break the moment they upgrade LiteLLM.
This violates the project's backward-compatibility rule. Use concrete (non-abstract) methods that raise NotImplementedError instead:
def transform_video_create_character_request(self, name, video, api_base, litellm_params, headers):
raise NotImplementedError("video create character is not supported for this provider")Apply the same pattern to all eight new methods.
Rule Used: What: avoid backwards-incompatible changes without... (source)
…sion methods Convert all 8 new video methods from @AbstractMethod to concrete implementations that raise NotImplementedError. This prevents breaking external third-party BaseVideoConfig subclasses at import time. Methods affected: - transform_video_create_character_request/response - transform_video_get_character_request/response - transform_video_edit_request/response - transform_video_extension_request/response External integrators can now upgrade without instantiation errors; NotImplementedError is only raised when operations are actually called on unsupported providers. This restores backward compatibility with the project's policy. Made-with: Cursor
| raw_response: httpx.Response, | ||
| logging_obj: Any, | ||
| ) -> CharacterObject: | ||
| return CharacterObject(**raw_response.json()) | ||
|
|
||
| def transform_video_edit_request( | ||
| self, | ||
| prompt: str, | ||
| video_id: str, | ||
| api_base: str, |
There was a problem hiding this comment.
model_id always None in edit/extension response video ID
transform_video_edit_response and transform_video_extension_response both call encode_video_id_with_provider(video_obj.id, custom_llm_provider, None), hard-coding model_id=None. This means the encoded video ID returned to the caller will not carry any model/deployment information.
For multi-deployment scenarios, a client that later uses this returned video ID for a follow-up operation (another edit, an extension, or a status check) will not be able to route back to the same deployment via resolve_model_name_from_model_id, since the model ID was never stored. The litellm_params object is available in the HTTP handler and contains the deployment's model_id — consider threading it into the transform methods and using it here:
def transform_video_edit_response(
self,
raw_response: httpx.Response,
logging_obj: Any,
custom_llm_provider: Optional[str] = None,
model_id: Optional[str] = None, # add parameter
) -> VideoObject:
video_obj = VideoObject(**raw_response.json())
if custom_llm_provider and video_obj.id:
video_obj.id = encode_video_id_with_provider(
video_obj.id, custom_llm_provider, model_id # use model_id
)
return video_objApply the same fix to transform_video_extension_response.
| fastapi_response=fastapi_response, | ||
| user_api_key_dict=user_api_key_dict, | ||
| route_type="avideo_get_character", | ||
| proxy_logging_obj=proxy_logging_obj, | ||
| llm_router=llm_router, | ||
| general_settings=general_settings, |
There was a problem hiding this comment.
Raw orjson.loads lacks error handling for invalid JSON
video_edit and video_extension parse the request body directly with orjson.loads(body), while the video_create_character endpoint in the same file uses _read_request_body(request=request). The _read_request_body utility includes:
- Proper HTTP 400 error responses via
ProxyExceptionon invalid JSON - Body caching to prevent double-reads
- Fallback
json.loadswith surrogate-pair cleanup
With raw orjson.loads, a malformed request body will raise an unhandled orjson.JSONDecodeError that surfaces as a 500 error to the caller instead of a proper 400. It also means these endpoints miss the body-caching mechanism used by every other proxy endpoint.
Replace both body-parsing blocks with _read_request_body:
data = await _read_request_body(request=request)Then keep the video_ref = data.pop("video", {}) extraction unchanged below it.
| proxy_config=proxy_config, | ||
| select_data_generator=select_data_generator, | ||
| model=None, | ||
| user_model=user_model, |
There was a problem hiding this comment.
Silent empty video_id when video.id is absent
If the caller omits the id field inside the video object, or sends "video": {}, video_id is silently set to an empty string "":
video_ref = data.pop("video", {})
video_id = video_ref.get("id", "") if isinstance(video_ref, dict) else ""
data["video_id"] = video_idThis empty string is forwarded all the way to the provider without any validation, which will produce a confusing provider error rather than a clear 422. The same pattern is repeated in video_extension. Consider adding an early check:
| proxy_config=proxy_config, | |
| select_data_generator=select_data_generator, | |
| model=None, | |
| user_model=user_model, | |
| video_ref = data.pop("video", {}) | |
| video_id = video_ref.get("id", "") if isinstance(video_ref, dict) else "" | |
| if not video_id: | |
| raise ProxyException( | |
| message="'video.id' is required for video edits", | |
| type="invalid_request_error", | |
| param="video.id", | |
| code=status.HTTP_422_UNPROCESSABLE_ENTITY, | |
| ) | |
| data["video_id"] = video_id |
| @@ -1,10 +1,9 @@ | |||
| from typing import Any, Dict, List, Literal, Optional | |||
|
|
|||
| from openai.types.audio.transcription_create_params import FileTypes # type: ignore | |||
There was a problem hiding this comment.
Fragile FileTypes import from unrelated OpenAI audio module
FileTypes is now imported from openai.types.audio.transcription_create_params — an internal module for audio transcription that is unrelated to video. The original import was from litellm.types.utils import FileTypes. The # type: ignore comment is already a signal that this path is fragile.
OpenAI has restructured internal module paths between minor versions before. If that happens here this import will break at runtime with an ImportError that has no graceful fallback.
Revert to the stable internal source:
| from openai.types.audio.transcription_create_params import FileTypes # type: ignore | |
| from litellm.types.utils import FileTypes |
Relevant issues
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/test_litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unit@greptileaiand received a Confidence Score of at least 4/5 before requesting a maintainer reviewDelays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
CI (LiteLLM team)
Branch creation CI run
Link:
CI run for the last commit
Link:
Merge / cherry-pick CI run
Links:
Type
🆕 New Feature
🐛 Bug Fix
🧹 Refactoring
📖 Documentation
🚄 Infrastructure
✅ Test
Changes