From c642ac7a72eb00d455fb33baad5ca863f5b4634e Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 07:48:47 -0500 Subject: [PATCH 01/13] review multi modal input handling --- pydantic_ai_slim/pydantic_ai/messages.py | 13 +- .../pydantic_ai/models/anthropic.py | 51 +- .../test_image_url_input_force_download.yaml | 672 ++++++++++++++++++ ...text_document_as_binary_content_input.yaml | 67 ++ tests/models/test_anthropic.py | 38 +- tests/models/test_mcp_sampling.py | 5 +- tests/models/test_mistral.py | 10 +- tests/test_messages.py | 7 + 8 files changed, 836 insertions(+), 27 deletions(-) create mode 100644 tests/models/cassettes/test_anthropic/test_image_url_input_force_download.yaml create mode 100644 tests/models/cassettes/test_anthropic/test_text_document_as_binary_content_input.yaml diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index a855036de3..6ce2a331f5 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -474,7 +474,11 @@ class BinaryContent: """Binary content, e.g. an audio or image file.""" data: bytes - """The binary data.""" + """Arbitrary binary data. + + Store actual bytes here, not base64. + Use `BinaryContent.base64` to get the base64-encoded string. + """ _: KW_ONLY @@ -574,7 +578,12 @@ def identifier(self) -> str: @property def data_uri(self) -> str: """Convert the `BinaryContent` to a data URI.""" - return f'data:{self.media_type};base64,{base64.b64encode(self.data).decode()}' + return f'data:{self.media_type};base64,{self.base64}' + + @property + def base64(self) -> str: + """Return the binary data as a base64-encoded string.""" + return base64.b64encode(self.data).decode() @property def is_audio(self) -> bool: diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 28395f56bd..812284e2fb 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -1006,6 +1006,33 @@ def _add_cache_control_to_last_param( # Add cache_control to the last param last_param['cache_control'] = self._build_cache_control(ttl) + @staticmethod + def _map_binary_content(item: BinaryContent) -> BetaContentBlockParam: + # Anthropic SDK accepts file-like objects (IO[bytes]) and handles base64 encoding internally + if item.is_image: + return BetaImageBlockParam( + source={'data': io.BytesIO(item.data), 'media_type': item.media_type, 'type': 'base64'}, # type: ignore + type='image', + ) + elif item.media_type == 'application/pdf': + return BetaBase64PDFBlockParam( + source=BetaBase64PDFSourceParam( + data=io.BytesIO(item.data), + media_type='application/pdf', + type='base64', + ), + type='document', + ) + elif item.media_type == 'text/plain': + return BetaBase64PDFBlockParam( + source=BetaPlainTextSourceParam( + data=item.data.decode('utf-8'), media_type=item.media_type, type='text' + ), + type='document', + ) + else: + raise RuntimeError(f'Unsupported binary content media type for Anthropic: {item.media_type}') + @staticmethod async def _map_user_prompt( part: UserPromptPart, @@ -1021,24 +1048,20 @@ async def _map_user_prompt( elif isinstance(item, CachePoint): yield item elif isinstance(item, BinaryContent): - if item.is_image: + yield AnthropicModel._map_binary_content(item) + elif isinstance(item, ImageUrl): + if item.force_download: + downloaded = await download_item(item, data_format='bytes') yield BetaImageBlockParam( - source={'data': io.BytesIO(item.data), 'media_type': item.media_type, 'type': 'base64'}, # type: ignore + source={ + 'data': io.BytesIO(downloaded['data']), + 'media_type': item.media_type, + 'type': 'base64', + }, # type: ignore type='image', ) - elif item.media_type == 'application/pdf': - yield BetaBase64PDFBlockParam( - source=BetaBase64PDFSourceParam( - data=io.BytesIO(item.data), - media_type='application/pdf', - type='base64', - ), - type='document', - ) else: - raise RuntimeError('Only images and PDFs are supported for binary content') - elif isinstance(item, ImageUrl): - yield BetaImageBlockParam(source={'type': 'url', 'url': item.url}, type='image') + yield BetaImageBlockParam(source={'type': 'url', 'url': item.url}, type='image') elif isinstance(item, DocumentUrl): if item.media_type == 'application/pdf': yield BetaBase64PDFBlockParam(source={'url': item.url, 'type': 'url'}, type='document') diff --git a/tests/models/cassettes/test_anthropic/test_image_url_input_force_download.yaml b/tests/models/cassettes/test_anthropic/test_image_url_input_force_download.yaml new file mode 100644 index 0000000000..8fcc60c1d5 --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_image_url_input_force_download.yaml @@ -0,0 +1,672 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - t3.ftcdn.net + method: GET + uri: https://t3.ftcdn.net/jpg/00/85/79/92/360_F_85799278_0BBGV9OAdQDTLnKwAPBCcg1J7QtiieJY.jpg + response: + body: + string: !!binary | + /9j/4AAQSkZJRgABAQEBLAEsAAD/4QBWRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUA + AAABAAAARgEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAAAAAEsAAAAAQAAASwAAAAB/+0ALFBo + b3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAPHAFaAAMbJUccAQAAAgAEAP/hDIFodHRwOi8vbnMuYWRv + YmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVT + ek5UY3prYzlkJz8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0nYWRvYmU6bnM6bWV0YS8nIHg6eG1wdGs9 + J0ltYWdlOjpFeGlmVG9vbCAxMC4xMCc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53 + My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyc+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6 + YWJvdXQ9JycKICB4bWxuczp0aWZmPSdodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyc+CiAg + PHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICA8dGlmZjpYUmVz + b2x1dGlvbj4zMDAvMTwvdGlmZjpYUmVzb2x1dGlvbj4KICA8dGlmZjpZUmVzb2x1dGlvbj4zMDAv + MTwvdGlmZjpZUmVzb2x1dGlvbj4KIDwvcmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRp + b24gcmRmOmFib3V0PScnCiAgeG1sbnM6eG1wTU09J2h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEu + MC9tbS8nPgogIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnN0b2NrOmVmNTcyZTAyLTA4 + NTUtNGFmMS1iMDA1LTVjNTMzOTllMTkyZTwveG1wTU06RG9jdW1lbnRJRD4KICA8eG1wTU06SW5z + dGFuY2VJRD54bXAuaWlkOjE3ODBmZTgxLWI1OTQtNDdlZS04ZmJiLTNhODRiM2FjYzcyODwveG1w + TU06SW5zdGFuY2VJRD4KIDwvcmRmOkRlc2NyaXB0aW9uPgo8L3JkZjpSREY+CjwveDp4bXBtZXRh + PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAog + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg + ICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSd3Jz8+/9sAQwAF + AwQEBAMFBAQEBQUFBgcMCAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQi + HiQcHh8e/9sAQwEFBQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e + Hh4eHh4eHh4eHh4eHh4eHh4e/8AAEQgBaAIcAwERAAIRAQMRAf/EABwAAQADAQEBAQEAAAAAAAAA + AAACAwQBBQYHCP/EAEcQAAEEAQMCBAMFBgMGBAUFAAEAAgMRIQQSMQVBEyJRYQZxgQcykaHBFCNC + YrHRFVJyCDNDkuHwFjSCshdEwtLxJFNjc6L/xAAbAQEAAwEBAQEAAAAAAAAAAAAAAQIDBAUGB//E + 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 + Tt2Ty9Hc3+aZzh/7FX/H5ftE/wCvx/RdF9g3xM2Pa3qXR4x2AdJ+jVb/AB2T20R/r8f0y1n2DfEZ + rf1rpTfUASO/QJ/jp/aI/wAhD6Zb/wDATrbvvdf6c35QvKn/ABsv+Q/yEfom37B+t8O67001wfAe + n+On/wAiP8hH6On7COshpDeudOPzhen+Ol/yH+Qj9EY/sJ661ueu9LLrxUMin/HS+yf8hH6Of/Av + 4habHXOlOHoY5Aj+Ol9kf5CP0UP+wn4ncNp6p0Yj/VL/APaq/wCOn9on/Xw+mbND9g3VB/5nr2hZ + /wD1wPf/AFIUr46XuRD+Qj6R6+h+wjQMN6z4g1Mg7th07WfmSVovjY+5FH8g/SPoum/Y58F6WjNp + 9ZrCP/39QQPwbS3j4OJd7MZeblf9H1HS/hP4a6WB+w9D0EJHDhCC78TZW8cGOPUUYyzZJds9oAAA + AUB2WpkdQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBA + EAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAE + AQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEA + QBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQ + BAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQB + AEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBA + EAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAE + AQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEAQBAEA + QBAEB//Z + headers: + accept-ranges: + - bytes + access-control-allow-origin: + - '*' + age: + - '1632126' + cache-control: + - public, max-age=31536000 + connection: + - keep-alive + content-length: + - '31812' + content-type: + - image/jpeg + etag: + - '"b6a51d242511723c74b5e277f2ec0896"' + last-modified: + - Sat, 28 Mar 2020 09:58:48 GMT + strict-transport-security: + - max-age=31536000 + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '42647' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 4096 + messages: + - content: + - text: What is this vegetable? + type: text + - source: + data: /9j/4AAQSkZJRgABAQEBLAEsAAD/4QBWRXhpZgAATU0AKgAAAAgABAEaAAUAAAABAAAAPgEbAAUAAAABAAAARgEoAAMAAAABAAIAAAITAAMAAAABAAEAAAAAAAAAAAEsAAAAAQAAASwAAAAB/+0ALFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAPHAFaAAMbJUccAQAAAgAEAP/hDIFodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvADw/eHBhY2tldCBiZWdpbj0n77u/JyBpZD0nVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkJz8+Cjx4OnhtcG1ldGEgeG1sbnM6eD0nYWRvYmU6bnM6bWV0YS8nIHg6eG1wdGs9J0ltYWdlOjpFeGlmVG9vbCAxMC4xMCc+CjxyZGY6UkRGIHhtbG5zOnJkZj0naHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyc+CgogPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9JycKICB4bWxuczp0aWZmPSdodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyc+CiAgPHRpZmY6UmVzb2x1dGlvblVuaXQ+MjwvdGlmZjpSZXNvbHV0aW9uVW5pdD4KICA8dGlmZjpYUmVzb2x1dGlvbj4zMDAvMTwvdGlmZjpYUmVzb2x1dGlvbj4KICA8dGlmZjpZUmVzb2x1dGlvbj4zMDAvMTwvdGlmZjpZUmVzb2x1dGlvbj4KIDwvcmRmOkRlc2NyaXB0aW9uPgoKIDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PScnCiAgeG1sbnM6eG1wTU09J2h0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8nPgogIDx4bXBNTTpEb2N1bWVudElEPmFkb2JlOmRvY2lkOnN0b2NrOmVmNTcyZTAyLTA4NTUtNGFmMS1iMDA1LTVjNTMzOTllMTkyZTwveG1wTU06RG9jdW1lbnRJRD4KICA8eG1wTU06SW5zdGFuY2VJRD54bXAuaWlkOjE3ODBmZTgxLWI1OTQtNDdlZS04ZmJiLTNhODRiM2FjYzcyODwveG1wTU06SW5zdGFuY2VJRD4KIDwvcmRmOkRlc2NyaXB0aW9uPgo8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSd3Jz8+/9sAQwAFAwQEBAMFBAQEBQUFBgcMCAcHBwcPCwsJDBEPEhIRDxERExYcFxMUGhURERghGBodHR8fHxMXIiQiHiQcHh8e/9sAQwEFBQUHBgcOCAgOHhQRFB4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4e/8AAEQgBaAIcAwERAAIRAQMRAf/EABwAAQADAQEBAQEAAAAAAAAAAAACAwQBBQYHCP/EAEcQAAEEAQMCBAMFBgMGBAUFAAEAAgMRIQQSMQVBEyJRYQZxgQcykaHBFCNCYrHRFVJyCDND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 + media_type: image/jpeg + type: base64 + type: image + role: user + model: claude-haiku-4-5 + stream: false + uri: https://api.anthropic.com/v1/messages?beta=true + response: + headers: + connection: + - keep-alive + content-length: + - '797' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + content: + - text: |- + This is a **potato**, specifically a yellow or gold potato variety. You can identify it by its characteristic features: + + - **Oval/round shape** with smooth skin + - **Golden-yellow color** with small dark spots or eyes + - **Starchy appearance** typical of potatoes + + This appears to be a russet or similar yellow potato variety commonly used for cooking, baking, or making mashed potatoes. + type: text + id: msg_01KHctj7AyWoJWmwcFoXsDH6 + model: claude-haiku-4-5-20251001 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation: + ephemeral_1h_input_tokens: 0 + ephemeral_5m_input_tokens: 0 + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 276 + output_tokens: 92 + service_tier: standard + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_anthropic/test_text_document_as_binary_content_input.yaml b/tests/models/cassettes/test_anthropic/test_text_document_as_binary_content_input.yaml new file mode 100644 index 0000000000..fc2ed220ed --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_text_document_as_binary_content_input.yaml @@ -0,0 +1,67 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '255' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 4096 + messages: + - content: + - text: What does this text file say? + type: text + - source: + data: | + Dummy TXT file + media_type: text/plain + type: text + type: document + role: user + model: claude-sonnet-4-5 + stream: false + uri: https://api.anthropic.com/v1/messages?beta=true + response: + headers: + connection: + - keep-alive + content-length: + - '444' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + content: + - text: The text file says "Dummy TXT file". + type: text + id: msg_01KanQBT3kkADVzF2uCY9XPR + model: claude-sonnet-4-5-20250929 + role: assistant + stop_reason: end_turn + stop_sequence: null + type: message + usage: + cache_creation: + ephemeral_1h_input_tokens: 0 + ephemeral_5m_input_tokens: 0 + cache_creation_input_tokens: 0 + cache_read_input_tokens: 0 + input_tokens: 57 + output_tokens: 14 + service_tier: standard + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index 2df96b0278..4df256b699 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -1485,6 +1485,32 @@ async def test_image_url_input(allow_model_requests: None, anthropic_api_key: st ) +async def test_image_url_input_force_download(allow_model_requests: None, anthropic_api_key: str): + m = AnthropicModel('claude-haiku-4-5', provider=AnthropicProvider(api_key=anthropic_api_key)) + agent = Agent(m) + + result = await agent.run( + [ + 'What is this vegetable?', + ImageUrl( + url='https://t3.ftcdn.net/jpg/00/85/79/92/360_F_85799278_0BBGV9OAdQDTLnKwAPBCcg1J7QtiieJY.jpg', + force_download=True, + ), + ] + ) + assert result.output == snapshot( + """\ +This is a **potato**, specifically a yellow or gold potato variety. You can identify it by its characteristic features: + +- **Oval/round shape** with smooth skin +- **Golden-yellow color** with small dark spots or eyes +- **Starchy appearance** typical of potatoes + +This appears to be a russet or similar yellow potato variety commonly used for cooking, baking, or making mashed potatoes.\ +""" + ) + + async def test_extra_headers(allow_model_requests: None, anthropic_api_key: str): # This test doesn't do anything, it's just here to ensure that calls with `extra_headers` don't cause errors, including type. m = AnthropicModel('claude-haiku-4-5', provider=AnthropicProvider(api_key=anthropic_api_key)) @@ -1614,7 +1640,7 @@ async def test_audio_as_binary_content_input(allow_model_requests: None, media_t base64_content = b'//uQZ' - with pytest.raises(RuntimeError, match='Only images and PDFs are supported for binary content'): + with pytest.raises(RuntimeError, match='Unsupported binary content media type for Anthropic'): await agent.run(['hello', BinaryContent(data=base64_content, media_type=media_type)]) @@ -1707,6 +1733,16 @@ async def test_text_document_url_input(allow_model_requests: None, anthropic_api """) +async def test_text_document_as_binary_content_input( + allow_model_requests: None, anthropic_api_key: str, text_document_content: BinaryContent +): + m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key=anthropic_api_key)) + agent = Agent(m) + + result = await agent.run(['What does this text file say?', text_document_content]) + assert result.output == snapshot('The text file says "Dummy TXT file".') + + def test_init_with_provider(): provider = AnthropicProvider(api_key='api-key') model = AnthropicModel('claude-3-opus-latest', provider=provider) diff --git a/tests/models/test_mcp_sampling.py b/tests/models/test_mcp_sampling.py index 1da0851c20..338695db82 100644 --- a/tests/models/test_mcp_sampling.py +++ b/tests/models/test_mcp_sampling.py @@ -1,4 +1,3 @@ -import base64 from dataclasses import dataclass from datetime import timezone from typing import Any @@ -121,9 +120,7 @@ def test_assistant_text_history_complex(): ModelRequest( parts=[ UserPromptPart(content='1'), - UserPromptPart( - content=['a string', BinaryContent(data=base64.b64encode(b'data'), media_type='image/jpeg')] - ), + UserPromptPart(content=['a string', BinaryContent(data=b'data', media_type='image/jpeg')]), SystemPromptPart(content='system content'), ] ), diff --git a/tests/models/test_mistral.py b/tests/models/test_mistral.py index 32a56c6395..a64b8248d8 100644 --- a/tests/models/test_mistral.py +++ b/tests/models/test_mistral.py @@ -1998,12 +1998,10 @@ async def test_image_as_binary_content_input(allow_model_requests: None): m = MistralModel('mistral-large-latest', provider=MistralProvider(mistral_client=mock_client)) agent = Agent(m) - base64_content = ( - b'/9j/4AAQSkZJRgABAQEAYABgAAD/4QBYRXhpZgAATU0AKgAAAAgAA1IBAAEAAAABAAAAPgIBAAEAAAABAAAARgMBAAEAAAABAAAA' - b'WgAAAAAAAAAE' - ) + # Fake image bytes for testing + image_bytes = b'fake image data' - result = await agent.run(['hello', BinaryContent(data=base64_content, media_type='image/jpeg')]) + result = await agent.run(['hello', BinaryContent(data=image_bytes, media_type='image/jpeg')]) assert result.all_messages() == snapshot( [ ModelRequest( @@ -2011,7 +2009,7 @@ async def test_image_as_binary_content_input(allow_model_requests: None): UserPromptPart( content=[ 'hello', - BinaryContent(data=base64_content, media_type='image/jpeg', identifier='cb93e3'), + BinaryContent(data=image_bytes, media_type='image/jpeg'), ], timestamp=IsDatetime(), ) diff --git a/tests/test_messages.py b/tests/test_messages.py index f116502882..8081292ba4 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -309,6 +309,13 @@ def test_binary_content_is_methods(): assert document_content.format == 'pdf' +def test_binary_content_base64(): + bc = BinaryContent(data=b'Hello, world!', media_type='image/png') + assert bc.base64 == 'SGVsbG8sIHdvcmxkIQ==' + assert not bc.base64.startswith('data:') + assert bc.data_uri == 'data:image/png;base64,SGVsbG8sIHdvcmxkIQ==' + + @pytest.mark.xdist_group(name='url_formats') @pytest.mark.parametrize( 'video_url,media_type,format', From 754c0f0156215a691b1dc52d968da5238d90b589 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:14:08 -0500 Subject: [PATCH 02/13] enable url media for openai responses --- pydantic_ai_slim/pydantic_ai/models/openai.py | 31 +- ...url_input_force_download_response_api.yaml | 389 ++++++++++++++++++ .../test_document_url_input_response_api.yaml | 110 +++++ tests/models/test_openai.py | 27 ++ 4 files changed, 541 insertions(+), 16 deletions(-) create mode 100644 tests/models/cassettes/test_openai/test_document_url_input_force_download_response_api.yaml create mode 100644 tests/models/cassettes/test_openai/test_document_url_input_response_api.yaml diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index a37be4f024..1daa9fc5be 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -1844,24 +1844,23 @@ async def _map_user_prompt(part: UserPromptPart) -> responses.EasyInputMessagePa detail=detail, ) ) - elif isinstance(item, AudioUrl): # pragma: no cover - downloaded_item = await download_item(item, data_format='base64_uri', type_format='extension') - content.append( - responses.ResponseInputFileParam( - type='input_file', - file_data=downloaded_item['data'], - filename=f'filename.{downloaded_item["data_type"]}', + elif isinstance(item, AudioUrl | DocumentUrl): + if item.force_download: + downloaded_item = await download_item(item, data_format='base64_uri', type_format='extension') + content.append( + responses.ResponseInputFileParam( + type='input_file', + file_data=downloaded_item['data'], + filename=f'filename.{downloaded_item["data_type"]}', + ) ) - ) - elif isinstance(item, DocumentUrl): - downloaded_item = await download_item(item, data_format='base64_uri', type_format='extension') - content.append( - responses.ResponseInputFileParam( - type='input_file', - file_data=downloaded_item['data'], - filename=f'filename.{downloaded_item["data_type"]}', + else: + content.append( + responses.ResponseInputFileParam( + type='input_file', + file_url=item.url, + ) ) - ) elif isinstance(item, VideoUrl): # pragma: no cover raise NotImplementedError('VideoUrl is not supported for OpenAI.') elif isinstance(item, CachePoint): diff --git a/tests/models/cassettes/test_openai/test_document_url_input_force_download_response_api.yaml b/tests/models/cassettes/test_openai/test_document_url_input_force_download_response_api.yaml new file mode 100644 index 0000000000..d7c6282dc2 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_document_url_input_force_download_response_api.yaml @@ -0,0 +1,389 @@ +interactions: +- request: + body: '' + headers: + accept: + - '*/*' + accept-encoding: + - gzip, deflate + connection: + - keep-alive + host: + - www.w3.org + method: GET + uri: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf + response: + body: + string: !!binary | + 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 + USRuPC6uZTI6hUkHjGBBoU5ADiZ+I8AZj6cuEx8zjpm4eFQITuTkV/uewQl+EA3PcXwkUimfl/nI + xJJC8fwSnKisjfV4PhV9JKegWvwUQR1YRV8Y650p5QAOFx4uP1w3VjhWPlZnFD+08BCQtofEURqp + fEihoCMw4wiAwW6K/XQB9N0fycuXiscE4HB0OwLyN17ow6526L8jA6fPOjagSw1I8cGZgMTwAYoR + xyYdoRmmkM4iJ0OSRSr8P1jbNhMKZW5kc3RyZWFtCmVuZG9iagoKNiAwIG9iagoxMDgyNQplbmRv + YmoKCjcgMCBvYmoKPDwvVHlwZS9Gb250RGVzY3JpcHRvci9Gb250TmFtZS9CQUFBQUErQXJpYWwt + Qm9sZE1UCi9GbGFncyA0Ci9Gb250QkJveFstNjI3IC0zNzYgMjAwMCAxMDExXS9JdGFsaWNBbmds + ZSAwCi9Bc2NlbnQgOTA1Ci9EZXNjZW50IDIxMQovQ2FwSGVpZ2h0IDEwMTAKL1N0ZW1WIDgwCi9G + b250RmlsZTIgNSAwIFI+PgplbmRvYmoKCjggMCBvYmoKPDwvTGVuZ3RoIDI3Mi9GaWx0ZXIvRmxh + dGVEZWNvZGU+PgpzdHJlYW0KeJxdkc9uhCAQxu88BcftYQNadbuJMdm62cRD/6S2D6AwWpKKBPHg + 2xcG2yY9QH7DzDf5ZmB1c220cuzVzqIFRwelpYVlXq0A2sOoNElSKpVwe4S3mDpDmNe22+JgavQw + lyVhbz63OLvRw0XOPdwR9mIlWKVHevioWx+3qzFfMIF2lJOqohIG3+epM8/dBAxVx0b6tHLb0Uv+ + Ct43AzTFOIlWxCxhMZ0A2+kRSMl5RcvbrSKg5b9cskv6QXx21pcmvpTzLKs8p8inPPA9cnENnMX3 + c+AcOeWBC+Qc+RT7FIEfohb5HBm1l8h14MfIOZrc3QS7YZ8/a6BitdavAJeOs4eplYbffzGzCSo8 + 3zuVhO0KZW5kc3RyZWFtCmVuZG9iagoKOSAwIG9iago8PC9UeXBlL0ZvbnQvU3VidHlwZS9UcnVl + VHlwZS9CYXNlRm9udC9CQUFBQUErQXJpYWwtQm9sZE1UCi9GaXJzdENoYXIgMAovTGFzdENoYXIg + MTEKL1dpZHRoc1s3NTAgNzIyIDYxMCA4ODkgNTU2IDI3NyA2NjYgNjEwIDMzMyAyNzcgMjc3IDU1 + NiBdCi9Gb250RGVzY3JpcHRvciA3IDAgUgovVG9Vbmljb2RlIDggMCBSCj4+CmVuZG9iagoKMTAg + MCBvYmoKPDwKL0YxIDkgMCBSCj4+CmVuZG9iagoKMTEgMCBvYmoKPDwvRm9udCAxMCAwIFIKL1By + b2NTZXRbL1BERi9UZXh0XT4+CmVuZG9iagoKMSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQg + MCBSL1Jlc291cmNlcyAxMSAwIFIvTWVkaWFCb3hbMCAwIDU5NSA4NDJdL0dyb3VwPDwvUy9UcmFu + c3BhcmVuY3kvQ1MvRGV2aWNlUkdCL0kgdHJ1ZT4+L0NvbnRlbnRzIDIgMCBSPj4KZW5kb2JqCgox + MiAwIG9iago8PC9Db3VudCAxL0ZpcnN0IDEzIDAgUi9MYXN0IDEzIDAgUgo+PgplbmRvYmoKCjEz + IDAgb2JqCjw8L1RpdGxlPEZFRkYwMDQ0MDA3NTAwNkQwMDZEMDA3OTAwMjAwMDUwMDA0NDAwNDYw + MDIwMDA2NjAwNjkwMDZDMDA2NT4KL0Rlc3RbMSAwIFIvWFlaIDU2LjcgNzczLjMgMF0vUGFyZW50 + IDEyIDAgUj4+CmVuZG9iagoKNCAwIG9iago8PC9UeXBlL1BhZ2VzCi9SZXNvdXJjZXMgMTEgMCBS + Ci9NZWRpYUJveFsgMCAwIDU5NSA4NDIgXQovS2lkc1sgMSAwIFIgXQovQ291bnQgMT4+CmVuZG9i + agoKMTQgMCBvYmoKPDwvVHlwZS9DYXRhbG9nL1BhZ2VzIDQgMCBSCi9PdXRsaW5lcyAxMiAwIFIK + Pj4KZW5kb2JqCgoxNSAwIG9iago8PC9BdXRob3I8RkVGRjAwNDUwMDc2MDA2MTAwNkUwMDY3MDA2 + NTAwNkMwMDZGMDA3MzAwMjAwMDU2MDA2QzAwNjEwMDYzMDA2ODAwNkYwMDY3MDA2OTAwNjEwMDZF + MDA2RTAwNjkwMDczPgovQ3JlYXRvcjxGRUZGMDA1NzAwNzIwMDY5MDA3NDAwNjUwMDcyPgovUHJv + ZHVjZXI8RkVGRjAwNEYwMDcwMDA2NTAwNkUwMDRGMDA2NjAwNjYwMDY5MDA2MzAwNjUwMDJFMDA2 + RjAwNzIwMDY3MDAyMDAwMzIwMDJFMDAzMT4KL0NyZWF0aW9uRGF0ZShEOjIwMDcwMjIzMTc1NjM3 + KzAyJzAwJyk+PgplbmRvYmoKCnhyZWYKMCAxNgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMTE5 + OTcgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMjI0IDAwMDAwIG4gCjAwMDAw + MTIzMzAgMDAwMDAgbiAKMDAwMDAwMDI0NCAwMDAwMCBuIAowMDAwMDExMTU0IDAwMDAwIG4gCjAw + MDAwMTExNzYgMDAwMDAgbiAKMDAwMDAxMTM2OCAwMDAwMCBuIAowMDAwMDExNzA5IDAwMDAwIG4g + CjAwMDAwMTE5MTAgMDAwMDAgbiAKMDAwMDAxMTk0MyAwMDAwMCBuIAowMDAwMDEyMTQwIDAwMDAw + IG4gCjAwMDAwMTIxOTYgMDAwMDAgbiAKMDAwMDAxMjQyOSAwMDAwMCBuIAowMDAwMDEyNDk0IDAw + MDAwIG4gCnRyYWlsZXIKPDwvU2l6ZSAxNi9Sb290IDE0IDAgUgovSW5mbyAxNSAwIFIKL0lEIFsg + PEY3RDc3QjNEMjJCOUY5MjgyOUQ0OUZGNUQ3OEI4RjI4Pgo8RjdENzdCM0QyMkI5RjkyODI5RDQ5 + RkY1RDc4QjhGMjg+IF0KPj4Kc3RhcnR4cmVmCjEyNzg3CiUlRU9GCg== + headers: + accept-ranges: + - bytes + age: + - '502211' + alt-svc: + - h3=":443"; ma=86400 + cache-control: + - public, max-age=2592000, s-maxage=2592000 + connection: + - keep-alive + content-length: + - '13264' + content-security-policy: + - frame-ancestors 'self' https://cms.w3.org/ https://cms-dev.w3.org/; upgrade-insecure-requests + content-type: + - application/pdf; qs=0.001 + etag: + - '"33d0-438b181451e00"' + expires: + - Thu, 04 Dec 2025 23:01:47 GMT + last-modified: + - Mon, 27 Aug 2007 17:15:36 GMT + strict-transport-security: + - max-age=15552000; includeSubdomains; preload + vary: + - Accept-Encoding + status: + code: 200 + message: OK +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '17930' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: + - text: What is the main content on this document? + type: input_text + - file_data: data:application/pdf;base64,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CmVuZG9iagoKOSAwIG9iago8PC9UeXBlL0ZvbnQvU3VidHlwZS9UcnVlVHlwZS9CYXNlRm9udC9CQUFBQUErQXJpYWwtQm9sZE1UCi9GaXJzdENoYXIgMAovTGFzdENoYXIgMTEKL1dpZHRoc1s3NTAgNzIyIDYxMCA4ODkgNTU2IDI3NyA2NjYgNjEwIDMzMyAyNzcgMjc3IDU1NiBdCi9Gb250RGVzY3JpcHRvciA3IDAgUgovVG9Vbmljb2RlIDggMCBSCj4+CmVuZG9iagoKMTAgMCBvYmoKPDwKL0YxIDkgMCBSCj4+CmVuZG9iagoKMTEgMCBvYmoKPDwvRm9udCAxMCAwIFIKL1Byb2NTZXRbL1BERi9UZXh0XT4+CmVuZG9iagoKMSAwIG9iago8PC9UeXBlL1BhZ2UvUGFyZW50IDQgMCBSL1Jlc291cmNlcyAxMSAwIFIvTWVkaWFCb3hbMCAwIDU5NSA4NDJdL0dyb3VwPDwvUy9UcmFuc3BhcmVuY3kvQ1MvRGV2aWNlUkdCL0kgdHJ1ZT4+L0NvbnRlbnRzIDIgMCBSPj4KZW5kb2JqCgoxMiAwIG9iago8PC9Db3VudCAxL0ZpcnN0IDEzIDAgUi9MYXN0IDEzIDAgUgo+PgplbmRvYmoKCjEzIDAgb2JqCjw8L1RpdGxlPEZFRkYwMDQ0MDA3NTAwNkQwMDZEMDA3OTAwMjAwMDUwMDA0NDAwNDYwMDIwMDA2NjAwNjkwMDZDMDA2NT4KL0Rlc3RbMSAwIFIvWFlaIDU2LjcgNzczLjMgMF0vUGFyZW50IDEyIDAgUj4+CmVuZG9iagoKNCAwIG9iago8PC9UeXBlL1BhZ2VzCi9SZXNvdXJjZXMgMTEgMCBSCi9NZWRpYUJveFsgMCAwIDU5NSA4NDIgXQovS2lkc1sgMSAwIFIgXQovQ291bnQgMT4+CmVuZG9iagoKMTQgMCBvYmoKPDwvVHlwZS9DYXRhbG9nL1BhZ2VzIDQgMCBSCi9PdXRsaW5lcyAxMiAwIFIKPj4KZW5kb2JqCgoxNSAwIG9iago8PC9BdXRob3I8RkVGRjAwNDUwMDc2MDA2MTAwNkUwMDY3MDA2NTAwNkMwMDZGMDA3MzAwMjAwMDU2MDA2QzAwNjEwMDYzMDA2ODAwNkYwMDY3MDA2OTAwNjEwMDZFMDA2RTAwNjkwMDczPgovQ3JlYXRvcjxGRUZGMDA1NzAwNzIwMDY5MDA3NDAwNjUwMDcyPgovUHJvZHVjZXI8RkVGRjAwNEYwMDcwMDA2NTAwNkUwMDRGMDA2NjAwNjYwMDY5MDA2MzAwNjUwMDJFMDA2RjAwNzIwMDY3MDAyMDAwMzIwMDJFMDAzMT4KL0NyZWF0aW9uRGF0ZShEOjIwMDcwMjIzMTc1NjM3KzAyJzAwJyk+PgplbmRvYmoKCnhyZWYKMCAxNgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMTE5OTcgMDAwMDAgbiAKMDAwMDAwMDAxOSAwMDAwMCBuIAowMDAwMDAwMjI0IDAwMDAwIG4gCjAwMDAwMTIzMzAgMDAwMDAgbiAKMDAwMDAwMDI0NCAwMDAwMCBuIAowMDAwMDExMTU0IDAwMDAwIG4gCjAwMDAwMTExNzYgMDAwMDAgbiAKMDAwMDAxMTM2OCAwMDAwMCBuIAowMDAwMDExNzA5IDAwMDAwIG4gCjAwMDAwMTE5MTAgMDAwMDAgbiAKMDAwMDAxMTk0MyAwMDAwMCBuIAowMDAwMDEyMTQwIDAwMDAwIG4gCjAwMDAwMTIxOTYgMDAwMDAgbiAKMDAwMDAxMjQyOSAwMDAwMCBuIAowMDAwMDEyNDk0IDAwMDAwIG4gCnRyYWlsZXIKPDwvU2l6ZSAxNi9Sb290IDE0IDAgUgovSW5mbyAxNSAwIFIKL0lEIFsgPEY3RDc3QjNEMjJCOUY5MjgyOUQ0OUZGNUQ3OEI4RjI4Pgo8RjdENzdCM0QyMkI5RjkyODI5RDQ5RkY1RDc4QjhGMjg+IF0KPj4Kc3RhcnR4cmVmCjEyNzg3CiUlRU9GCg== + filename: filename.pdf + type: input_file + role: user + model: gpt-4.1-nano + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '1712' + content-type: + - application/json + openai-organization: + - user-grnwlxd1653lxdzp921aoihz + openai-processing-ms: + - '2035' + openai-project: + - proj_FYsIItHHgnSPdHBVMzhNBWGa + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + background: false + billing: + payer: developer + created_at: 1765372490 + error: null + id: resp_0dcee7b90a1649d90069397249a4148196a73201b1c5b445ac + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-nano-2025-04-14 + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: The document appears to be titled "Dummy PDF File," which suggests that it is a placeholder or example document + rather than actual content. If you can provide more specific details or share the content within the document, + I can help analyze or summarize it further. + type: output_text + id: msg_0dcee7b90a1649d9006939724aea1c8196b1ea7c07c1285b20 + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: null + reasoning: + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 23 + input_tokens_details: + cached_tokens: 0 + output_tokens: 51 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 74 + user: null + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/cassettes/test_openai/test_document_url_input_response_api.yaml b/tests/models/cassettes/test_openai/test_document_url_input_response_api.yaml new file mode 100644 index 0000000000..96874becb5 --- /dev/null +++ b/tests/models/cassettes/test_openai/test_document_url_input_response_api.yaml @@ -0,0 +1,110 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '258' + content-type: + - application/json + host: + - api.openai.com + method: POST + parsed_body: + input: + - content: + - text: What is the main content on this document? + type: input_text + - file_url: https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf + type: input_file + role: user + model: gpt-4.1-nano + stream: false + uri: https://api.openai.com/v1/responses + response: + headers: + alt-svc: + - h3=":443"; ma=86400 + connection: + - keep-alive + content-length: + - '1830' + content-type: + - application/json + openai-organization: + - user-grnwlxd1653lxdzp921aoihz + openai-processing-ms: + - '2806' + openai-project: + - proj_FYsIItHHgnSPdHBVMzhNBWGa + openai-version: + - '2020-10-01' + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + transfer-encoding: + - chunked + parsed_body: + background: false + billing: + payer: developer + created_at: 1765372486 + error: null + id: resp_015b88f1b471dcb90069397245702481979e5c36ff51d29a52 + incomplete_details: null + instructions: null + max_output_tokens: null + max_tool_calls: null + metadata: {} + model: gpt-4.1-nano-2025-04-14 + object: response + output: + - content: + - annotations: [] + logprobs: [] + text: The document is titled "Dummy PDF file," indicating that it is a sample or placeholder PDF. Without viewing + the specific content within the PDF, it appears to be a generic or placeholder document often used for testing + or demonstration purposes. If you need a detailed summary or specific information from the actual content of the + PDF, please upload the file or provide more details. + type: output_text + id: msg_015b88f1b471dcb900693972472ae88197acf92d328008c6a8 + role: assistant + status: completed + type: message + parallel_tool_calls: true + previous_response_id: null + prompt_cache_key: null + prompt_cache_retention: null + reasoning: + effort: null + summary: null + safety_identifier: null + service_tier: default + status: completed + store: true + temperature: 1.0 + text: + format: + type: text + verbosity: medium + tool_choice: auto + tools: [] + top_logprobs: 0 + top_p: 1.0 + truncation: disabled + usage: + input_tokens: 23 + input_tokens_details: + cached_tokens: 0 + output_tokens: 72 + output_tokens_details: + reasoning_tokens: 0 + total_tokens: 95 + user: null + status: + code: 200 + message: OK +version: 1 diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index ed68edd94f..8bc0d08250 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -873,6 +873,33 @@ async def test_document_url_input(allow_model_requests: None, openai_api_key: st assert result.output == snapshot('The document contains the text "Dummy PDF file" on its single page.') +async def test_document_url_input_response_api(allow_model_requests: None, openai_api_key: str): + """Test DocumentUrl with Responses API sends URL directly (default behavior).""" + provider = OpenAIProvider(api_key=openai_api_key) + m = OpenAIResponsesModel('gpt-4.1-nano', provider=provider) + agent = Agent(m) + + document_url = DocumentUrl(url='https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf') + + result = await agent.run(['What is the main content on this document?', document_url]) + assert 'Dummy PDF' in result.output + + +async def test_document_url_input_force_download_response_api(allow_model_requests: None, openai_api_key: str): + """Test DocumentUrl with force_download=True downloads and sends as file_data.""" + provider = OpenAIProvider(api_key=openai_api_key) + m = OpenAIResponsesModel('gpt-4.1-nano', provider=provider) + agent = Agent(m) + + document_url = DocumentUrl( + url='https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', + force_download=True, + ) + + result = await agent.run(['What is the main content on this document?', document_url]) + assert 'Dummy PDF' in result.output + + @pytest.mark.vcr() async def test_image_url_tool_response(allow_model_requests: None, openai_api_key: str): m = OpenAIChatModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) From 2f5bcf6e89345e3eab81cee98e75d45a139e2d3b Mon Sep 17 00:00:00 2001 From: David <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 10:52:52 -0500 Subject: [PATCH 03/13] Update documentation for BinaryContent.base64 usage --- pydantic_ai_slim/pydantic_ai/messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 6ce2a331f5..1e851c145e 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -477,7 +477,7 @@ class BinaryContent: """Arbitrary binary data. Store actual bytes here, not base64. - Use `BinaryContent.base64` to get the base64-encoded string. + Use `.base64` to get the base64-encoded string. """ _: KW_ONLY From 6bba553f19e36bb59f63f15998792b7ccf563d22 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:24:39 -0500 Subject: [PATCH 04/13] Address review comments: force_download, type rename, refactoring MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit addresses Douwe's review comments on PR #3694: 1. **Anthropic: Add force_download for DocumentUrl PDF** - DocumentUrl with PDF now respects force_download parameter - When force_download=True: downloads and sends as base64 - When force_download=False: sends URL directly 2. **Anthropic: Rename BetaBase64PDFBlockParam → BetaRequestDocumentBlockParam** - Updated to use newer type name (BetaBase64PDFBlockParam is now an alias) - Updated imports and all usages 3. **Anthropic: Refactor _map_binary_content to handle FileUrl** - Created new _map_binary_to_block helper method - Eliminates code duplication between BinaryContent, ImageUrl force_download, and DocumentUrl force_download - Supports image/*, application/pdf, and text/plain media types 4. **Messages: Use base64 property in otel_message_parts** - Replaced manual base64.b64encode calls with item.base64 property - 3 replacements in UserPromptPart.otel_message_parts and ModelResponse.otel_* methods 5. **Tests: Add force_download verification tests** - 4 new tests for Anthropic (ImageUrl + DocumentUrl) - 5 new tests for OpenAI (ImageUrl + DocumentUrl + AudioUrl) - Tests verify download_item is called when force_download=True All tests pass (306 tests in test_anthropic.py, test_openai.py, test_messages.py). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- pydantic_ai_slim/pydantic_ai/messages.py | 8 +- .../pydantic_ai/models/anthropic.py | 47 ++--- tests/models/test_anthropic.py | 134 ++++++++++++++ tests/models/test_openai.py | 163 ++++++++++++++++++ 4 files changed, 325 insertions(+), 27 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 9924a92255..3f9005b1d2 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -582,7 +582,7 @@ def data_uri(self) -> str: @property def base64(self) -> str: - """Return the binary data as a base64-encoded string.""" + """Return the binary data as a base64-encoded string. Default encoding is UTF-8.""" return base64.b64encode(self.data).decode() @property @@ -784,7 +784,7 @@ def otel_message_parts(self, settings: InstrumentationSettings) -> list[_otel_me elif isinstance(part, BinaryContent): converted_part = _otel_messages.BinaryDataPart(type='binary', media_type=part.media_type) if settings.include_content and settings.include_binary_content: - converted_part['content'] = base64.b64encode(part.data).decode() + converted_part['content'] = part.base64 parts.append(converted_part) elif isinstance(part, CachePoint): # CachePoint is a marker, not actual content - skip it for otel @@ -1387,7 +1387,7 @@ def new_event_body(): 'kind': 'binary', 'media_type': part.content.media_type, **( - {'binary_content': base64.b64encode(part.content.data).decode()} + {'binary_content': part.content.base64} if settings.include_content and settings.include_binary_content else {} ), @@ -1421,7 +1421,7 @@ def otel_message_parts(self, settings: InstrumentationSettings) -> list[_otel_me elif isinstance(part, FilePart): converted_part = _otel_messages.BinaryDataPart(type='binary', media_type=part.content.media_type) if settings.include_content and settings.include_binary_content: - converted_part['content'] = base64.b64encode(part.content.data).decode() + converted_part['content'] = part.content.base64 parts.append(converted_part) elif isinstance(part, BaseToolCallPart): call_part = _otel_messages.ToolCallPart(type='tool_call', id=part.tool_call_id, name=part.tool_name) diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 42d1b87b90..ecf78bbbd1 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -64,7 +64,6 @@ omit as OMIT, ) from anthropic.types.beta import ( - BetaBase64PDFBlockParam, BetaBase64PDFSourceParam, BetaCacheControlEphemeralParam, BetaCitationsConfigParam, @@ -98,6 +97,7 @@ BetaRawMessageStreamEvent, BetaRedactedThinkingBlock, BetaRedactedThinkingBlockParam, + BetaRequestDocumentBlockParam, BetaRequestMCPServerToolConfigurationParam, BetaRequestMCPServerURLDefinitionParam, BetaServerToolUseBlock, @@ -1035,31 +1035,33 @@ def _add_cache_control_to_last_param( last_param['cache_control'] = self._build_cache_control(ttl) @staticmethod - def _map_binary_content(item: BinaryContent) -> BetaContentBlockParam: + def _map_binary_to_block(data: bytes, media_type: str) -> BetaContentBlockParam: # Anthropic SDK accepts file-like objects (IO[bytes]) and handles base64 encoding internally - if item.is_image: + if media_type.startswith('image/'): return BetaImageBlockParam( - source={'data': io.BytesIO(item.data), 'media_type': item.media_type, 'type': 'base64'}, # type: ignore + source={'data': io.BytesIO(data), 'media_type': media_type, 'type': 'base64'}, # type: ignore type='image', ) - elif item.media_type == 'application/pdf': - return BetaBase64PDFBlockParam( + elif media_type == 'application/pdf': + return BetaRequestDocumentBlockParam( source=BetaBase64PDFSourceParam( - data=io.BytesIO(item.data), + data=io.BytesIO(data), media_type='application/pdf', type='base64', ), type='document', ) - elif item.media_type == 'text/plain': - return BetaBase64PDFBlockParam( - source=BetaPlainTextSourceParam( - data=item.data.decode('utf-8'), media_type=item.media_type, type='text' - ), + elif media_type == 'text/plain': + return BetaRequestDocumentBlockParam( + source=BetaPlainTextSourceParam(data=data.decode('utf-8'), media_type=media_type, type='text'), type='document', ) else: - raise RuntimeError(f'Unsupported binary content media type for Anthropic: {item.media_type}') + raise RuntimeError(f'Unsupported binary content media type for Anthropic: {media_type}') + + @staticmethod + def _map_binary_content(item: BinaryContent) -> BetaContentBlockParam: + return AnthropicModel._map_binary_to_block(item.data, item.media_type) @staticmethod async def _map_user_prompt( @@ -1080,22 +1082,21 @@ async def _map_user_prompt( elif isinstance(item, ImageUrl): if item.force_download: downloaded = await download_item(item, data_format='bytes') - yield BetaImageBlockParam( - source={ - 'data': io.BytesIO(downloaded['data']), - 'media_type': item.media_type, - 'type': 'base64', - }, # type: ignore - type='image', - ) + yield AnthropicModel._map_binary_to_block(downloaded['data'], item.media_type) else: yield BetaImageBlockParam(source={'type': 'url', 'url': item.url}, type='image') elif isinstance(item, DocumentUrl): if item.media_type == 'application/pdf': - yield BetaBase64PDFBlockParam(source={'url': item.url, 'type': 'url'}, type='document') + if item.force_download: + downloaded = await download_item(item, data_format='bytes') + yield AnthropicModel._map_binary_to_block(downloaded['data'], item.media_type) + else: + yield BetaRequestDocumentBlockParam( + source={'url': item.url, 'type': 'url'}, type='document' + ) elif item.media_type == 'text/plain': downloaded_item = await download_item(item, data_format='text') - yield BetaBase64PDFBlockParam( + yield BetaRequestDocumentBlockParam( source=BetaPlainTextSourceParam( data=downloaded_item['data'], media_type=item.media_type, type='text' ), diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index 97b264235b..ba14ad4575 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -1541,6 +1541,140 @@ async def test_image_url_input_invalid_mime_type(allow_model_requests: None, ant ) +async def test_image_url_force_download() -> None: + """Test that force_download=True calls download_item for ImageUrl.""" + from unittest.mock import AsyncMock, patch + + m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key='test-key')) + + with patch('pydantic_ai.models.anthropic.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': b'\x89PNG\r\n\x1a\n fake image data', + 'content_type': 'image/png', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test image', + ImageUrl( + url='https://example.com/image.png', + media_type='image/png', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_message(messages, ModelRequestParameters(), {}) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/image.png' + + +async def test_image_url_no_force_download() -> None: + """Test that force_download=False does not call download_item for ImageUrl.""" + from unittest.mock import AsyncMock, patch + + m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key='test-key')) + + with patch('pydantic_ai.models.anthropic.download_item', new_callable=AsyncMock) as mock_download: + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test image', + ImageUrl( + url='https://example.com/image.png', + media_type='image/png', + force_download=False, + ), + ] + ) + ] + ) + ] + + await m._map_message(messages, ModelRequestParameters(), {}) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_not_called() + + +async def test_document_url_pdf_force_download() -> None: + """Test that force_download=True calls download_item for DocumentUrl (PDF).""" + from unittest.mock import AsyncMock, patch + + m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key='test-key')) + + with patch('pydantic_ai.models.anthropic.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': b'%PDF-1.4 fake pdf data', + 'content_type': 'application/pdf', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test PDF', + DocumentUrl( + url='https://example.com/doc.pdf', + media_type='application/pdf', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_message(messages, ModelRequestParameters(), {}) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/doc.pdf' + + +async def test_document_url_text_force_download() -> None: + """Test that force_download=True calls download_item for DocumentUrl (text/plain).""" + from unittest.mock import AsyncMock, patch + + m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key='test-key')) + + with patch('pydantic_ai.models.anthropic.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': 'Sample text content', + 'content_type': 'text/plain', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test text file', + DocumentUrl( + url='https://example.com/doc.txt', + media_type='text/plain', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_message(messages, ModelRequestParameters(), {}) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/doc.txt' + + async def test_image_as_binary_content_tool_response( allow_model_requests: None, anthropic_api_key: str, image_content: BinaryContent ): diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index 8bc0d08250..5dc81a9998 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -900,6 +900,169 @@ async def test_document_url_input_force_download_response_api(allow_model_reques assert 'Dummy PDF' in result.output +async def test_image_url_force_download_chat() -> None: + """Test that force_download=True calls download_item for ImageUrl in OpenAIChatModel.""" + from unittest.mock import AsyncMock, patch + + m = OpenAIChatModel('gpt-4o', provider=OpenAIProvider(api_key='test-key')) + + with patch('pydantic_ai.models.openai.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + 'content_type': 'image/png', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test image', + ImageUrl( + url='https://example.com/image.png', + media_type='image/png', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage, reportArgumentType] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/image.png' + + +async def test_image_url_no_force_download_chat() -> None: + """Test that force_download=False does not call download_item for ImageUrl in OpenAIChatModel.""" + from unittest.mock import AsyncMock, patch + + m = OpenAIChatModel('gpt-4o', provider=OpenAIProvider(api_key='test-key')) + + with patch('pydantic_ai.models.openai.download_item', new_callable=AsyncMock) as mock_download: + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test image', + ImageUrl( + url='https://example.com/image.png', + media_type='image/png', + force_download=False, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage, reportArgumentType] + + mock_download.assert_not_called() + + +async def test_document_url_force_download_responses() -> None: + """Test that force_download=True calls download_item for DocumentUrl in OpenAIResponsesModel.""" + from unittest.mock import AsyncMock, patch + + m = OpenAIResponsesModel('gpt-4.5-nano', provider=OpenAIProvider(api_key='test-key')) + + with patch('pydantic_ai.models.openai.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': 'data:application/pdf;base64,JVBERi0xLjQK', + 'data_type': 'pdf', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test PDF', + DocumentUrl( + url='https://example.com/doc.pdf', + media_type='application/pdf', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, {}, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/doc.pdf' + + +async def test_document_url_no_force_download_responses() -> None: + """Test that force_download=False does not call download_item for DocumentUrl in OpenAIResponsesModel.""" + from unittest.mock import AsyncMock, patch + + m = OpenAIResponsesModel('gpt-4.5-nano', provider=OpenAIProvider(api_key='test-key')) + + with patch('pydantic_ai.models.openai.download_item', new_callable=AsyncMock) as mock_download: + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test document', + DocumentUrl( + url='https://example.com/doc.pdf', + media_type='application/pdf', + force_download=False, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, {}, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_not_called() + + +async def test_audio_url_force_download_responses() -> None: + """Test that force_download=True calls download_item for AudioUrl in OpenAIResponsesModel.""" + from unittest.mock import AsyncMock, patch + + m = OpenAIResponsesModel('gpt-4.5-nano', provider=OpenAIProvider(api_key='test-key')) + + with patch('pydantic_ai.models.openai.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2', + 'data_type': 'mp3', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test audio', + AudioUrl( + url='https://example.com/audio.mp3', + media_type='audio/mp3', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, {}, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/audio.mp3' + + @pytest.mark.vcr() async def test_image_url_tool_response(allow_model_requests: None, openai_api_key: str): m = OpenAIChatModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) From 73894672cf8ebf7bdc8194bc602e6809c5213f06 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:30:19 -0500 Subject: [PATCH 05/13] upstream cerebras merge stuff --- pydantic_ai_slim/pydantic_ai/models/gemini.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/models/gemini.py b/pydantic_ai_slim/pydantic_ai/models/gemini.py index 2772e26631..8d73ce76bd 100644 --- a/pydantic_ai_slim/pydantic_ai/models/gemini.py +++ b/pydantic_ai_slim/pydantic_ai/models/gemini.py @@ -1,6 +1,5 @@ from __future__ import annotations as _annotations -import base64 from collections.abc import AsyncIterator, Sequence from contextlib import asynccontextmanager from dataclasses import dataclass, field @@ -375,9 +374,8 @@ async def _map_user_prompt(self, part: UserPromptPart) -> list[_GeminiPartUnion] if isinstance(item, str): content.append({'text': item}) elif isinstance(item, BinaryContent): - base64_encoded = base64.b64encode(item.data).decode('utf-8') content.append( - _GeminiInlineDataPart(inline_data={'data': base64_encoded, 'mime_type': item.media_type}) + _GeminiInlineDataPart(inline_data={'data': item.base64, 'mime_type': item.media_type}) ) elif isinstance(item, VideoUrl) and item.is_youtube: file_data = _GeminiFileDataPart(file_data={'file_uri': item.url, 'mime_type': item.media_type}) From 8ad810d65695ea45a02aef428befd4bc46b0017f Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:32:08 -0500 Subject: [PATCH 06/13] AsyncClient --- pydantic_ai_slim/pydantic_ai/providers/groq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pydantic_ai_slim/pydantic_ai/providers/groq.py b/pydantic_ai_slim/pydantic_ai/providers/groq.py index f0e5c5b53b..6b0fb2ac37 100644 --- a/pydantic_ai_slim/pydantic_ai/providers/groq.py +++ b/pydantic_ai_slim/pydantic_ai/providers/groq.py @@ -107,7 +107,7 @@ def __init__( groq_client: An existing [`AsyncGroq`](https://github.com/groq/groq-python?tab=readme-ov-file#async-usage) client to use. If provided, `api_key` and `http_client` must be `None`. - http_client: An existing `AsyncHTTPClient` to use for making HTTP requests. + http_client: An existing `AsyncClient` to use for making HTTP requests. """ if groq_client is not None: assert http_client is None, 'Cannot provide both `groq_client` and `http_client`' From da9ec78daaf1a6212a4d1243fbca0f0c7e847e31 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 19:33:53 -0500 Subject: [PATCH 07/13] base64 replacements --- pydantic_ai_slim/pydantic_ai/_mcp.py | 2 +- pydantic_ai_slim/pydantic_ai/models/__init__.py | 4 ++-- pydantic_ai_slim/pydantic_ai/models/openai.py | 4 ++-- tests/models/test_openai.py | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/_mcp.py b/pydantic_ai_slim/pydantic_ai/_mcp.py index 1729e4c225..add5c917f3 100644 --- a/pydantic_ai_slim/pydantic_ai/_mcp.py +++ b/pydantic_ai_slim/pydantic_ai/_mcp.py @@ -91,7 +91,7 @@ def add_msg( 'user', mcp_types.ImageContent( type='image', - data=base64.b64encode(chunk.data).decode(), + data=chunk.base64, mimeType=chunk.media_type, ), ) diff --git a/pydantic_ai_slim/pydantic_ai/models/__init__.py b/pydantic_ai_slim/pydantic_ai/models/__init__.py index aef137cc29..7c42339574 100644 --- a/pydantic_ai_slim/pydantic_ai/models/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/models/__init__.py @@ -9,7 +9,7 @@ import base64 import warnings from abc import ABC, abstractmethod -from collections.abc import AsyncIterator, Callable, Iterator +from collections.abc import AsyncIterator, Callable, Iterator, Sequence from contextlib import asynccontextmanager, contextmanager from dataclasses import dataclass, field, replace from datetime import datetime @@ -721,7 +721,7 @@ def base_url(self) -> str | None: @staticmethod def _get_instructions( - messages: list[ModelMessage], model_request_parameters: ModelRequestParameters | None = None + messages: Sequence[ModelMessage], model_request_parameters: ModelRequestParameters | None = None ) -> str | None: """Get instructions from the first ModelRequest found when iterating messages in reverse. diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 47c465c6c6..5a59a180de 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -890,7 +890,7 @@ def _map_finish_reason( return _CHAT_FINISH_REASON_MAP.get(key) async def _map_messages( - self, messages: list[ModelMessage], model_request_parameters: ModelRequestParameters + self, messages: Sequence[ModelMessage], model_request_parameters: ModelRequestParameters ) -> list[chat.ChatCompletionMessageParam]: """Just maps a `pydantic_ai.Message` to a `openai.types.ChatCompletionMessageParam`.""" openai_messages: list[chat.ChatCompletionMessageParam] = [] @@ -1002,7 +1002,7 @@ async def _map_user_prompt(self, part: UserPromptPart) -> chat.ChatCompletionUse content.append(ChatCompletionContentPartImageParam(image_url=image_url, type='image_url')) elif item.is_audio: assert item.format in ('wav', 'mp3') - audio = InputAudio(data=base64.b64encode(item.data).decode('utf-8'), format=item.format) + audio = InputAudio(data=item.base64, format=item.format) content.append(ChatCompletionContentPartInputAudioParam(input_audio=audio, type='input_audio')) elif item.is_document: content.append( diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index 5dc81a9998..d21af371ba 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -929,7 +929,7 @@ async def test_image_url_force_download_chat() -> None: ) ] - await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage, reportArgumentType] + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage] mock_download.assert_called_once() assert mock_download.call_args[0][0].url == 'https://example.com/image.png' @@ -959,7 +959,7 @@ async def test_image_url_no_force_download_chat() -> None: ) ] - await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage, reportArgumentType] + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage] mock_download.assert_not_called() From 9a55ce23e7fc887e74a74223ca4c8ed174f8abf7 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Wed, 10 Dec 2025 22:45:14 -0500 Subject: [PATCH 08/13] add force download support for mistral --- .../pydantic_ai/models/mistral.py | 35 +++-- tests/models/test_mistral.py | 131 ++++++++++++++++++ 2 files changed, 155 insertions(+), 11 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/models/mistral.py b/pydantic_ai_slim/pydantic_ai/models/mistral.py index 2a3752c370..2b0e544c91 100644 --- a/pydantic_ai_slim/pydantic_ai/models/mistral.py +++ b/pydantic_ai_slim/pydantic_ai/models/mistral.py @@ -1,6 +1,6 @@ from __future__ import annotations as _annotations -from collections.abc import AsyncIterable, AsyncIterator, Iterable +from collections.abc import AsyncIterable, AsyncIterator, Sequence from contextlib import asynccontextmanager from dataclasses import dataclass, field from datetime import datetime @@ -46,6 +46,7 @@ ModelRequestParameters, StreamedResponse, check_allow_model_requests, + download_item, get_user_agent, ) @@ -230,7 +231,7 @@ async def _completions_create( try: response = await self.client.chat.complete_async( model=str(self._model_name), - messages=self._map_messages(messages, model_request_parameters), + messages=await self._map_messages(messages, model_request_parameters), n=1, tools=self._map_function_and_output_tools_definition(model_request_parameters) or UNSET, tool_choice=self._get_tool_choice(model_request_parameters), @@ -259,7 +260,7 @@ async def _stream_completions_create( ) -> MistralEventStreamAsync[MistralCompletionEvent]: """Create a streaming completion request to the Mistral model.""" response: MistralEventStreamAsync[MistralCompletionEvent] | None - mistral_messages = self._map_messages(messages, model_request_parameters) + mistral_messages = await self._map_messages(messages, model_request_parameters) # TODO(Marcelo): We need to replace the current MistralAI client to use the beta client. # See https://docs.mistral.ai/agents/connectors/websearch/ to support web search. @@ -501,12 +502,12 @@ def _get_timeout_ms(timeout: Timeout | float | None) -> int | None: return int(1000 * timeout) raise NotImplementedError('Timeout object is not yet supported for MistralModel.') - def _map_user_message(self, message: ModelRequest) -> Iterable[MistralMessages]: + async def _map_user_message(self, message: ModelRequest) -> AsyncIterable[MistralMessages]: for part in message.parts: if isinstance(part, SystemPromptPart): yield MistralSystemMessage(content=part.content) elif isinstance(part, UserPromptPart): - yield self._map_user_prompt(part) + yield await self._map_user_prompt(part) elif isinstance(part, ToolReturnPart): yield MistralToolMessage( tool_call_id=part.tool_call_id, @@ -523,14 +524,15 @@ def _map_user_message(self, message: ModelRequest) -> Iterable[MistralMessages]: else: assert_never(part) - def _map_messages( - self, messages: list[ModelMessage], model_request_parameters: ModelRequestParameters + async def _map_messages( # noqa: C901 + self, messages: Sequence[ModelMessage], model_request_parameters: ModelRequestParameters ) -> list[MistralMessages]: """Just maps a `pydantic_ai.Message` to a `MistralMessage`.""" mistral_messages: list[MistralMessages] = [] for message in messages: if isinstance(message, ModelRequest): - mistral_messages.extend(self._map_user_message(message)) + async for msg in self._map_user_message(message): + mistral_messages.append(msg) elif isinstance(message, ModelResponse): content_chunks: list[MistralContentChunk] = [] thinking_chunks: list[MistralTextChunk | MistralReferenceChunk] = [] @@ -573,7 +575,7 @@ def _map_messages( return processed_messages - def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage: + async def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage: content: str | list[MistralContentChunk] if isinstance(part.content, str): content = part.content @@ -583,7 +585,12 @@ def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage: if isinstance(item, str): content.append(MistralTextChunk(text=item)) elif isinstance(item, ImageUrl): - content.append(MistralImageURLChunk(image_url=MistralImageURL(url=item.url))) + if item.force_download: + downloaded = await download_item(item, data_format='base64_uri') + image_url = MistralImageURL(url=downloaded['data']) + content.append(MistralImageURLChunk(image_url=image_url, type='image_url')) + else: + content.append(MistralImageURLChunk(image_url=MistralImageURL(url=item.url))) elif isinstance(item, BinaryContent): if item.is_image: image_url = MistralImageURL(url=item.data_uri) @@ -594,7 +601,13 @@ def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage: raise RuntimeError('BinaryContent other than image or PDF is not supported in Mistral.') elif isinstance(item, DocumentUrl): if item.media_type == 'application/pdf': - content.append(MistralDocumentURLChunk(document_url=item.url, type='document_url')) + if item.force_download: + downloaded = await download_item(item, data_format='base64_uri') + content.append( + MistralDocumentURLChunk(document_url=downloaded['data'], type='document_url') + ) + else: + content.append(MistralDocumentURLChunk(document_url=item.url, type='document_url')) else: raise RuntimeError('DocumentUrl other than PDF is not supported in Mistral.') elif isinstance(item, VideoUrl): diff --git a/tests/models/test_mistral.py b/tests/models/test_mistral.py index a64b8248d8..1fa8bedde2 100644 --- a/tests/models/test_mistral.py +++ b/tests/models/test_mistral.py @@ -29,6 +29,7 @@ ) from pydantic_ai.agent import Agent from pydantic_ai.exceptions import ModelAPIError, ModelHTTPError, ModelRetry +from pydantic_ai.models import ModelRequestParameters from pydantic_ai.usage import RequestUsage from ..conftest import IsDatetime, IsNow, IsStr, raise_if_exception, try_import @@ -2343,3 +2344,133 @@ async def test_mistral_model_thinking_part_iter(allow_model_requests: None, mist ), ] ) + + +async def test_image_url_force_download() -> None: + """Test that force_download=True calls download_item for ImageUrl in MistralModel.""" + from unittest.mock import AsyncMock, patch + + m = MistralModel('mistral-large-2512', provider=MistralProvider(api_key='test-key')) + + with patch('pydantic_ai.models.mistral.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==', + 'data_type': 'image/png', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test image', + ImageUrl( + url='https://example.com/image.png', + media_type='image/png', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/image.png' + assert mock_download.call_args[1]['data_format'] == 'base64_uri' + + +async def test_image_url_no_force_download() -> None: + """Test that force_download=False does not call download_item for ImageUrl in MistralModel.""" + from unittest.mock import AsyncMock, patch + + m = MistralModel('mistral-large-2512', provider=MistralProvider(api_key='test-key')) + + with patch('pydantic_ai.models.mistral.download_item', new_callable=AsyncMock) as mock_download: + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test image', + ImageUrl( + url='https://example.com/image.png', + media_type='image/png', + force_download=False, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage] + + mock_download.assert_not_called() + + +async def test_document_url_force_download() -> None: + """Test that force_download=True calls download_item for DocumentUrl PDF in MistralModel.""" + from unittest.mock import AsyncMock, patch + + m = MistralModel('mistral-large-2512', provider=MistralProvider(api_key='test-key')) + + with patch('pydantic_ai.models.mistral.download_item', new_callable=AsyncMock) as mock_download: + mock_download.return_value = { + 'data': 'data:application/pdf;base64,JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PC9UeXBlL', + 'data_type': 'application/pdf', + } + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test PDF', + DocumentUrl( + url='https://example.com/document.pdf', + media_type='application/pdf', + force_download=True, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage] + + mock_download.assert_called_once() + assert mock_download.call_args[0][0].url == 'https://example.com/document.pdf' + assert mock_download.call_args[1]['data_format'] == 'base64_uri' + + +async def test_document_url_no_force_download() -> None: + """Test that force_download=False does not call download_item for DocumentUrl PDF in MistralModel.""" + from unittest.mock import AsyncMock, patch + + m = MistralModel('mistral-large-2512', provider=MistralProvider(api_key='test-key')) + + with patch('pydantic_ai.models.mistral.download_item', new_callable=AsyncMock) as mock_download: + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test PDF', + DocumentUrl( + url='https://example.com/document.pdf', + media_type='application/pdf', + force_download=False, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage] + + mock_download.assert_not_called() From 703b7724277db756fdd12b3cf20cf534f4b85310 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Fri, 12 Dec 2025 19:58:13 -0500 Subject: [PATCH 09/13] address review comments --- pydantic_ai_slim/pydantic_ai/messages.py | 3 +-- pydantic_ai_slim/pydantic_ai/models/anthropic.py | 12 ++++-------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index 3f9005b1d2..cc24ec3d70 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -474,9 +474,8 @@ class BinaryContent: """Binary content, e.g. an audio or image file.""" data: bytes - """Arbitrary binary data. + """The binary file data. - Store actual bytes here, not base64. Use `.base64` to get the base64-encoded string. """ diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index ecf78bbbd1..83f1d53ae4 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -1035,7 +1035,7 @@ def _add_cache_control_to_last_param( last_param['cache_control'] = self._build_cache_control(ttl) @staticmethod - def _map_binary_to_block(data: bytes, media_type: str) -> BetaContentBlockParam: + def _map_binary_data(data: bytes, media_type: str) -> BetaContentBlockParam: # Anthropic SDK accepts file-like objects (IO[bytes]) and handles base64 encoding internally if media_type.startswith('image/'): return BetaImageBlockParam( @@ -1059,10 +1059,6 @@ def _map_binary_to_block(data: bytes, media_type: str) -> BetaContentBlockParam: else: raise RuntimeError(f'Unsupported binary content media type for Anthropic: {media_type}') - @staticmethod - def _map_binary_content(item: BinaryContent) -> BetaContentBlockParam: - return AnthropicModel._map_binary_to_block(item.data, item.media_type) - @staticmethod async def _map_user_prompt( part: UserPromptPart, @@ -1078,18 +1074,18 @@ async def _map_user_prompt( elif isinstance(item, CachePoint): yield item elif isinstance(item, BinaryContent): - yield AnthropicModel._map_binary_content(item) + yield AnthropicModel._map_binary_data(item.data, item.media_type) elif isinstance(item, ImageUrl): if item.force_download: downloaded = await download_item(item, data_format='bytes') - yield AnthropicModel._map_binary_to_block(downloaded['data'], item.media_type) + yield AnthropicModel._map_binary_data(downloaded['data'], item.media_type) else: yield BetaImageBlockParam(source={'type': 'url', 'url': item.url}, type='image') elif isinstance(item, DocumentUrl): if item.media_type == 'application/pdf': if item.force_download: downloaded = await download_item(item, data_format='bytes') - yield AnthropicModel._map_binary_to_block(downloaded['data'], item.media_type) + yield AnthropicModel._map_binary_data(downloaded['data'], item.media_type) else: yield BetaRequestDocumentBlockParam( source={'url': item.url, 'type': 'url'}, type='document' From 35d8745e06e9a17244d1acae5421dd325d23c5e2 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Fri, 12 Dec 2025 23:26:15 -0500 Subject: [PATCH 10/13] update docs --- docs/input.md | 31 ++++++++++++++++++++++--------- docs/models/google.md | 20 +++++++++++++++++++- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/docs/input.md b/docs/input.md index f60516f373..768fd6b4e1 100644 --- a/docs/input.md +++ b/docs/input.md @@ -104,21 +104,34 @@ print(result.output) ## User-side download vs. direct file URL -When you provide a URL using any of `ImageUrl`, `AudioUrl`, `VideoUrl` or `DocumentUrl`, Pydantic AI will typically send the URL directly to the model API so that the download happens on their side. +When using one of `ImageUrl`, `AudioUrl`, `VideoUrl` or `DocumentUrl`, Pydantic AI will default to sending the URL to the model, so the file is downloaded on their side. -Some model APIs do not support file URLs at all or for specific file types. In the following cases, Pydantic AI will download the file content and send it as part of the API request instead: +Support for file URLs varies depending on type and provider. Pydantic AI handles this as follows: -- [`OpenAIChatModel`][pydantic_ai.models.openai.OpenAIChatModel]: `AudioUrl` and `DocumentUrl` -- [`OpenAIResponsesModel`][pydantic_ai.models.openai.OpenAIResponsesModel]: All URLs -- [`AnthropicModel`][pydantic_ai.models.anthropic.AnthropicModel]: `DocumentUrl` with media type `text/plain` -- [`GoogleModel`][pydantic_ai.models.google.GoogleModel] using GLA (Gemini Developer API): All URLs except YouTube video URLs and files uploaded to the [Files API](https://ai.google.dev/gemini-api/docs/files). -- [`BedrockConverseModel`][pydantic_ai.models.bedrock.BedrockConverseModel]: All URLs +| Model | Supported URL types | Sends URL directly | +|-------|---------------------|-------------------| +| [`OpenAIChatModel`][pydantic_ai.models.openai.OpenAIChatModel] | `ImageUrl`, `AudioUrl`, `DocumentUrl` | `ImageUrl` only | +| [`OpenAIResponsesModel`][pydantic_ai.models.openai.OpenAIResponsesModel] | `ImageUrl`, `AudioUrl`, `DocumentUrl` | Yes | +| [`AnthropicModel`][pydantic_ai.models.anthropic.AnthropicModel] | `ImageUrl`, `DocumentUrl` | Yes, except `DocumentUrl` (`text/plain`) | +| [`GoogleModel`][pydantic_ai.models.google.GoogleModel] (Vertex) | All URL types | Yes | +| [`GoogleModel`][pydantic_ai.models.google.GoogleModel] (GLA) | All URL types | [YouTube](models/google.md#document-image-audio-and-video-input) and [Files API](https://ai.google.dev/gemini-api/docs/files) URLs only | +| [`MistralModel`][pydantic_ai.models.mistral.MistralModel] | `ImageUrl`, `DocumentUrl` (PDF) | Yes | +| [`BedrockConverseModel`][pydantic_ai.models.bedrock.BedrockConverseModel] | `ImageUrl`, `DocumentUrl`, `VideoUrl` | No, defaults to `force_download` | -If the model API supports file URLs but may not be able to download a file because of crawling or access restrictions, you can instruct Pydantic AI to download the file content and send that instead of the URL by enabling the `force_download` flag on the URL object. For example, [`GoogleModel`][pydantic_ai.models.google.GoogleModel] on Vertex AI limits YouTube video URLs to one URL per request. +A model API may be unable to download a file (e.g., because of crawling or access restrictions) even if it supports file URLs. For example, [`GoogleModel`][pydantic_ai.models.google.GoogleModel] on Vertex AI limits YouTube video URLs to one URL per request. In such cases, you can instruct Pydantic AI to download the file content locally and send that instead of the URL by setting `force_download` on the URL object: + +```py {title="force_download.py" test="skip" lint="skip"} +from pydantic_ai import ImageUrl, AudioUrl, VideoUrl, DocumentUrl + +ImageUrl(url='https://example.com/image.png', force_download=True) +AudioUrl(url='https://example.com/audio.mp3', force_download=True) +VideoUrl(url='https://example.com/video.mp4', force_download=True) +DocumentUrl(url='https://example.com/doc.pdf', force_download=True) +``` ## Uploaded Files -Some model providers like Google's Gemini API support [uploading files](https://ai.google.dev/gemini-api/docs/files). You can upload a file to the model API using the client you can get from the provider and use the resulting URL as input: +Some model providers like Google's Gemini API support [uploading files](https://ai.google.dev/gemini-api/docs/files). You can upload a file using the provider's client and passing the resulting URL as input: ```py {title="file_upload.py" test="skip"} from pydantic_ai import Agent, DocumentUrl diff --git a/docs/models/google.md b/docs/models/google.md index 1f3aedb433..a5dea490a3 100644 --- a/docs/models/google.md +++ b/docs/models/google.md @@ -199,7 +199,25 @@ agent = Agent(model) ## Document, Image, Audio, and Video Input -`GoogleModel` supports multi-modal input, including documents, images, audio, and video. See the [input documentation](../input.md) for details and examples. +`GoogleModel` supports multi-modal input, including documents, images, audio, and video. + +YouTube video URLs can be passed directly to Google models: + +```py {title="youtube_input.py" test="skip" lint="skip"} +from pydantic_ai import Agent, VideoUrl +from pydantic_ai.models.google import GoogleModel + +agent = Agent(GoogleModel('gemini-2.5-flash')) +result = agent.run_sync( + [ + 'What is this video about?', + VideoUrl(url='https://www.youtube.com/watch?v=dQw4w9WgXcQ'), + ] +) +print(result.output) +``` + +See the [input documentation](../input.md) for more details and examples. ## Model settings From d9e8a011521fb63199381491706cd4c24b0fbfbc Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Sun, 14 Dec 2025 09:19:54 -0500 Subject: [PATCH 11/13] allow explicit download disallowing --- experiments/test_anthropic_changes.py | 47 ++++++ experiments/verify_cohere_multimodal.py | 137 ++++++++++++++++++ experiments/verify_mistral_pdf.py | 137 ++++++++++++++++++ pydantic_ai_slim/pydantic_ai/messages.py | 19 +-- .../pydantic_ai/models/anthropic.py | 25 ++-- .../pydantic_ai/models/bedrock.py | 5 + pydantic_ai_slim/pydantic_ai/models/gemini.py | 2 +- pydantic_ai_slim/pydantic_ai/models/google.py | 5 +- .../pydantic_ai/models/mistral.py | 4 +- pydantic_ai_slim/pydantic_ai/models/openai.py | 6 +- ..._document_url_input_no_force_download.yaml | 52 +++++++ tests/models/test_anthropic.py | 21 +++ tests/models/test_bedrock.py | 25 ++++ tests/models/test_openai.py | 34 +++++ 14 files changed, 493 insertions(+), 26 deletions(-) create mode 100644 experiments/test_anthropic_changes.py create mode 100644 experiments/verify_cohere_multimodal.py create mode 100644 experiments/verify_mistral_pdf.py create mode 100644 tests/models/cassettes/test_anthropic/test_text_document_url_input_no_force_download.yaml diff --git a/experiments/test_anthropic_changes.py b/experiments/test_anthropic_changes.py new file mode 100644 index 0000000000..0b64146ef6 --- /dev/null +++ b/experiments/test_anthropic_changes.py @@ -0,0 +1,47 @@ +"""Quick verification script for Anthropic multimodal changes.""" +import asyncio + +from pydantic_ai_slim.pydantic_ai.models.anthropic import AnthropicModel + + +async def test_map_binary_to_block(): + """Test the new _map_binary_to_block helper method.""" + + # Test with image data + image_data = b'\x89PNG\r\n\x1a\n fake image data' + image_block = AnthropicModel._map_binary_to_block(image_data, 'image/png') + assert image_block['type'] == 'image' + assert image_block['source']['type'] == 'base64' + assert image_block['source']['media_type'] == 'image/png' + print('✓ Image block creation works') + + # Test with PDF data + pdf_data = b'%PDF-1.4 fake pdf data' + pdf_block = AnthropicModel._map_binary_to_block(pdf_data, 'application/pdf') + assert pdf_block['type'] == 'document' + assert pdf_block['source']['type'] == 'base64' + assert pdf_block['source']['media_type'] == 'application/pdf' + print('✓ PDF block creation works') + + # Test with text data + text_data = b'Sample text content' + text_block = AnthropicModel._map_binary_to_block(text_data, 'text/plain') + assert text_block['type'] == 'document' + assert text_block['source']['type'] == 'text' + assert text_block['source']['media_type'] == 'text/plain' + assert text_block['source']['data'] == 'Sample text content' + print('✓ Text block creation works') + + # Test with unsupported media type + try: + AnthropicModel._map_binary_to_block(b'data', 'application/unknown') + assert False, 'Should have raised RuntimeError' + except RuntimeError as e: + assert 'Unsupported binary content media type' in str(e) + print('✓ Unsupported media type raises error') + + print('\n✅ All _map_binary_to_block tests passed!') + + +if __name__ == '__main__': + asyncio.run(test_map_binary_to_block()) diff --git a/experiments/verify_cohere_multimodal.py b/experiments/verify_cohere_multimodal.py new file mode 100644 index 0000000000..934e58e7ae --- /dev/null +++ b/experiments/verify_cohere_multimodal.py @@ -0,0 +1,137 @@ +"""Verification script: Cohere Command A Vision supports multimodal inputs. + +This script demonstrates that Cohere's Command A Vision model DOES support +multimodal inputs (images via URL and base64), contradicting our current +error message that says "Cohere does not yet support multi-modal inputs". + +Expected: API successfully processes both URL and base64 images. +""" + +import asyncio +import base64 +import os +import sys +from pathlib import Path + +# Add parent directory to path so we can import pydantic_ai +sys.path.insert(0, str(Path(__file__).parent.parent)) + +import cohere +from cohere import ImageUrl, ImageUrlContent, UserChatMessageV2 + + +async def test_cohere_image_url(): + """Test that Cohere Command A Vision accepts image URLs.""" + api_key = os.getenv('CO_API_KEY') + if not api_key: + print('ERROR: CO_API_KEY environment variable not set') + print('Please run: source .env && python experiments/verify_cohere_multimodal.py') + sys.exit(1) + + client = cohere.AsyncClientV2(api_key=api_key) + + print('Test 1: Sending image via URL...') + try: + message = UserChatMessageV2( + role='user', + content=[ + {'type': 'text', 'text': 'What is in this image? Be brief.'}, + ImageUrlContent( + type='image_url', + image_url=ImageUrl( + url='https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/200px-Cat03.jpg', + detail='auto', + ), + ), + ], + ) + + response = await client.chat(model='command-a-vision-07-2025', messages=[message], max_tokens=100) + + result_text = response.message.content[0].text + print(f'✓ SUCCESS: Image URL processed correctly') + print(f'Response: {result_text}') + return True, result_text + + except Exception as e: + print(f'✗ FAILED: {type(e).__name__}: {e}') + print(f'\nFull error: {e}') + return False, str(e) + + +async def test_cohere_image_base64(): + """Test that Cohere Command A Vision accepts base64-encoded images.""" + api_key = os.getenv('CO_API_KEY') + client = cohere.AsyncClientV2(api_key=api_key) + + print('\n\nTest 2: Sending image via base64 data URI...') + + # Simple 1x1 red PNG image (base64 encoded) + red_pixel_png = ( + 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg==' + ) + data_uri = f'data:image/png;base64,{red_pixel_png}' + + try: + message = UserChatMessageV2( + role='user', + content=[ + {'type': 'text', 'text': 'What color is this pixel?'}, + ImageUrlContent(type='image_url', image_url=ImageUrl(url=data_uri, detail='auto')), + ], + ) + + response = await client.chat(model='command-a-vision-07-2025', messages=[message], max_tokens=100) + + result_text = response.message.content[0].text + print(f'✓ SUCCESS: Base64 image processed correctly') + print(f'Response: {result_text}') + return True, result_text + + except Exception as e: + print(f'✗ FAILED: {type(e).__name__}: {e}') + print(f'\nFull error: {e}') + return False, str(e) + + +async def main(): + print('=' * 80) + print('Cohere Multimodal Support Verification') + print('=' * 80) + print() + print('Purpose: Verify that Cohere Command A Vision DOES support multimodal') + print('Model: command-a-vision-07-2025') + print('Expected: Both tests succeed') + print() + + url_success, url_response = await test_cohere_image_url() + base64_success, base64_response = await test_cohere_image_base64() + + print('\n' + '=' * 80) + print('RESULTS:') + print('=' * 80) + print(f'Image URL test: {"✓ PASS" if url_success else "✗ FAIL"}') + print(f'Base64 image test: {"✓ PASS" if base64_success else "✗ FAIL"}') + print() + + if url_success and base64_success: + print('✓ VERIFICATION COMPLETE: Cohere Command A Vision DOES support multimodal') + print(' → Our current code incorrectly blocks multimodal inputs') + print(' → We should implement ImageUrl and BinaryContent support') + else: + print('✗ SOME TESTS FAILED - See errors above') + + print('\n' + '=' * 80) + print('API Responses:') + print('=' * 80) + if url_success: + print(f'\nURL test response:\n{url_response}') + if base64_success: + print(f'\nBase64 test response:\n{base64_response}') + + return url_success and base64_success + + +if __name__ == '__main__': + success = asyncio.run(main()) + sys.exit(0 if success else 1) diff --git a/experiments/verify_mistral_pdf.py b/experiments/verify_mistral_pdf.py new file mode 100644 index 0000000000..453ac899fc --- /dev/null +++ b/experiments/verify_mistral_pdf.py @@ -0,0 +1,137 @@ +"""Verification script: Mistral vision API does NOT support PDFs. + +This script demonstrates that Mistral's vision/chat API rejects PDF files. +The Mistral SDK has `DocumentURLChunk` but it's for the separate Document AI API, +not for the vision models. + +Expected: API will reject the PDF with an error. +""" + +import asyncio +import os +import sys +from pathlib import Path + +# Add parent directory to path so we can import pydantic_ai +sys.path.insert(0, str(Path(__file__).parent.parent)) + +from mistralai import Mistral + + +async def test_mistral_pdf_rejection(): + """Test that Mistral vision API rejects PDFs.""" + api_key = os.getenv('MISTRAL_API_KEY') + if not api_key: + print('ERROR: MISTRAL_API_KEY environment variable not set') + print('Please run: source .env && python experiments/verify_mistral_pdf.py') + sys.exit(1) + + client = Mistral(api_key=api_key) + + # Test 1: Try to send a PDF URL (will fail) + print('Test 1: Attempting to send PDF via DocumentURLChunk...') + try: + from mistralai.models import DocumentURLChunk, UserMessage + + messages = [ + UserMessage( + role='user', + content=[ + {'type': 'text', 'text': 'What is in this document?'}, + DocumentURLChunk( + document_url='https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf', + type='document_url', + ), + ], + ) + ] + + response = await client.chat.stream_async( + model='mistral-large-2512', messages=messages, max_tokens=100 + ) + + result_text = '' + async for chunk in response: + if chunk.data.choices[0].delta.content: + result_text += chunk.data.choices[0].delta.content + + print(f'ERROR: API accepted PDF! Response: {result_text}') + print('This should not happen - Mistral vision API should reject PDFs') + return False + + except Exception as e: + print(f'✓ EXPECTED: API rejected PDF with error: {type(e).__name__}: {e}') + print(f'\nFull error: {e}') + return True + + +async def test_mistral_image_works(): + """Verify that images DO work (for comparison).""" + api_key = os.getenv('MISTRAL_API_KEY') + client = Mistral(api_key=api_key) + + print('\n\nTest 2: Verifying images work correctly...') + try: + from mistralai.models import ImageURLChunk, UserMessage + + messages = [ + UserMessage( + role='user', + content=[ + {'type': 'text', 'text': 'What is in this image?'}, + ImageURLChunk( + image_url='https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/200px-Cat03.jpg' + ), + ], + ) + ] + + response = await client.chat.stream_async( + model='mistral-large-2512', messages=messages, max_tokens=50 + ) + + result_text = '' + async for chunk in response: + if chunk.data.choices[0].delta.content: + result_text += chunk.data.choices[0].delta.content + + print(f'✓ SUCCESS: Image processed correctly') + print(f'Response: {result_text[:100]}...') + return True + + except Exception as e: + print(f'ERROR: Image request failed: {e}') + return False + + +async def main(): + print('=' * 80) + print('Mistral PDF Support Verification') + print('=' * 80) + print() + print('Purpose: Verify that Mistral vision API does NOT support PDFs') + print('Expected: Test 1 fails (PDF rejected), Test 2 succeeds (image works)') + print() + + pdf_rejected = await test_mistral_pdf_rejection() + image_works = await test_mistral_image_works() + + print('\n' + '=' * 80) + print('RESULTS:') + print('=' * 80) + print(f'PDF rejected (expected): {"✓ PASS" if pdf_rejected else "✗ FAIL"}') + print(f'Image works (expected): {"✓ PASS" if image_works else "✗ FAIL"}') + print() + + if pdf_rejected and image_works: + print('✓ VERIFICATION COMPLETE: Mistral vision API does NOT support PDFs') + print(' → Our code should reject PDFs for Mistral models') + else: + print('✗ UNEXPECTED RESULTS') + + return pdf_rejected and image_works + + +if __name__ == '__main__': + success = asyncio.run(main()) + sys.exit(0 if success else 1) diff --git a/pydantic_ai_slim/pydantic_ai/messages.py b/pydantic_ai_slim/pydantic_ai/messages.py index cc24ec3d70..48273d9af7 100644 --- a/pydantic_ai_slim/pydantic_ai/messages.py +++ b/pydantic_ai_slim/pydantic_ai/messages.py @@ -120,11 +120,12 @@ class FileUrl(ABC): _: KW_ONLY - force_download: bool = False - """For OpenAI and Google APIs it: + force_download: bool | None = None + """Controls download behavior for file URLs. - * If True, the file is downloaded and the data is sent to the model as bytes. - * If False, the URL is sent directly to the model and no download is performed. + * If None (default), the provider decides whether to download or send the URL directly. + * If True, the file is always downloaded and sent as bytes. + * If False, the URL is always sent directly without downloading (useful for proxies). """ vendor_metadata: dict[str, Any] | None = None @@ -149,7 +150,7 @@ def __init__( *, media_type: str | None = None, identifier: str | None = None, - force_download: bool = False, + force_download: bool | None = None, vendor_metadata: dict[str, Any] | None = None, ) -> None: self.url = url @@ -213,7 +214,7 @@ def __init__( *, media_type: str | None = None, identifier: str | None = None, - force_download: bool = False, + force_download: bool | None = None, vendor_metadata: dict[str, Any] | None = None, kind: Literal['video-url'] = 'video-url', # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs. @@ -289,7 +290,7 @@ def __init__( *, media_type: str | None = None, identifier: str | None = None, - force_download: bool = False, + force_download: bool | None = None, vendor_metadata: dict[str, Any] | None = None, kind: Literal['audio-url'] = 'audio-url', # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs. @@ -352,7 +353,7 @@ def __init__( *, media_type: str | None = None, identifier: str | None = None, - force_download: bool = False, + force_download: bool | None = None, vendor_metadata: dict[str, Any] | None = None, kind: Literal['image-url'] = 'image-url', # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs. @@ -410,7 +411,7 @@ def __init__( *, media_type: str | None = None, identifier: str | None = None, - force_download: bool = False, + force_download: bool | None = None, vendor_metadata: dict[str, Any] | None = None, kind: Literal['document-url'] = 'document-url', # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs. diff --git a/pydantic_ai_slim/pydantic_ai/models/anthropic.py b/pydantic_ai_slim/pydantic_ai/models/anthropic.py index 83f1d53ae4..9afb7da26e 100644 --- a/pydantic_ai_slim/pydantic_ai/models/anthropic.py +++ b/pydantic_ai_slim/pydantic_ai/models/anthropic.py @@ -1076,14 +1076,14 @@ async def _map_user_prompt( elif isinstance(item, BinaryContent): yield AnthropicModel._map_binary_data(item.data, item.media_type) elif isinstance(item, ImageUrl): - if item.force_download: + if item.force_download is True: downloaded = await download_item(item, data_format='bytes') yield AnthropicModel._map_binary_data(downloaded['data'], item.media_type) else: yield BetaImageBlockParam(source={'type': 'url', 'url': item.url}, type='image') elif isinstance(item, DocumentUrl): if item.media_type == 'application/pdf': - if item.force_download: + if item.force_download is True: downloaded = await download_item(item, data_format='bytes') yield AnthropicModel._map_binary_data(downloaded['data'], item.media_type) else: @@ -1091,13 +1091,20 @@ async def _map_user_prompt( source={'url': item.url, 'type': 'url'}, type='document' ) elif item.media_type == 'text/plain': - downloaded_item = await download_item(item, data_format='text') - yield BetaRequestDocumentBlockParam( - source=BetaPlainTextSourceParam( - data=downloaded_item['data'], media_type=item.media_type, type='text' - ), - type='document', - ) + # the request will fail as evidenced by test_text_document_url_input_no_force_download + # but if the user is explicitly setting `force_download` to `False` we comply + if item.force_download is False: + yield BetaRequestDocumentBlockParam( + source={'url': item.url, 'type': 'url'}, type='document' + ) + else: + downloaded_item = await download_item(item, data_format='text') + yield BetaRequestDocumentBlockParam( + source=BetaPlainTextSourceParam( + data=downloaded_item['data'], media_type=item.media_type, type='text' + ), + type='document', + ) else: # pragma: no cover raise RuntimeError(f'Unsupported media type: {item.media_type}') else: diff --git a/pydantic_ai_slim/pydantic_ai/models/bedrock.py b/pydantic_ai_slim/pydantic_ai/models/bedrock.py index 1424d2cb58..eb780452d8 100644 --- a/pydantic_ai_slim/pydantic_ai/models/bedrock.py +++ b/pydantic_ai_slim/pydantic_ai/models/bedrock.py @@ -637,6 +637,11 @@ async def _map_user_prompt(part: UserPromptPart, document_count: Iterator[int]) else: raise NotImplementedError('Binary content is not supported yet.') elif isinstance(item, ImageUrl | DocumentUrl | VideoUrl): + if item.force_download is False: + raise RuntimeError( + 'Bedrock does not support URL passthrough. ' + 'Remove force_download=False or use BinaryContent instead.' + ) downloaded_item = await download_item(item, data_format='bytes', type_format='extension') format = downloaded_item['data_type'] if item.kind == 'image-url': diff --git a/pydantic_ai_slim/pydantic_ai/models/gemini.py b/pydantic_ai_slim/pydantic_ai/models/gemini.py index 8d73ce76bd..e53e57af5f 100644 --- a/pydantic_ai_slim/pydantic_ai/models/gemini.py +++ b/pydantic_ai_slim/pydantic_ai/models/gemini.py @@ -381,7 +381,7 @@ async def _map_user_prompt(self, part: UserPromptPart) -> list[_GeminiPartUnion] file_data = _GeminiFileDataPart(file_data={'file_uri': item.url, 'mime_type': item.media_type}) content.append(file_data) elif isinstance(item, FileUrl): - if self.system == 'google-gla' or item.force_download: + if item.force_download is True or (item.force_download is None and self.system == 'google-gla'): downloaded_item = await download_item(item, data_format='base64') inline_data = _GeminiInlineDataPart( inline_data={'data': downloaded_item['data'], 'mime_type': downloaded_item['data_type']} diff --git a/pydantic_ai_slim/pydantic_ai/models/google.py b/pydantic_ai_slim/pydantic_ai/models/google.py index bf3ba0206d..303cd017e6 100644 --- a/pydantic_ai_slim/pydantic_ai/models/google.py +++ b/pydantic_ai_slim/pydantic_ai/models/google.py @@ -623,10 +623,11 @@ async def _map_user_prompt(self, part: UserPromptPart) -> list[PartDict]: part_dict['video_metadata'] = cast(VideoMetadataDict, item.vendor_metadata) content.append(part_dict) elif isinstance(item, FileUrl): - if item.force_download or ( + if item.force_download is True or ( # google-gla does not support passing file urls directly, except for youtube videos # (see above) and files uploaded to the file API (which cannot be downloaded anyway) - self.system == 'google-gla' + item.force_download is None + and self.system == 'google-gla' and not item.url.startswith(r'https://generativelanguage.googleapis.com/v1beta/files') ): downloaded_item = await download_item(item, data_format='bytes') diff --git a/pydantic_ai_slim/pydantic_ai/models/mistral.py b/pydantic_ai_slim/pydantic_ai/models/mistral.py index 2b0e544c91..a48e648c79 100644 --- a/pydantic_ai_slim/pydantic_ai/models/mistral.py +++ b/pydantic_ai_slim/pydantic_ai/models/mistral.py @@ -585,7 +585,7 @@ async def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage: if isinstance(item, str): content.append(MistralTextChunk(text=item)) elif isinstance(item, ImageUrl): - if item.force_download: + if item.force_download is True: downloaded = await download_item(item, data_format='base64_uri') image_url = MistralImageURL(url=downloaded['data']) content.append(MistralImageURLChunk(image_url=image_url, type='image_url')) @@ -601,7 +601,7 @@ async def _map_user_prompt(self, part: UserPromptPart) -> MistralUserMessage: raise RuntimeError('BinaryContent other than image or PDF is not supported in Mistral.') elif isinstance(item, DocumentUrl): if item.media_type == 'application/pdf': - if item.force_download: + if item.force_download is True: downloaded = await download_item(item, data_format='base64_uri') content.append( MistralDocumentURLChunk(document_url=downloaded['data'], type='document_url') diff --git a/pydantic_ai_slim/pydantic_ai/models/openai.py b/pydantic_ai_slim/pydantic_ai/models/openai.py index 5a59a180de..076e9d48b0 100644 --- a/pydantic_ai_slim/pydantic_ai/models/openai.py +++ b/pydantic_ai_slim/pydantic_ai/models/openai.py @@ -981,7 +981,7 @@ async def _map_user_prompt(self, part: UserPromptPart) -> chat.ChatCompletionUse image_url: ImageURL = {'url': item.url} if metadata := item.vendor_metadata: image_url['detail'] = metadata.get('detail', 'auto') - if item.force_download: + if item.force_download is True: image_content = await download_item(item, data_format='base64_uri', type_format='extension') image_url['url'] = image_content['data'] content.append(ChatCompletionContentPartImageParam(image_url=image_url, type='image_url')) @@ -1880,7 +1880,7 @@ async def _map_user_prompt(part: UserPromptPart) -> responses.EasyInputMessagePa image_url = item.url if metadata := item.vendor_metadata: detail = cast(Literal['auto', 'low', 'high'], metadata.get('detail', 'auto')) - if item.force_download: + if item.force_download is True: downloaded_item = await download_item(item, data_format='base64_uri', type_format='extension') image_url = downloaded_item['data'] @@ -1892,7 +1892,7 @@ async def _map_user_prompt(part: UserPromptPart) -> responses.EasyInputMessagePa ) ) elif isinstance(item, AudioUrl | DocumentUrl): - if item.force_download: + if item.force_download is True: downloaded_item = await download_item(item, data_format='base64_uri', type_format='extension') content.append( responses.ResponseInputFileParam( diff --git a/tests/models/cassettes/test_anthropic/test_text_document_url_input_no_force_download.yaml b/tests/models/cassettes/test_anthropic/test_text_document_url_input_no_force_download.yaml new file mode 100644 index 0000000000..9af87c4a86 --- /dev/null +++ b/tests/models/cassettes/test_anthropic/test_text_document_url_input_no_force_download.yaml @@ -0,0 +1,52 @@ +interactions: +- request: + headers: + accept: + - application/json + accept-encoding: + - gzip, deflate + connection: + - keep-alive + content-length: + - '289' + content-type: + - application/json + host: + - api.anthropic.com + method: POST + parsed_body: + max_tokens: 4096 + messages: + - content: + - text: What is the main content on this document? + type: text + - source: + type: url + url: https://example-files.online-convert.com/document/txt/example.txt + type: document + role: user + model: claude-sonnet-4-5 + stream: false + uri: https://api.anthropic.com/v1/messages?beta=true + response: + headers: + connection: + - keep-alive + content-length: + - '202' + content-type: + - application/json + strict-transport-security: + - max-age=31536000; includeSubDomains; preload + vary: + - Accept-Encoding + parsed_body: + error: + message: 'messages.0.content.1.image.source.base64.data: The file format is invalid or unsupported' + type: invalid_request_error + request_id: req_011CW6iSW9a42ghQfXsaHqWi + type: error + status: + code: 400 + message: Bad Request +version: 1 diff --git a/tests/models/test_anthropic.py b/tests/models/test_anthropic.py index ba14ad4575..f3d988a900 100644 --- a/tests/models/test_anthropic.py +++ b/tests/models/test_anthropic.py @@ -1868,6 +1868,27 @@ async def test_text_document_url_input(allow_model_requests: None, anthropic_api """) +@pytest.mark.vcr() +async def test_text_document_url_input_no_force_download(allow_model_requests: None, anthropic_api_key: str): + """Test text/plain DocumentUrl with force_download=False sends URL directly to Anthropic. + + Anthropic doesn't support URL passthrough for text/plain documents (only PDFs), + so this test verifies the expected API error when using force_download=False. + """ + from pydantic_ai.exceptions import ModelHTTPError + + m = AnthropicModel('claude-sonnet-4-5', provider=AnthropicProvider(api_key=anthropic_api_key)) + agent = Agent(m) + + text_document_url = DocumentUrl( + url='https://example-files.online-convert.com/document/txt/example.txt', + force_download=False, + ) + + with pytest.raises(ModelHTTPError, match='invalid_request_error'): + await agent.run(['What is the main content on this document?', text_document_url]) + + async def test_text_document_as_binary_content_input( allow_model_requests: None, anthropic_api_key: str, text_document_content: BinaryContent ): diff --git a/tests/models/test_bedrock.py b/tests/models/test_bedrock.py index ceb0e76680..67b30b69ff 100644 --- a/tests/models/test_bedrock.py +++ b/tests/models/test_bedrock.py @@ -729,6 +729,31 @@ async def test_text_document_url_input(allow_model_requests: None, bedrock_provi """) +async def test_url_force_download_false_raises_error(bedrock_provider: BedrockProvider) -> None: + """Test that force_download=False raises RuntimeError since Bedrock doesn't support URL passthrough.""" + m = BedrockConverseModel('anthropic.claude-v2', provider=bedrock_provider) + + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test document', + DocumentUrl( + url='file://local/path/to/doc.pdf', + media_type='application/pdf', + force_download=False, + ), + ] + ) + ] + ) + ] + + with pytest.raises(RuntimeError, match='Bedrock does not support URL passthrough'): + await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + @pytest.mark.vcr() async def test_text_as_binary_content_input(allow_model_requests: None, bedrock_provider: BedrockProvider): m = BedrockConverseModel('us.amazon.nova-pro-v1:0', provider=bedrock_provider) diff --git a/tests/models/test_openai.py b/tests/models/test_openai.py index d21af371ba..ccf34cddca 100644 --- a/tests/models/test_openai.py +++ b/tests/models/test_openai.py @@ -1063,6 +1063,40 @@ async def test_audio_url_force_download_responses() -> None: assert mock_download.call_args[0][0].url == 'https://example.com/audio.mp3' +async def test_document_url_passthrough_non_http_url() -> None: + """Test that force_download=False allows non-http URLs to passthrough without validation. + + This enables proxy use cases where middleware intercepts and transforms URLs. + See: https://github.com/pydantic/pydantic-ai/issues/3724 + """ + from unittest.mock import AsyncMock, patch + + m = OpenAIResponsesModel('gpt-4.5-nano', provider=OpenAIProvider(api_key='test-key')) + + with patch('pydantic_ai.models.openai.download_item', new_callable=AsyncMock) as mock_download: + messages = [ + ModelRequest( + parts=[ + UserPromptPart( + content=[ + 'Test document', + DocumentUrl( + url='file://local/path/to/doc.pdf', + media_type='application/pdf', + force_download=False, + ), + ] + ) + ] + ) + ] + + await m._map_messages(messages, {}, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage,reportArgumentType] + + # Verify download was NOT called - the non-http URL passed through without validation + mock_download.assert_not_called() + + @pytest.mark.vcr() async def test_image_url_tool_response(allow_model_requests: None, openai_api_key: str): m = OpenAIChatModel('gpt-4o', provider=OpenAIProvider(api_key=openai_api_key)) From 8a98f770e3b17d4bc4b4bfc7efc9a19656546f2b Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Sun, 14 Dec 2025 09:45:17 -0500 Subject: [PATCH 12/13] fix linting issue and update doc --- docs/input.md | 31 ++++++++++++++++--- .../pydantic_ai/models/bedrock.py | 4 +-- tests/models/test_bedrock.py | 2 +- 3 files changed, 29 insertions(+), 8 deletions(-) diff --git a/docs/input.md b/docs/input.md index 768fd6b4e1..5c1378f49b 100644 --- a/docs/input.md +++ b/docs/input.md @@ -118,17 +118,38 @@ Support for file URLs varies depending on type and provider. Pydantic AI handles | [`MistralModel`][pydantic_ai.models.mistral.MistralModel] | `ImageUrl`, `DocumentUrl` (PDF) | Yes | | [`BedrockConverseModel`][pydantic_ai.models.bedrock.BedrockConverseModel] | `ImageUrl`, `DocumentUrl`, `VideoUrl` | No, defaults to `force_download` | -A model API may be unable to download a file (e.g., because of crawling or access restrictions) even if it supports file URLs. For example, [`GoogleModel`][pydantic_ai.models.google.GoogleModel] on Vertex AI limits YouTube video URLs to one URL per request. In such cases, you can instruct Pydantic AI to download the file content locally and send that instead of the URL by setting `force_download` on the URL object: +### How to use private URLs or disallow local download -```py {title="force_download.py" test="skip" lint="skip"} -from pydantic_ai import ImageUrl, AudioUrl, VideoUrl, DocumentUrl +The `force_download` parameter controls how file URLs are handled: +| Value | Behavior | +|-------|----------| +| `None` (default) | Provider decides - downloads if needed, sends URL if supported | +| `True` | Always download content locally and send as bytes | +| `False` | Never download - always pass URL directly to the API | + +Use `force_download=True` when a model API is unable to download a file (e.g., because the URL isn't publicly accessible, or may get blocked by crawling or access restrictions): + +```py {title="force_download_true.py" test="skip" lint="skip"} +from pydantic_ai import ImageUrl, DocumentUrl + +# Force local download when the API can't fetch the URL ImageUrl(url='https://example.com/image.png', force_download=True) -AudioUrl(url='https://example.com/audio.mp3', force_download=True) -VideoUrl(url='https://example.com/video.mp4', force_download=True) DocumentUrl(url='https://example.com/doc.pdf', force_download=True) ``` +Use `force_download=False` to pass URLs directly without any download attempt. This is useful when you have a proxy or middleware that intercepts requests: + +```py {title="force_download_false.py" test="skip" lint="skip"} +from pydantic_ai import DocumentUrl + +# Pass URL as-is (useful for proxies handling custom URL schemes) +DocumentUrl(url='file://local/path/to/doc.pdf', force_download=False) +``` + +!!! note + Setting `force_download=False` for providers or content types that don't support URL passthrough will result in an API error. For example, Bedrock doesn't support URLs at all, and Anthropic doesn't support URL passthrough for `text/plain` documents. (As of Dec 2025) + ## Uploaded Files Some model providers like Google's Gemini API support [uploading files](https://ai.google.dev/gemini-api/docs/files). You can upload a file using the provider's client and passing the resulting URL as input: diff --git a/pydantic_ai_slim/pydantic_ai/models/bedrock.py b/pydantic_ai_slim/pydantic_ai/models/bedrock.py index eacb9668eb..6cc6ea028c 100644 --- a/pydantic_ai_slim/pydantic_ai/models/bedrock.py +++ b/pydantic_ai_slim/pydantic_ai/models/bedrock.py @@ -2,7 +2,7 @@ import functools import typing -from collections.abc import AsyncIterator, Iterable, Iterator, Mapping +from collections.abc import AsyncIterator, Iterable, Iterator, Mapping, Sequence from contextlib import asynccontextmanager from dataclasses import dataclass, field from datetime import datetime @@ -545,7 +545,7 @@ def _map_tool_config( async def _map_messages( # noqa: C901 self, - messages: list[ModelMessage], + messages: Sequence[ModelMessage], model_request_parameters: ModelRequestParameters, model_settings: BedrockModelSettings | None, ) -> tuple[list[SystemContentBlockTypeDef], list[MessageUnionTypeDef]]: diff --git a/tests/models/test_bedrock.py b/tests/models/test_bedrock.py index c99dffc69e..2fb6d91c49 100644 --- a/tests/models/test_bedrock.py +++ b/tests/models/test_bedrock.py @@ -761,7 +761,7 @@ async def test_url_force_download_false_raises_error(bedrock_provider: BedrockPr ] with pytest.raises(RuntimeError, match='Bedrock does not support URL passthrough'): - await m._map_messages(messages, ModelRequestParameters()) # pyright: ignore[reportPrivateUsage,reportArgumentType] + await m._map_messages(messages, ModelRequestParameters(), None) # pyright: ignore[reportPrivateUsage] @pytest.mark.vcr() From 45d35e57b7a46c839cbb7d384b255191d159a7b7 Mon Sep 17 00:00:00 2001 From: David Sanchez <64162682+dsfaccini@users.noreply.github.com> Date: Sun, 14 Dec 2025 10:13:46 -0500 Subject: [PATCH 13/13] fix tests --- tests/test_agent.py | 4 ++-- tests/test_messages.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/test_agent.py b/tests/test_agent.py index 6ce2d91c54..9eb620f599 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -4573,7 +4573,7 @@ def test_image_url_serializable_missing_media_type(): 'Hello', { 'url': 'https://example.com/chart.jpeg', - 'force_download': False, + 'force_download': None, 'vendor_metadata': None, 'kind': 'image-url', 'media_type': 'image/jpeg', @@ -4643,7 +4643,7 @@ def test_image_url_serializable(): 'Hello', { 'url': 'https://example.com/chart', - 'force_download': False, + 'force_download': None, 'vendor_metadata': None, 'kind': 'image-url', 'media_type': 'image/jpeg', diff --git a/tests/test_messages.py b/tests/test_messages.py index 80a0bc3e56..257569c02d 100644 --- a/tests/test_messages.py +++ b/tests/test_messages.py @@ -609,7 +609,7 @@ def test_image_url_validation_with_optional_identifier(): assert image_url_ta.dump_python(image) == snapshot( { 'url': 'https://example.com/image.jpg', - 'force_download': False, + 'force_download': None, 'vendor_metadata': None, 'kind': 'image-url', 'media_type': 'image/jpeg', @@ -626,7 +626,7 @@ def test_image_url_validation_with_optional_identifier(): assert image_url_ta.dump_python(image) == snapshot( { 'url': 'https://example.com/image.jpg', - 'force_download': False, + 'force_download': None, 'vendor_metadata': None, 'kind': 'image-url', 'media_type': 'image/png',