mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-23 05:34:41 +00:00
fix(hosted_vllm): stop rewriting the caller's video file part into video_url in place
hosted_vllm converted a video file part into video_url by assigning into the caller's own content list, so a Router fallback to another provider received a part it does not read and the video was dropped without an error. Build a new content list for the vLLM request instead
This commit is contained in:
parent
a5a86cdda7
commit
6c37fbe4ba
2 changed files with 39 additions and 9 deletions
|
|
@ -21,6 +21,7 @@ from litellm.types.llms.openai import (
|
|||
ChatCompletionToolCallFunctionChunk,
|
||||
ChatCompletionVideoObject,
|
||||
ChatCompletionVideoUrlObject,
|
||||
OpenAIMessageContentListBlock,
|
||||
)
|
||||
|
||||
from ....utils import remove_additional_properties, remove_strict_from_schema
|
||||
|
|
@ -142,6 +143,12 @@ class HostedVLLMChatConfig(OpenAIGPTConfig):
|
|||
return ChatCompletionVideoObject(type="video_url", video_url=ChatCompletionVideoUrlObject(url=file_data))
|
||||
raise ValueError("file_id or file_data is required")
|
||||
|
||||
def _video_file_as_video_url(self, content_item: OpenAIMessageContentListBlock) -> OpenAIMessageContentListBlock:
|
||||
if content_item.get("type") != "file":
|
||||
return content_item
|
||||
file_item: Final = cast(ChatCompletionFileObject, content_item)
|
||||
return self._convert_file_to_video_url(file_item) if self._is_video_file(file_item) else content_item
|
||||
|
||||
@overload
|
||||
def _transform_messages(
|
||||
self, messages: list[AllMessageValues], model: str, is_async: Literal[True]
|
||||
|
|
@ -224,14 +231,7 @@ class HostedVLLMChatConfig(OpenAIGPTConfig):
|
|||
elif message["role"] == "user":
|
||||
message_content = message.get("content")
|
||||
if message_content and isinstance(message_content, list):
|
||||
replaced_content_items: list[tuple[int, ChatCompletionFileObject]] = []
|
||||
for idx, content_item in enumerate(message_content):
|
||||
if content_item.get("type") == "file":
|
||||
content_item = cast(ChatCompletionFileObject, content_item)
|
||||
if self._is_video_file(content_item):
|
||||
replaced_content_items.append((idx, content_item))
|
||||
for idx, content_item in replaced_content_items:
|
||||
message_content[idx] = self._convert_file_to_video_url(content_item)
|
||||
message["content"] = [self._video_file_as_video_url(item) for item in message_content]
|
||||
|
||||
if is_async:
|
||||
return super()._transform_messages(messages, model, is_async=cast(Literal[True], True))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
import respx
|
||||
|
||||
|
||||
import litellm
|
||||
from litellm.constants import (
|
||||
DEFAULT_REASONING_EFFORT_HIGH_THINKING_BUDGET,
|
||||
DEFAULT_REASONING_EFFORT_LOW_THINKING_BUDGET,
|
||||
|
|
@ -43,6 +45,34 @@ def test_hosted_vllm_chat_transformation_file_url():
|
|||
]
|
||||
|
||||
|
||||
def test_hosted_vllm_video_file_conversion_leaves_caller_messages_untouched():
|
||||
video_file_part = {"type": "file", "file": {"file_id": "https://example.com/video.mp4", "format": "video/mp4"}}
|
||||
messages = [{"role": "user", "content": [{"type": "text", "text": "Describe this video"}, video_file_part]}]
|
||||
with respx.mock() as upstream:
|
||||
route = upstream.post("http://vllm.test/v1/chat/completions").mock(
|
||||
return_value=httpx.Response(
|
||||
200,
|
||||
json={
|
||||
"id": "chatcmpl-1",
|
||||
"object": "chat.completion",
|
||||
"created": 1,
|
||||
"model": "qwen-vl",
|
||||
"choices": [
|
||||
{"index": 0, "message": {"role": "assistant", "content": "A cat."}, "finish_reason": "stop"}
|
||||
],
|
||||
"usage": {"prompt_tokens": 1, "completion_tokens": 1, "total_tokens": 2},
|
||||
},
|
||||
)
|
||||
)
|
||||
litellm.completion(model="hosted_vllm/qwen-vl", messages=messages, api_base="http://vllm.test/v1")
|
||||
|
||||
assert json.loads(route.calls.last.request.content)["messages"][0]["content"][1] == {
|
||||
"type": "video_url",
|
||||
"video_url": {"url": "https://example.com/video.mp4"},
|
||||
}
|
||||
assert messages[0]["content"][1] is video_file_part
|
||||
|
||||
|
||||
def test_hosted_vllm_supports_reasoning_effort():
|
||||
config = HostedVLLMChatConfig()
|
||||
supported_params = config.get_supported_openai_params(model="hosted_vllm/gpt-oss-120b")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue