@@ -750,8 +750,7 @@ async def _map_user_message(self, message: ModelRequest) -> AsyncIterable[chat.C
750
750
else :
751
751
assert_never (part )
752
752
753
- @staticmethod
754
- async def _map_user_prompt (part : UserPromptPart ) -> chat .ChatCompletionUserMessageParam :
753
+ async def _map_user_prompt (self , part : UserPromptPart ) -> chat .ChatCompletionUserMessageParam : # noqa: C901
755
754
content : str | list [ChatCompletionContentPartParam ]
756
755
if isinstance (part .content , str ):
757
756
content = part .content
@@ -766,28 +765,40 @@ async def _map_user_prompt(part: UserPromptPart) -> chat.ChatCompletionUserMessa
766
765
image_url ['detail' ] = metadata .get ('detail' , 'auto' )
767
766
content .append (ChatCompletionContentPartImageParam (image_url = image_url , type = 'image_url' ))
768
767
elif isinstance (item , BinaryContent ):
769
- base64_encoded = base64 .b64encode (item .data ).decode ('utf-8' )
770
- if item .is_image :
771
- image_url : ImageURL = {'url' : f'data:{ item .media_type } ;base64,{ base64_encoded } ' }
772
- if metadata := item .vendor_metadata :
773
- image_url ['detail' ] = metadata .get ('detail' , 'auto' )
774
- content .append (ChatCompletionContentPartImageParam (image_url = image_url , type = 'image_url' ))
775
- elif item .is_audio :
776
- assert item .format in ('wav' , 'mp3' )
777
- audio = InputAudio (data = base64_encoded , format = item .format )
778
- content .append (ChatCompletionContentPartInputAudioParam (input_audio = audio , type = 'input_audio' ))
779
- elif item .is_document :
768
+ if self ._is_text_like_media_type (item .media_type ):
769
+ # Inline text-like binary content as a text block
780
770
content .append (
781
- File (
782
- file = FileFile (
783
- file_data = f'data:{ item .media_type } ;base64,{ base64_encoded } ' ,
784
- filename = f'filename.{ item .format } ' ,
785
- ),
786
- type = 'file' ,
771
+ self ._inline_text_file_part (
772
+ item .data .decode ('utf-8' ),
773
+ media_type = item .media_type ,
774
+ identifier = item .identifier ,
787
775
)
788
776
)
789
- else : # pragma: no cover
790
- raise RuntimeError (f'Unsupported binary content type: { item .media_type } ' )
777
+ else :
778
+ base64_encoded = base64 .b64encode (item .data ).decode ('utf-8' )
779
+ if item .is_image :
780
+ image_url : ImageURL = {'url' : f'data:{ item .media_type } ;base64,{ base64_encoded } ' }
781
+ if metadata := item .vendor_metadata :
782
+ image_url ['detail' ] = metadata .get ('detail' , 'auto' )
783
+ content .append (ChatCompletionContentPartImageParam (image_url = image_url , type = 'image_url' ))
784
+ elif item .is_audio :
785
+ assert item .format in ('wav' , 'mp3' )
786
+ audio = InputAudio (data = base64_encoded , format = item .format )
787
+ content .append (
788
+ ChatCompletionContentPartInputAudioParam (input_audio = audio , type = 'input_audio' )
789
+ )
790
+ elif item .is_document :
791
+ content .append (
792
+ File (
793
+ file = FileFile (
794
+ file_data = f'data:{ item .media_type } ;base64,{ base64_encoded } ' ,
795
+ filename = f'filename.{ item .format } ' ,
796
+ ),
797
+ type = 'file' ,
798
+ )
799
+ )
800
+ else : # pragma: no cover
801
+ raise RuntimeError (f'Unsupported binary content type: { item .media_type } ' )
791
802
elif isinstance (item , AudioUrl ):
792
803
downloaded_item = await download_item (item , data_format = 'base64' , type_format = 'extension' )
793
804
assert downloaded_item ['data_type' ] in (
@@ -797,20 +808,54 @@ async def _map_user_prompt(part: UserPromptPart) -> chat.ChatCompletionUserMessa
797
808
audio = InputAudio (data = downloaded_item ['data' ], format = downloaded_item ['data_type' ])
798
809
content .append (ChatCompletionContentPartInputAudioParam (input_audio = audio , type = 'input_audio' ))
799
810
elif isinstance (item , DocumentUrl ):
800
- downloaded_item = await download_item (item , data_format = 'base64_uri' , type_format = 'extension' )
801
- file = File (
802
- file = FileFile (
803
- file_data = downloaded_item ['data' ], filename = f'filename.{ downloaded_item ["data_type" ]} '
804
- ),
805
- type = 'file' ,
806
- )
807
- content .append (file )
811
+ if self ._is_text_like_media_type (item .media_type ):
812
+ downloaded_text = await download_item (item , data_format = 'text' )
813
+ content .append (
814
+ self ._inline_text_file_part (
815
+ downloaded_text ['data' ],
816
+ media_type = item .media_type ,
817
+ identifier = item .identifier ,
818
+ )
819
+ )
820
+ else :
821
+ downloaded_item = await download_item (item , data_format = 'base64_uri' , type_format = 'extension' )
822
+ content .append (
823
+ File (
824
+ file = FileFile (
825
+ file_data = downloaded_item ['data' ],
826
+ filename = f'filename.{ downloaded_item ["data_type" ]} ' ,
827
+ ),
828
+ type = 'file' ,
829
+ )
830
+ )
808
831
elif isinstance (item , VideoUrl ): # pragma: no cover
809
832
raise NotImplementedError ('VideoUrl is not supported for OpenAI' )
810
833
else :
811
834
assert_never (item )
812
835
return chat .ChatCompletionUserMessageParam (role = 'user' , content = content )
813
836
837
+ @staticmethod
838
+ def _is_text_like_media_type (media_type : str ) -> bool :
839
+ return (
840
+ media_type .startswith ('text/' )
841
+ or media_type == 'application/json'
842
+ or media_type .endswith ('+json' )
843
+ or media_type == 'application/xml'
844
+ or media_type .endswith ('+xml' )
845
+ or media_type in ('application/x-yaml' , 'application/yaml' )
846
+ )
847
+
848
+ @staticmethod
849
+ def _inline_text_file_part (text : str , * , media_type : str , identifier : str ) -> ChatCompletionContentPartTextParam :
850
+ text = '\n ' .join (
851
+ [
852
+ f'-----BEGIN FILE id="{ identifier } " type="{ media_type } "-----' ,
853
+ text ,
854
+ f'-----END FILE id="{ identifier } "-----' ,
855
+ ]
856
+ )
857
+ return ChatCompletionContentPartTextParam (text = text , type = 'text' )
858
+
814
859
815
860
@deprecated (
816
861
'`OpenAIModel` was renamed to `OpenAIChatModel` to clearly distinguish it from `OpenAIResponsesModel` which '
0 commit comments