11from __future__ import annotations as _annotations
22
33import base64
4+ import hashlib
45from abc import ABC , abstractmethod
56from collections .abc import Sequence
67from dataclasses import KW_ONLY , dataclass , field , replace
@@ -88,6 +89,13 @@ def otel_message_parts(self, settings: InstrumentationSettings) -> list[_otel_me
8889 __repr__ = _utils .dataclasses_no_defaults_repr
8990
9091
92+ def _multi_modal_content_identifier (identifier : str | bytes ) -> str :
93+ """Generate stable identifier for multi-modal content to help LLM in finding a specific file in tool call responses."""
94+ if isinstance (identifier , str ):
95+ identifier = identifier .encode ('utf-8' )
96+ return hashlib .sha1 (identifier ).hexdigest ()[:6 ]
97+
98+
9199@dataclass (init = False , repr = False )
92100class FileUrl (ABC ):
93101 """Abstract base class for any URL-based file."""
@@ -115,17 +123,31 @@ class FileUrl(ABC):
115123 compare = False , default = None
116124 )
117125
126+ identifier : str | None = None
127+ """The identifier of the file, such as a unique ID. generating one from the url if not explicitly set
128+
129+ This identifier can be provided to the model in a message to allow it to refer to this file in a tool call argument,
130+ and the tool can look up the file in question by iterating over the message history and finding the matching `FileUrl`.
131+
132+ This identifier is only automatically passed to the model when the `FileUrl` is returned by a tool.
133+ If you're passing the `FileUrl` as a user message, it's up to you to include a separate text part with the identifier,
134+ e.g. "This is file <identifier>:" preceding the `FileUrl`.
135+ """
136+
118137 def __init__ (
119138 self ,
120139 url : str ,
140+ * ,
121141 force_download : bool = False ,
122142 vendor_metadata : dict [str , Any ] | None = None ,
123143 media_type : str | None = None ,
144+ identifier : str | None = None ,
124145 ) -> None :
125146 self .url = url
126- self .vendor_metadata = vendor_metadata
127147 self .force_download = force_download
148+ self .vendor_metadata = vendor_metadata
128149 self ._media_type = media_type
150+ self .identifier = identifier or _multi_modal_content_identifier (url )
129151
130152 @pydantic .computed_field
131153 @property
@@ -162,11 +184,12 @@ class VideoUrl(FileUrl):
162184 def __init__ (
163185 self ,
164186 url : str ,
187+ * ,
165188 force_download : bool = False ,
166189 vendor_metadata : dict [str , Any ] | None = None ,
167190 media_type : str | None = None ,
168191 kind : Literal ['video-url' ] = 'video-url' ,
169- * ,
192+ identifier : str | None = None ,
170193 # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
171194 _media_type : str | None = None ,
172195 ) -> None :
@@ -175,6 +198,7 @@ def __init__(
175198 force_download = force_download ,
176199 vendor_metadata = vendor_metadata ,
177200 media_type = media_type or _media_type ,
201+ identifier = identifier ,
178202 )
179203 self .kind = kind
180204
@@ -235,11 +259,12 @@ class AudioUrl(FileUrl):
235259 def __init__ (
236260 self ,
237261 url : str ,
262+ * ,
238263 force_download : bool = False ,
239264 vendor_metadata : dict [str , Any ] | None = None ,
240265 media_type : str | None = None ,
241266 kind : Literal ['audio-url' ] = 'audio-url' ,
242- * ,
267+ identifier : str | None = None ,
243268 # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
244269 _media_type : str | None = None ,
245270 ) -> None :
@@ -248,6 +273,7 @@ def __init__(
248273 force_download = force_download ,
249274 vendor_metadata = vendor_metadata ,
250275 media_type = media_type or _media_type ,
276+ identifier = identifier ,
251277 )
252278 self .kind = kind
253279
@@ -295,11 +321,12 @@ class ImageUrl(FileUrl):
295321 def __init__ (
296322 self ,
297323 url : str ,
324+ * ,
298325 force_download : bool = False ,
299326 vendor_metadata : dict [str , Any ] | None = None ,
300327 media_type : str | None = None ,
301328 kind : Literal ['image-url' ] = 'image-url' ,
302- * ,
329+ identifier : str | None = None ,
303330 # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
304331 _media_type : str | None = None ,
305332 ) -> None :
@@ -308,6 +335,7 @@ def __init__(
308335 force_download = force_download ,
309336 vendor_metadata = vendor_metadata ,
310337 media_type = media_type or _media_type ,
338+ identifier = identifier ,
311339 )
312340 self .kind = kind
313341
@@ -350,11 +378,12 @@ class DocumentUrl(FileUrl):
350378 def __init__ (
351379 self ,
352380 url : str ,
381+ * ,
353382 force_download : bool = False ,
354383 vendor_metadata : dict [str , Any ] | None = None ,
355384 media_type : str | None = None ,
356385 kind : Literal ['document-url' ] = 'document-url' ,
357- * ,
386+ identifier : str | None = None ,
358387 # Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
359388 _media_type : str | None = None ,
360389 ) -> None :
@@ -363,6 +392,7 @@ def __init__(
363392 force_download = force_download ,
364393 vendor_metadata = vendor_metadata ,
365394 media_type = media_type or _media_type ,
395+ identifier = identifier ,
366396 )
367397 self .kind = kind
368398
@@ -405,24 +435,26 @@ def format(self) -> DocumentFormat:
405435 raise ValueError (f'Unknown document media type: { media_type } ' ) from e
406436
407437
408- @dataclass (repr = False )
438+ @dataclass (init = False , repr = False )
409439class BinaryContent :
410440 """Binary content, e.g. an audio or image file."""
411441
412442 data : bytes
413443 """The binary data."""
414444
415- media_type : AudioMediaType | ImageMediaType | DocumentMediaType | str
416- """The media type of the binary data."""
417-
418445 _ : KW_ONLY
419446
420- identifier : str | None = None
421- """Identifier for the binary content, such as a URL or unique ID.
447+ media_type : AudioMediaType | ImageMediaType | DocumentMediaType | str
448+ """The media type of the binary data."""
422449
423- This identifier can be provided to the model in a message to allow it to refer to this file in a tool call argument, and the tool can look up the file in question by iterating over the message history and finding the matching `BinaryContent`.
450+ identifier : str
451+ """Identifier for the binary content, such as a unique ID. generating one from the data if not explicitly set
452+ This identifier can be provided to the model in a message to allow it to refer to this file in a tool call argument,
453+ and the tool can look up the file in question by iterating over the message history and finding the matching `BinaryContent`.
424454
425- This identifier is only automatically passed to the model when the `BinaryContent` is returned by a tool. If you're passing the `BinaryContent` as a user message, it's up to you to include a separate text part with the identifier, e.g. "This is file <identifier>:" preceding the `BinaryContent`.
455+ This identifier is only automatically passed to the model when the `BinaryContent` is returned by a tool.
456+ If you're passing the `BinaryContent` as a user message, it's up to you to include a separate text part with the identifier,
457+ e.g. "This is file <identifier>:" preceding the `BinaryContent`.
426458 """
427459
428460 vendor_metadata : dict [str , Any ] | None = None
@@ -435,6 +467,21 @@ class BinaryContent:
435467 kind : Literal ['binary' ] = 'binary'
436468 """Type identifier, this is available on all parts as a discriminator."""
437469
470+ def __init__ (
471+ self ,
472+ data : bytes ,
473+ * ,
474+ media_type : AudioMediaType | ImageMediaType | DocumentMediaType | str ,
475+ identifier : str | None = None ,
476+ vendor_metadata : dict [str , Any ] | None = None ,
477+ kind : Literal ['binary' ] = 'binary' ,
478+ ) -> None :
479+ self .data = data
480+ self .media_type = media_type
481+ self .identifier = identifier or _multi_modal_content_identifier (data )
482+ self .vendor_metadata = vendor_metadata
483+ self .kind = kind
484+
438485 @property
439486 def is_audio (self ) -> bool :
440487 """Return `True` if the media type is an audio type."""
0 commit comments