fix(bedrock): infer video content type from url extension

A remote video url served with a missing or generic content type
(binary/octet-stream) could not be resolved to a Bedrock video format, so the
request raised before it was built. The extension map only covered image and
document formats

Add the nine video extensions Converse accepts (mp4, mov, mkv, webm, flv, mpeg,
mpg, wmv, 3gp) to that map, so a remote clip resolves to a video block the same
way a data uri already does

Verified without network by composing the real helpers: a .mp4 url with a
binary/octet-stream header now yields video/mp4, Bedrock format mp4 and a video
block
This commit is contained in:
yuwk 2026-09-11 13:34:24 +08:00
parent 02f06e0782
commit b767b7cbf4
3 changed files with 42 additions and 7 deletions

View file

@ -1565,6 +1565,15 @@ def infer_content_type_from_url_and_content(
"png": "image/png",
"gif": "image/gif",
"webp": "image/webp",
"mp4": "video/mp4",
"mov": "video/mov",
"mkv": "video/mkv",
"webm": "video/webm",
"flv": "video/flv",
"mpeg": "video/mpeg",
"mpg": "video/mpg",
"wmv": "video/wmv",
"3gp": "video/3gp",
# Document formats
"pdf": "application/pdf",
"csv": "text/csv",

View file

@ -4372,9 +4372,6 @@ class BedrockConverseMessagesProcessor:
)
_parts.append(_part)
elif element["type"] == "video_url":
# see the sync path: video shares the image
# processor, which keys the block type off the
# mime type.
video_element = element["video_url"]
if isinstance(video_element, dict):
video_url = video_element["url"]
@ -4760,10 +4757,6 @@ def _bedrock_converse_messages_pt(
)
_parts.append(_part)
elif element["type"] == "video_url":
# OpenAI `video_url` parts must reach Converse as
# video blocks too. `process_image_sync` picks the
# block type from the mime type (video/* -> video,
# image/* -> image), so a video shares this path.
video_element = element["video_url"]
if isinstance(video_element, dict):
video_url = video_element["url"]

View file

@ -19,6 +19,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
get_format_from_file_id,
handle_any_messages_to_chat_completion_str_messages_conversion,
hoist_images_from_tool_messages,
infer_content_type_from_url_and_content,
is_encrypted_reasoning_block,
responses_reasoning_items_from_thinking_blocks,
split_concatenated_json_objects,
@ -1813,3 +1814,35 @@ class TestEncryptedReasoningReplay:
strip_encrypted_reasoning_from_messages(messages)
assert messages == before
@pytest.mark.parametrize(
"url,expected",
[
("https://example.com/clip.mp4", "video/mp4"),
("https://example.com/clip.webm?X-Amz-Signature=abc123", "video/webm"),
("https://example.com/clip.mov", "video/mov"),
("https://example.com/clip.mkv", "video/mkv"),
("https://example.com/clip.3gp", "video/3gp"),
],
)
def test_infer_video_content_type_from_url_extension(url, expected):
assert (
infer_content_type_from_url_and_content(
url=url,
content=b"\x00\x00\x00\x18ftypmp42",
current_content_type="binary/octet-stream",
)
== expected
)
def test_infer_video_content_type_from_url_extension_without_header():
assert (
infer_content_type_from_url_and_content(
url="https://example.com/clip.mp4",
content=b"\x00\x00\x00\x18ftypmp42",
current_content_type=None,
)
== "video/mp4"
)