33import hashlib
44import json
55import mimetypes
6+ import os
67import re
78import xml .etree .ElementTree as ET
89from enum import Enum
3334 ChatCompletionToolCallFunctionChunk ,
3435 ChatCompletionToolMessage ,
3536 ChatCompletionUserMessage ,
37+ ChatCompletionVideoObject ,
3638 OpenAIMessageContentListBlock ,
3739)
3840from litellm .types .llms .vertex_ai import FunctionCall as VertexFunctionCall
@@ -3446,6 +3448,7 @@ def stringify_json_tool_call_content(messages: List) -> List:
34463448from litellm .types .llms .bedrock import ContentBlock as BedrockContentBlock
34473449from litellm .types .llms .bedrock import DocumentBlock as BedrockDocumentBlock
34483450from litellm .types .llms .bedrock import ImageBlock as BedrockImageBlock
3451+ from litellm .types .llms .bedrock import S3Location as BedrockS3Location
34493452from litellm .types .llms .bedrock import SourceBlock as BedrockSourceBlock
34503453from litellm .types .llms .bedrock import ToolBlock as BedrockToolBlock
34513454from litellm .types .llms .bedrock import (
@@ -3459,6 +3462,7 @@ def stringify_json_tool_call_content(messages: List) -> List:
34593462from litellm .types .llms .bedrock import ToolSpecBlock as BedrockToolSpecBlock
34603463from litellm .types .llms .bedrock import ToolUseBlock as BedrockToolUseBlock
34613464from litellm .types .llms .bedrock import VideoBlock as BedrockVideoBlock
3465+ from litellm .utils import supports_s3_input
34623466
34633467
34643468def _parse_content_type (content_type : str ) -> str :
@@ -3476,7 +3480,14 @@ def _parse_mime_type(base64_data: str) -> Optional[str]:
34763480
34773481
34783482class BedrockImageProcessor :
3479- """Handles both sync and async image processing for Bedrock conversations."""
3483+ """Handles both sync and async image/media processing for Bedrock conversations."""
3484+
3485+ @staticmethod
3486+ def _extract_video_url (element : Union [dict , "ChatCompletionVideoObject" ]) -> str :
3487+ """Extract the URL string from a video_url content element."""
3488+ if isinstance (element ["video_url" ], dict ):
3489+ return element ["video_url" ]["url" ]
3490+ return element ["video_url" ]
34803491
34813492 @staticmethod
34823493 def _post_call_image_processing (
@@ -3695,20 +3706,98 @@ def _create_bedrock_block(
36953706 image = BedrockImageBlock (source = _blob , format = image_format )
36963707 )
36973708
3709+ @staticmethod
3710+ def _get_bedrock_format_from_extension (extension : str ) -> str :
3711+ """Map file extension to Bedrock format enum value."""
3712+ # 3gpp is an alias for 3gp
3713+ if extension == "3gpp" :
3714+ return "3gp"
3715+ # jpg → jpeg
3716+ if extension == "jpg" :
3717+ return "jpeg"
3718+ return extension
3719+
3720+ @classmethod
3721+ def _create_s3_bedrock_block (
3722+ cls , s3_url : str , format : Optional [str ] = None
3723+ ) -> BedrockContentBlock :
3724+ """
3725+ Create a Bedrock content block referencing an S3 object.
3726+ Determines block type (image/document/video) from file extension,
3727+ or from the explicit ``format`` override if provided.
3728+ """
3729+ # If explicit format override provided (e.g. from file.format field),
3730+ # use it directly instead of requiring a file extension.
3731+ if format :
3732+ raw_format = format .split ("/" )[- 1 ] if "/" in format else format
3733+ bedrock_format = cls ._get_bedrock_format_from_extension (raw_format .lower ())
3734+ else :
3735+ extension = os .path .splitext (s3_url )[- 1 ].lstrip ("." ).lower ()
3736+ if not extension :
3737+ raise ValueError (
3738+ f"Cannot determine file type from S3 URL (no extension): { s3_url } "
3739+ )
3740+ bedrock_format = cls ._get_bedrock_format_from_extension (extension )
3741+
3742+ s3_source = BedrockSourceBlock (s3Location = BedrockS3Location (uri = s3_url ))
3743+
3744+ config = litellm .AmazonConverseConfig ()
3745+ supported_image_formats = config .get_supported_image_types ()
3746+ supported_doc_formats = config .get_supported_document_types ()
3747+ supported_video_formats = config .get_supported_video_types ()
3748+
3749+ if bedrock_format in supported_image_formats :
3750+ return BedrockContentBlock (
3751+ image = BedrockImageBlock (source = s3_source , format = bedrock_format )
3752+ )
3753+ elif bedrock_format in supported_doc_formats :
3754+ doc_name = f"s3doc_{ hashlib .sha256 (s3_url .encode ()).hexdigest ()[:16 ]} _{ bedrock_format } "
3755+ return BedrockContentBlock (
3756+ document = BedrockDocumentBlock (
3757+ source = s3_source , format = bedrock_format , name = doc_name
3758+ )
3759+ )
3760+ elif bedrock_format in supported_video_formats :
3761+ return BedrockContentBlock (
3762+ video = BedrockVideoBlock (source = s3_source , format = bedrock_format )
3763+ )
3764+ else :
3765+ raise ValueError (
3766+ f"Unsupported file format '{ bedrock_format } ' for Bedrock S3 content. "
3767+ f"Supported: images={ supported_image_formats } , "
3768+ f"documents={ supported_doc_formats } , "
3769+ f"videos={ supported_video_formats } "
3770+ )
3771+
3772+ # TODO: Rename to process_media_sync/async — these methods now handle images, documents, and videos (not just images).
36983773 @classmethod
36993774 def process_image_sync (
3700- cls , image_url : str , format : Optional [str ] = None
3775+ cls ,
3776+ image_url : str ,
3777+ format : Optional [str ] = None ,
3778+ model : Optional [str ] = None ,
3779+ custom_llm_provider : Optional [str ] = None ,
37013780 ) -> BedrockContentBlock :
3702- """Synchronous image processing."""
3781+ """Synchronous processing of media URLs (images, documents, videos) for Bedrock ."""
37033782
3704- if "base64" in image_url :
3783+ if image_url .startswith ("s3://" ):
3784+ if model and not supports_s3_input (
3785+ model = model , custom_llm_provider = custom_llm_provider
3786+ ):
3787+ raise ValueError (
3788+ f"Model '{ model } ' does not support s3:// URLs. "
3789+ "Only Amazon Nova models (with vision) support S3 input via Bedrock Converse API. "
3790+ "Please use a base64-encoded or https:// URL instead."
3791+ )
3792+ return cls ._create_s3_bedrock_block (image_url , format )
3793+ elif "base64" in image_url :
37053794 img_bytes , mime_type , image_format = cls ._parse_base64_image (image_url )
37063795 elif "http://" in image_url or "https://" in image_url :
37073796 img_bytes , mime_type = BedrockImageProcessor .get_image_details (image_url )
37083797 image_format = mime_type .split ("/" )[1 ]
37093798 else :
37103799 raise ValueError (
3711- "Unsupported image type. Expected either image url or base64 encoded string"
3800+ "Unsupported image type. Expected either image url, base64 encoded string, or s3:// URL "
37123801 )
37133802
37143803 if format :
@@ -3720,11 +3809,25 @@ def process_image_sync(
37203809
37213810 @classmethod
37223811 async def process_image_async (
3723- cls , image_url : str , format : Optional [str ]
3812+ cls ,
3813+ image_url : str ,
3814+ format : Optional [str ],
3815+ model : Optional [str ] = None ,
3816+ custom_llm_provider : Optional [str ] = None ,
37243817 ) -> BedrockContentBlock :
3725- """Asynchronous image processing."""
3818+ """Asynchronous processing of media URLs (images, documents, videos) for Bedrock ."""
37263819
3727- if "base64" in image_url :
3820+ if image_url .startswith ("s3://" ):
3821+ if model and not supports_s3_input (
3822+ model = model , custom_llm_provider = custom_llm_provider
3823+ ):
3824+ raise ValueError (
3825+ f"Model '{ model } ' does not support s3:// URLs. "
3826+ "Only Amazon Nova models (with vision) support S3 input via Bedrock Converse API. "
3827+ "Please use a base64-encoded or https:// URL instead."
3828+ )
3829+ return cls ._create_s3_bedrock_block (image_url , format )
3830+ elif "base64" in image_url :
37283831 img_bytes , mime_type , image_format = cls ._parse_base64_image (image_url )
37293832 elif "http://" in image_url or "https://" in image_url :
37303833 img_bytes , mime_type = await BedrockImageProcessor .get_image_details_async (
@@ -3733,7 +3836,7 @@ async def process_image_async(
37333836 image_format = mime_type .split ("/" )[1 ]
37343837 else :
37353838 raise ValueError (
3736- "Unsupported image type. Expected either image url or base64 encoded string"
3839+ "Unsupported image type. Expected either image url, base64 encoded string, or s3:// URL "
37373840 )
37383841
37393842 if format : # override with user-defined params
@@ -4374,14 +4477,30 @@ async def _bedrock_converse_messages_pt_async( # noqa: PLR0915
43744477 else :
43754478 image_url = element ["image_url" ]
43764479 _part = await BedrockImageProcessor .process_image_async ( # type: ignore
4377- image_url = image_url , format = format
4480+ image_url = image_url ,
4481+ format = format ,
4482+ model = model ,
4483+ custom_llm_provider = llm_provider ,
43784484 )
43794485 _parts .append (_part ) # type: ignore
43804486 elif element ["type" ] == "file" :
43814487 _part = await BedrockConverseMessagesProcessor ._async_process_file_message (
4382- message = cast (ChatCompletionFileObject , element )
4488+ message = cast (ChatCompletionFileObject , element ),
4489+ model = model ,
4490+ llm_provider = llm_provider ,
43834491 )
43844492 _parts .append (_part )
4493+ elif element ["type" ] == "video_url" :
4494+ video_url = BedrockImageProcessor ._extract_video_url (
4495+ element
4496+ )
4497+ _part = await BedrockImageProcessor .process_image_async ( # type: ignore
4498+ image_url = video_url ,
4499+ format = None ,
4500+ model = model ,
4501+ custom_llm_provider = llm_provider ,
4502+ )
4503+ _parts .append (_part ) # type: ignore
43854504 _cache_point_block = (
43864505 litellm .AmazonConverseConfig ()._get_cache_point_block (
43874506 message_block = cast (
@@ -4623,7 +4742,11 @@ def translate_thinking_blocks_to_reasoning_content_blocks(
46234742 return reasoning_content_blocks
46244743
46254744 @staticmethod
4626- def _process_file_message (message : ChatCompletionFileObject ) -> BedrockContentBlock :
4745+ def _process_file_message (
4746+ message : ChatCompletionFileObject ,
4747+ model : Optional [str ] = None ,
4748+ llm_provider : Optional [str ] = None ,
4749+ ) -> BedrockContentBlock :
46274750 file_message = message ["file" ]
46284751 file_data = file_message .get ("file_data" )
46294752 file_id = file_message .get ("file_id" )
@@ -4638,12 +4761,17 @@ def _process_file_message(message: ChatCompletionFileObject) -> BedrockContentBl
46384761 )
46394762 format = file_message .get ("format" )
46404763 return BedrockImageProcessor .process_image_sync (
4641- image_url = cast (str , file_id or file_data ), format = format
4764+ image_url = cast (str , file_id or file_data ),
4765+ format = format ,
4766+ model = model ,
4767+ custom_llm_provider = llm_provider ,
46424768 )
46434769
46444770 @staticmethod
46454771 async def _async_process_file_message (
46464772 message : ChatCompletionFileObject ,
4773+ model : Optional [str ] = None ,
4774+ llm_provider : Optional [str ] = None ,
46474775 ) -> BedrockContentBlock :
46484776 file_message = message ["file" ]
46494777 file_data = file_message .get ("file_data" )
@@ -4658,7 +4786,10 @@ async def _async_process_file_message(
46584786 llm_provider = "bedrock" ,
46594787 )
46604788 return await BedrockImageProcessor .process_image_async (
4661- image_url = cast (str , file_id or file_data ), format = format
4789+ image_url = cast (str , file_id or file_data ),
4790+ format = format ,
4791+ model = model ,
4792+ custom_llm_provider = llm_provider ,
46624793 )
46634794
46644795 @staticmethod
@@ -4749,15 +4880,30 @@ def _bedrock_converse_messages_pt( # noqa: PLR0915
47494880 _part = BedrockImageProcessor .process_image_sync ( # type: ignore
47504881 image_url = image_url ,
47514882 format = format ,
4883+ model = model ,
4884+ custom_llm_provider = llm_provider ,
47524885 )
47534886 _parts .append (_part ) # type: ignore
47544887 elif element ["type" ] == "file" :
47554888 _part = (
47564889 BedrockConverseMessagesProcessor ._process_file_message (
4757- message = cast (ChatCompletionFileObject , element )
4890+ message = cast (ChatCompletionFileObject , element ),
4891+ model = model ,
4892+ llm_provider = llm_provider ,
47584893 )
47594894 )
47604895 _parts .append (_part )
4896+ elif element ["type" ] == "video_url" :
4897+ video_url = BedrockImageProcessor ._extract_video_url (
4898+ element
4899+ )
4900+ _part = BedrockImageProcessor .process_image_sync ( # type: ignore
4901+ image_url = video_url ,
4902+ format = None ,
4903+ model = model ,
4904+ custom_llm_provider = llm_provider ,
4905+ )
4906+ _parts .append (_part ) # type: ignore
47614907 _cache_point_block = (
47624908 litellm .AmazonConverseConfig ()._get_cache_point_block (
47634909 message_block = cast (
0 commit comments