pydantic-ai-slim 0.8.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pydantic-ai-slim might be problematic. Click here for more details.
- pydantic_ai/__init__.py +28 -2
- pydantic_ai/_a2a.py +1 -1
- pydantic_ai/_agent_graph.py +323 -156
- pydantic_ai/_function_schema.py +5 -5
- pydantic_ai/_griffe.py +2 -1
- pydantic_ai/_otel_messages.py +2 -2
- pydantic_ai/_output.py +31 -35
- pydantic_ai/_parts_manager.py +7 -5
- pydantic_ai/_run_context.py +3 -1
- pydantic_ai/_system_prompt.py +2 -2
- pydantic_ai/_tool_manager.py +32 -28
- pydantic_ai/_utils.py +14 -26
- pydantic_ai/ag_ui.py +82 -51
- pydantic_ai/agent/__init__.py +70 -9
- pydantic_ai/agent/abstract.py +35 -4
- pydantic_ai/agent/wrapper.py +6 -0
- pydantic_ai/builtin_tools.py +2 -2
- pydantic_ai/common_tools/duckduckgo.py +4 -2
- pydantic_ai/durable_exec/temporal/__init__.py +4 -2
- pydantic_ai/durable_exec/temporal/_agent.py +93 -11
- pydantic_ai/durable_exec/temporal/_function_toolset.py +53 -6
- pydantic_ai/durable_exec/temporal/_logfire.py +1 -1
- pydantic_ai/durable_exec/temporal/_mcp_server.py +2 -1
- pydantic_ai/durable_exec/temporal/_model.py +2 -2
- pydantic_ai/durable_exec/temporal/_run_context.py +2 -1
- pydantic_ai/durable_exec/temporal/_toolset.py +2 -1
- pydantic_ai/exceptions.py +45 -2
- pydantic_ai/format_prompt.py +2 -2
- pydantic_ai/mcp.py +15 -27
- pydantic_ai/messages.py +149 -42
- pydantic_ai/models/__init__.py +6 -4
- pydantic_ai/models/anthropic.py +9 -16
- pydantic_ai/models/bedrock.py +50 -56
- pydantic_ai/models/cohere.py +3 -3
- pydantic_ai/models/fallback.py +2 -2
- pydantic_ai/models/function.py +25 -23
- pydantic_ai/models/gemini.py +12 -13
- pydantic_ai/models/google.py +18 -4
- pydantic_ai/models/groq.py +126 -38
- pydantic_ai/models/huggingface.py +4 -4
- pydantic_ai/models/instrumented.py +35 -16
- pydantic_ai/models/mcp_sampling.py +3 -1
- pydantic_ai/models/mistral.py +6 -6
- pydantic_ai/models/openai.py +35 -40
- pydantic_ai/models/test.py +24 -4
- pydantic_ai/output.py +27 -32
- pydantic_ai/profiles/__init__.py +3 -3
- pydantic_ai/profiles/groq.py +1 -1
- pydantic_ai/profiles/openai.py +25 -4
- pydantic_ai/providers/__init__.py +4 -0
- pydantic_ai/providers/anthropic.py +2 -3
- pydantic_ai/providers/bedrock.py +3 -2
- pydantic_ai/providers/google_vertex.py +2 -1
- pydantic_ai/providers/groq.py +21 -2
- pydantic_ai/providers/litellm.py +134 -0
- pydantic_ai/result.py +144 -41
- pydantic_ai/retries.py +52 -31
- pydantic_ai/run.py +12 -5
- pydantic_ai/tools.py +127 -23
- pydantic_ai/toolsets/__init__.py +4 -1
- pydantic_ai/toolsets/_dynamic.py +4 -4
- pydantic_ai/toolsets/abstract.py +18 -2
- pydantic_ai/toolsets/approval_required.py +32 -0
- pydantic_ai/toolsets/combined.py +7 -12
- pydantic_ai/toolsets/{deferred.py → external.py} +11 -5
- pydantic_ai/toolsets/filtered.py +1 -1
- pydantic_ai/toolsets/function.py +58 -21
- pydantic_ai/toolsets/wrapper.py +2 -1
- pydantic_ai/usage.py +44 -8
- {pydantic_ai_slim-0.8.1.dist-info → pydantic_ai_slim-1.0.0.dist-info}/METADATA +8 -9
- pydantic_ai_slim-1.0.0.dist-info/RECORD +121 -0
- pydantic_ai_slim-0.8.1.dist-info/RECORD +0 -119
- {pydantic_ai_slim-0.8.1.dist-info → pydantic_ai_slim-1.0.0.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-0.8.1.dist-info → pydantic_ai_slim-1.0.0.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-0.8.1.dist-info → pydantic_ai_slim-1.0.0.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/messages.py
CHANGED
|
@@ -1,24 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
3
|
import base64
|
|
4
|
+
import hashlib
|
|
4
5
|
from abc import ABC, abstractmethod
|
|
5
6
|
from collections.abc import Sequence
|
|
6
|
-
from dataclasses import dataclass, field, replace
|
|
7
|
+
from dataclasses import KW_ONLY, dataclass, field, replace
|
|
7
8
|
from datetime import datetime
|
|
8
9
|
from mimetypes import guess_type
|
|
9
|
-
from typing import TYPE_CHECKING, Annotated, Any, Literal,
|
|
10
|
+
from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeAlias, cast, overload
|
|
10
11
|
|
|
11
12
|
import pydantic
|
|
12
13
|
import pydantic_core
|
|
13
14
|
from genai_prices import calc_price, types as genai_types
|
|
14
15
|
from opentelemetry._events import Event # pyright: ignore[reportPrivateImportUsage]
|
|
15
|
-
from typing_extensions import
|
|
16
|
+
from typing_extensions import deprecated
|
|
16
17
|
|
|
17
18
|
from . import _otel_messages, _utils
|
|
18
|
-
from ._utils import
|
|
19
|
-
generate_tool_call_id as _generate_tool_call_id,
|
|
20
|
-
now_utc as _now_utc,
|
|
21
|
-
)
|
|
19
|
+
from ._utils import generate_tool_call_id as _generate_tool_call_id, now_utc as _now_utc
|
|
22
20
|
from .exceptions import UnexpectedModelBehavior
|
|
23
21
|
from .usage import RequestUsage
|
|
24
22
|
|
|
@@ -65,6 +63,8 @@ class SystemPromptPart:
|
|
|
65
63
|
content: str
|
|
66
64
|
"""The content of the prompt."""
|
|
67
65
|
|
|
66
|
+
_: KW_ONLY
|
|
67
|
+
|
|
68
68
|
timestamp: datetime = field(default_factory=_now_utc)
|
|
69
69
|
"""The timestamp of the prompt."""
|
|
70
70
|
|
|
@@ -89,6 +89,13 @@ class SystemPromptPart:
|
|
|
89
89
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
90
90
|
|
|
91
91
|
|
|
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
|
+
|
|
92
99
|
@dataclass(init=False, repr=False)
|
|
93
100
|
class FileUrl(ABC):
|
|
94
101
|
"""Abstract base class for any URL-based file."""
|
|
@@ -96,6 +103,8 @@ class FileUrl(ABC):
|
|
|
96
103
|
url: str
|
|
97
104
|
"""The URL of the file."""
|
|
98
105
|
|
|
106
|
+
_: KW_ONLY
|
|
107
|
+
|
|
99
108
|
force_download: bool = False
|
|
100
109
|
"""If the model supports it:
|
|
101
110
|
|
|
@@ -114,17 +123,31 @@ class FileUrl(ABC):
|
|
|
114
123
|
compare=False, default=None
|
|
115
124
|
)
|
|
116
125
|
|
|
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
|
+
|
|
117
137
|
def __init__(
|
|
118
138
|
self,
|
|
119
139
|
url: str,
|
|
140
|
+
*,
|
|
120
141
|
force_download: bool = False,
|
|
121
142
|
vendor_metadata: dict[str, Any] | None = None,
|
|
122
143
|
media_type: str | None = None,
|
|
144
|
+
identifier: str | None = None,
|
|
123
145
|
) -> None:
|
|
124
146
|
self.url = url
|
|
125
|
-
self.vendor_metadata = vendor_metadata
|
|
126
147
|
self.force_download = force_download
|
|
148
|
+
self.vendor_metadata = vendor_metadata
|
|
127
149
|
self._media_type = media_type
|
|
150
|
+
self.identifier = identifier or _multi_modal_content_identifier(url)
|
|
128
151
|
|
|
129
152
|
@pydantic.computed_field
|
|
130
153
|
@property
|
|
@@ -153,17 +176,20 @@ class VideoUrl(FileUrl):
|
|
|
153
176
|
url: str
|
|
154
177
|
"""The URL of the video."""
|
|
155
178
|
|
|
179
|
+
_: KW_ONLY
|
|
180
|
+
|
|
156
181
|
kind: Literal['video-url'] = 'video-url'
|
|
157
182
|
"""Type identifier, this is available on all parts as a discriminator."""
|
|
158
183
|
|
|
159
184
|
def __init__(
|
|
160
185
|
self,
|
|
161
186
|
url: str,
|
|
187
|
+
*,
|
|
162
188
|
force_download: bool = False,
|
|
163
189
|
vendor_metadata: dict[str, Any] | None = None,
|
|
164
190
|
media_type: str | None = None,
|
|
165
191
|
kind: Literal['video-url'] = 'video-url',
|
|
166
|
-
|
|
192
|
+
identifier: str | None = None,
|
|
167
193
|
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
168
194
|
_media_type: str | None = None,
|
|
169
195
|
) -> None:
|
|
@@ -172,6 +198,7 @@ class VideoUrl(FileUrl):
|
|
|
172
198
|
force_download=force_download,
|
|
173
199
|
vendor_metadata=vendor_metadata,
|
|
174
200
|
media_type=media_type or _media_type,
|
|
201
|
+
identifier=identifier,
|
|
175
202
|
)
|
|
176
203
|
self.kind = kind
|
|
177
204
|
|
|
@@ -224,17 +251,20 @@ class AudioUrl(FileUrl):
|
|
|
224
251
|
url: str
|
|
225
252
|
"""The URL of the audio file."""
|
|
226
253
|
|
|
254
|
+
_: KW_ONLY
|
|
255
|
+
|
|
227
256
|
kind: Literal['audio-url'] = 'audio-url'
|
|
228
257
|
"""Type identifier, this is available on all parts as a discriminator."""
|
|
229
258
|
|
|
230
259
|
def __init__(
|
|
231
260
|
self,
|
|
232
261
|
url: str,
|
|
262
|
+
*,
|
|
233
263
|
force_download: bool = False,
|
|
234
264
|
vendor_metadata: dict[str, Any] | None = None,
|
|
235
265
|
media_type: str | None = None,
|
|
236
266
|
kind: Literal['audio-url'] = 'audio-url',
|
|
237
|
-
|
|
267
|
+
identifier: str | None = None,
|
|
238
268
|
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
239
269
|
_media_type: str | None = None,
|
|
240
270
|
) -> None:
|
|
@@ -243,6 +273,7 @@ class AudioUrl(FileUrl):
|
|
|
243
273
|
force_download=force_download,
|
|
244
274
|
vendor_metadata=vendor_metadata,
|
|
245
275
|
media_type=media_type or _media_type,
|
|
276
|
+
identifier=identifier,
|
|
246
277
|
)
|
|
247
278
|
self.kind = kind
|
|
248
279
|
|
|
@@ -282,17 +313,20 @@ class ImageUrl(FileUrl):
|
|
|
282
313
|
url: str
|
|
283
314
|
"""The URL of the image."""
|
|
284
315
|
|
|
316
|
+
_: KW_ONLY
|
|
317
|
+
|
|
285
318
|
kind: Literal['image-url'] = 'image-url'
|
|
286
319
|
"""Type identifier, this is available on all parts as a discriminator."""
|
|
287
320
|
|
|
288
321
|
def __init__(
|
|
289
322
|
self,
|
|
290
323
|
url: str,
|
|
324
|
+
*,
|
|
291
325
|
force_download: bool = False,
|
|
292
326
|
vendor_metadata: dict[str, Any] | None = None,
|
|
293
327
|
media_type: str | None = None,
|
|
294
328
|
kind: Literal['image-url'] = 'image-url',
|
|
295
|
-
|
|
329
|
+
identifier: str | None = None,
|
|
296
330
|
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
297
331
|
_media_type: str | None = None,
|
|
298
332
|
) -> None:
|
|
@@ -301,6 +335,7 @@ class ImageUrl(FileUrl):
|
|
|
301
335
|
force_download=force_download,
|
|
302
336
|
vendor_metadata=vendor_metadata,
|
|
303
337
|
media_type=media_type or _media_type,
|
|
338
|
+
identifier=identifier,
|
|
304
339
|
)
|
|
305
340
|
self.kind = kind
|
|
306
341
|
|
|
@@ -335,17 +370,20 @@ class DocumentUrl(FileUrl):
|
|
|
335
370
|
url: str
|
|
336
371
|
"""The URL of the document."""
|
|
337
372
|
|
|
373
|
+
_: KW_ONLY
|
|
374
|
+
|
|
338
375
|
kind: Literal['document-url'] = 'document-url'
|
|
339
376
|
"""Type identifier, this is available on all parts as a discriminator."""
|
|
340
377
|
|
|
341
378
|
def __init__(
|
|
342
379
|
self,
|
|
343
380
|
url: str,
|
|
381
|
+
*,
|
|
344
382
|
force_download: bool = False,
|
|
345
383
|
vendor_metadata: dict[str, Any] | None = None,
|
|
346
384
|
media_type: str | None = None,
|
|
347
385
|
kind: Literal['document-url'] = 'document-url',
|
|
348
|
-
|
|
386
|
+
identifier: str | None = None,
|
|
349
387
|
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
350
388
|
_media_type: str | None = None,
|
|
351
389
|
) -> None:
|
|
@@ -354,6 +392,7 @@ class DocumentUrl(FileUrl):
|
|
|
354
392
|
force_download=force_download,
|
|
355
393
|
vendor_metadata=vendor_metadata,
|
|
356
394
|
media_type=media_type or _media_type,
|
|
395
|
+
identifier=identifier,
|
|
357
396
|
)
|
|
358
397
|
self.kind = kind
|
|
359
398
|
|
|
@@ -396,22 +435,26 @@ class DocumentUrl(FileUrl):
|
|
|
396
435
|
raise ValueError(f'Unknown document media type: {media_type}') from e
|
|
397
436
|
|
|
398
437
|
|
|
399
|
-
@dataclass(repr=False)
|
|
438
|
+
@dataclass(init=False, repr=False)
|
|
400
439
|
class BinaryContent:
|
|
401
440
|
"""Binary content, e.g. an audio or image file."""
|
|
402
441
|
|
|
403
442
|
data: bytes
|
|
404
443
|
"""The binary data."""
|
|
405
444
|
|
|
445
|
+
_: KW_ONLY
|
|
446
|
+
|
|
406
447
|
media_type: AudioMediaType | ImageMediaType | DocumentMediaType | str
|
|
407
448
|
"""The media type of the binary data."""
|
|
408
449
|
|
|
409
|
-
identifier: str
|
|
410
|
-
"""Identifier for the binary content, such as a
|
|
411
|
-
|
|
412
|
-
|
|
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`.
|
|
413
454
|
|
|
414
|
-
This identifier is only automatically passed to the model when the `BinaryContent` is returned by a tool.
|
|
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`.
|
|
415
458
|
"""
|
|
416
459
|
|
|
417
460
|
vendor_metadata: dict[str, Any] | None = None
|
|
@@ -424,6 +467,21 @@ class BinaryContent:
|
|
|
424
467
|
kind: Literal['binary'] = 'binary'
|
|
425
468
|
"""Type identifier, this is available on all parts as a discriminator."""
|
|
426
469
|
|
|
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
|
+
|
|
427
485
|
@property
|
|
428
486
|
def is_audio(self) -> bool:
|
|
429
487
|
"""Return `True` if the media type is an audio type."""
|
|
@@ -462,7 +520,8 @@ class BinaryContent:
|
|
|
462
520
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
463
521
|
|
|
464
522
|
|
|
465
|
-
|
|
523
|
+
MultiModalContent = ImageUrl | AudioUrl | DocumentUrl | VideoUrl | BinaryContent
|
|
524
|
+
UserContent: TypeAlias = str | MultiModalContent
|
|
466
525
|
|
|
467
526
|
|
|
468
527
|
@dataclass(repr=False)
|
|
@@ -478,17 +537,19 @@ class ToolReturn:
|
|
|
478
537
|
return_value: Any
|
|
479
538
|
"""The return value to be used in the tool response."""
|
|
480
539
|
|
|
540
|
+
_: KW_ONLY
|
|
541
|
+
|
|
481
542
|
content: str | Sequence[UserContent] | None = None
|
|
482
543
|
"""The content to be sent to the model as a UserPromptPart."""
|
|
483
544
|
|
|
484
545
|
metadata: Any = None
|
|
485
546
|
"""Additional data that can be accessed programmatically by the application but is not sent to the LLM."""
|
|
486
547
|
|
|
548
|
+
kind: Literal['tool-return'] = 'tool-return'
|
|
549
|
+
|
|
487
550
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
488
551
|
|
|
489
552
|
|
|
490
|
-
# Ideally this would be a Union of types, but Python 3.9 requires it to be a string, and strings don't work with `isinstance``.
|
|
491
|
-
MultiModalContentTypes = (ImageUrl, AudioUrl, DocumentUrl, VideoUrl, BinaryContent)
|
|
492
553
|
_document_format_lookup: dict[str, DocumentFormat] = {
|
|
493
554
|
'application/pdf': 'pdf',
|
|
494
555
|
'text/plain': 'txt',
|
|
@@ -536,6 +597,8 @@ class UserPromptPart:
|
|
|
536
597
|
content: str | Sequence[UserContent]
|
|
537
598
|
"""The content of the prompt."""
|
|
538
599
|
|
|
600
|
+
_: KW_ONLY
|
|
601
|
+
|
|
539
602
|
timestamp: datetime = field(default_factory=_now_utc)
|
|
540
603
|
"""The timestamp of the prompt."""
|
|
541
604
|
|
|
@@ -562,7 +625,7 @@ class UserPromptPart:
|
|
|
562
625
|
parts.append(
|
|
563
626
|
_otel_messages.TextPart(type='text', **({'content': part} if settings.include_content else {}))
|
|
564
627
|
)
|
|
565
|
-
elif isinstance(part,
|
|
628
|
+
elif isinstance(part, ImageUrl | AudioUrl | DocumentUrl | VideoUrl):
|
|
566
629
|
parts.append(
|
|
567
630
|
_otel_messages.MediaUrlPart(
|
|
568
631
|
type=part.kind,
|
|
@@ -599,6 +662,8 @@ class BaseToolReturnPart:
|
|
|
599
662
|
tool_call_id: str
|
|
600
663
|
"""The tool call identifier, this is used by some models including OpenAI."""
|
|
601
664
|
|
|
665
|
+
_: KW_ONLY
|
|
666
|
+
|
|
602
667
|
metadata: Any = None
|
|
603
668
|
"""Additional data that can be accessed programmatically by the application but is not sent to the LLM."""
|
|
604
669
|
|
|
@@ -654,6 +719,8 @@ class BaseToolReturnPart:
|
|
|
654
719
|
class ToolReturnPart(BaseToolReturnPart):
|
|
655
720
|
"""A tool return message, this encodes the result of running a tool."""
|
|
656
721
|
|
|
722
|
+
_: KW_ONLY
|
|
723
|
+
|
|
657
724
|
part_kind: Literal['tool-return'] = 'tool-return'
|
|
658
725
|
"""Part type identifier, this is available on all parts as a discriminator."""
|
|
659
726
|
|
|
@@ -662,6 +729,8 @@ class ToolReturnPart(BaseToolReturnPart):
|
|
|
662
729
|
class BuiltinToolReturnPart(BaseToolReturnPart):
|
|
663
730
|
"""A tool return message from a built-in tool."""
|
|
664
731
|
|
|
732
|
+
_: KW_ONLY
|
|
733
|
+
|
|
665
734
|
provider_name: str | None = None
|
|
666
735
|
"""The name of the provider that generated the response."""
|
|
667
736
|
|
|
@@ -695,6 +764,8 @@ class RetryPromptPart:
|
|
|
695
764
|
error details.
|
|
696
765
|
"""
|
|
697
766
|
|
|
767
|
+
_: KW_ONLY
|
|
768
|
+
|
|
698
769
|
tool_name: str | None = None
|
|
699
770
|
"""The name of the tool that was called, if any."""
|
|
700
771
|
|
|
@@ -753,7 +824,7 @@ class RetryPromptPart:
|
|
|
753
824
|
|
|
754
825
|
|
|
755
826
|
ModelRequestPart = Annotated[
|
|
756
|
-
|
|
827
|
+
SystemPromptPart | UserPromptPart | ToolReturnPart | RetryPromptPart, pydantic.Discriminator('part_kind')
|
|
757
828
|
]
|
|
758
829
|
"""A message part sent by Pydantic AI to a model."""
|
|
759
830
|
|
|
@@ -762,9 +833,11 @@ ModelRequestPart = Annotated[
|
|
|
762
833
|
class ModelRequest:
|
|
763
834
|
"""A request generated by Pydantic AI and sent to a model, e.g. a message from the Pydantic AI app to the model."""
|
|
764
835
|
|
|
765
|
-
parts:
|
|
836
|
+
parts: Sequence[ModelRequestPart]
|
|
766
837
|
"""The parts of the user message."""
|
|
767
838
|
|
|
839
|
+
_: KW_ONLY
|
|
840
|
+
|
|
768
841
|
instructions: str | None = None
|
|
769
842
|
"""The instructions for the model."""
|
|
770
843
|
|
|
@@ -786,6 +859,8 @@ class TextPart:
|
|
|
786
859
|
content: str
|
|
787
860
|
"""The text content of the response."""
|
|
788
861
|
|
|
862
|
+
_: KW_ONLY
|
|
863
|
+
|
|
789
864
|
part_kind: Literal['text'] = 'text'
|
|
790
865
|
"""Part type identifier, this is available on all parts as a discriminator."""
|
|
791
866
|
|
|
@@ -803,6 +878,8 @@ class ThinkingPart:
|
|
|
803
878
|
content: str
|
|
804
879
|
"""The thinking content of the response."""
|
|
805
880
|
|
|
881
|
+
_: KW_ONLY
|
|
882
|
+
|
|
806
883
|
id: str | None = None
|
|
807
884
|
"""The identifier of the thinking part."""
|
|
808
885
|
|
|
@@ -881,6 +958,8 @@ class BaseToolCallPart:
|
|
|
881
958
|
class ToolCallPart(BaseToolCallPart):
|
|
882
959
|
"""A tool call from a model."""
|
|
883
960
|
|
|
961
|
+
_: KW_ONLY
|
|
962
|
+
|
|
884
963
|
part_kind: Literal['tool-call'] = 'tool-call'
|
|
885
964
|
"""Part type identifier, this is available on all parts as a discriminator."""
|
|
886
965
|
|
|
@@ -889,6 +968,8 @@ class ToolCallPart(BaseToolCallPart):
|
|
|
889
968
|
class BuiltinToolCallPart(BaseToolCallPart):
|
|
890
969
|
"""A tool call to a built-in tool."""
|
|
891
970
|
|
|
971
|
+
_: KW_ONLY
|
|
972
|
+
|
|
892
973
|
provider_name: str | None = None
|
|
893
974
|
"""The name of the provider that generated the response."""
|
|
894
975
|
|
|
@@ -897,7 +978,7 @@ class BuiltinToolCallPart(BaseToolCallPart):
|
|
|
897
978
|
|
|
898
979
|
|
|
899
980
|
ModelResponsePart = Annotated[
|
|
900
|
-
|
|
981
|
+
TextPart | ToolCallPart | BuiltinToolCallPart | BuiltinToolReturnPart | ThinkingPart,
|
|
901
982
|
pydantic.Discriminator('part_kind'),
|
|
902
983
|
]
|
|
903
984
|
"""A message part returned by a model."""
|
|
@@ -907,9 +988,11 @@ ModelResponsePart = Annotated[
|
|
|
907
988
|
class ModelResponse:
|
|
908
989
|
"""A response from a model, e.g. a message from the model to the Pydantic AI app."""
|
|
909
990
|
|
|
910
|
-
parts:
|
|
991
|
+
parts: Sequence[ModelResponsePart]
|
|
911
992
|
"""The parts of the model message."""
|
|
912
993
|
|
|
994
|
+
_: KW_ONLY
|
|
995
|
+
|
|
913
996
|
usage: RequestUsage = field(default_factory=RequestUsage)
|
|
914
997
|
"""Usage information for the request.
|
|
915
998
|
|
|
@@ -931,18 +1014,30 @@ class ModelResponse:
|
|
|
931
1014
|
provider_name: str | None = None
|
|
932
1015
|
"""The name of the LLM provider that generated the response."""
|
|
933
1016
|
|
|
934
|
-
provider_details:
|
|
1017
|
+
provider_details: Annotated[
|
|
1018
|
+
dict[str, Any] | None,
|
|
1019
|
+
# `vendor_details` is deprecated, but we still want to support deserializing model responses stored in a DB before the name was changed
|
|
1020
|
+
pydantic.Field(validation_alias=pydantic.AliasChoices('provider_details', 'vendor_details')),
|
|
1021
|
+
] = None
|
|
935
1022
|
"""Additional provider-specific details in a serializable format.
|
|
936
1023
|
|
|
937
1024
|
This allows storing selected vendor-specific data that isn't mapped to standard ModelResponse fields.
|
|
938
1025
|
For OpenAI models, this may include 'logprobs', 'finish_reason', etc.
|
|
939
1026
|
"""
|
|
940
1027
|
|
|
941
|
-
provider_response_id:
|
|
1028
|
+
provider_response_id: Annotated[
|
|
1029
|
+
str | None,
|
|
1030
|
+
# `vendor_id` is deprecated, but we still want to support deserializing model responses stored in a DB before the name was changed
|
|
1031
|
+
pydantic.Field(validation_alias=pydantic.AliasChoices('provider_response_id', 'vendor_id')),
|
|
1032
|
+
] = None
|
|
942
1033
|
"""request ID as specified by the model provider. This can be used to track the specific request to the model."""
|
|
943
1034
|
|
|
944
|
-
|
|
945
|
-
|
|
1035
|
+
@deprecated('`price` is deprecated, use `cost` instead')
|
|
1036
|
+
def price(self) -> genai_types.PriceCalculation: # pragma: no cover
|
|
1037
|
+
return self.cost()
|
|
1038
|
+
|
|
1039
|
+
def cost(self) -> genai_types.PriceCalculation:
|
|
1040
|
+
"""Calculate the cost of the usage.
|
|
946
1041
|
|
|
947
1042
|
Uses [`genai-prices`](https://github.com/pydantic/genai-prices).
|
|
948
1043
|
"""
|
|
@@ -970,14 +1065,14 @@ class ModelResponse:
|
|
|
970
1065
|
body.setdefault('tool_calls', []).append(
|
|
971
1066
|
{
|
|
972
1067
|
'id': part.tool_call_id,
|
|
973
|
-
'type': 'function',
|
|
1068
|
+
'type': 'function',
|
|
974
1069
|
'function': {
|
|
975
1070
|
'name': part.tool_name,
|
|
976
1071
|
**({'arguments': part.args} if settings.include_content else {}),
|
|
977
1072
|
},
|
|
978
1073
|
}
|
|
979
1074
|
)
|
|
980
|
-
elif isinstance(part,
|
|
1075
|
+
elif isinstance(part, TextPart | ThinkingPart):
|
|
981
1076
|
kind = part.part_kind
|
|
982
1077
|
body.setdefault('content', []).append(
|
|
983
1078
|
{'kind': kind, **({'text': part.content} if settings.include_content else {})}
|
|
@@ -1038,7 +1133,7 @@ class ModelResponse:
|
|
|
1038
1133
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1039
1134
|
|
|
1040
1135
|
|
|
1041
|
-
ModelMessage = Annotated[
|
|
1136
|
+
ModelMessage = Annotated[ModelRequest | ModelResponse, pydantic.Discriminator('kind')]
|
|
1042
1137
|
"""Any message sent to or returned by a model."""
|
|
1043
1138
|
|
|
1044
1139
|
ModelMessagesTypeAdapter = pydantic.TypeAdapter(
|
|
@@ -1054,6 +1149,8 @@ class TextPartDelta:
|
|
|
1054
1149
|
content_delta: str
|
|
1055
1150
|
"""The incremental text content to add to the existing `TextPart` content."""
|
|
1056
1151
|
|
|
1152
|
+
_: KW_ONLY
|
|
1153
|
+
|
|
1057
1154
|
part_delta_kind: Literal['text'] = 'text'
|
|
1058
1155
|
"""Part delta type identifier, used as a discriminator."""
|
|
1059
1156
|
|
|
@@ -1076,7 +1173,7 @@ class TextPartDelta:
|
|
|
1076
1173
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1077
1174
|
|
|
1078
1175
|
|
|
1079
|
-
@dataclass(repr=False)
|
|
1176
|
+
@dataclass(repr=False, kw_only=True)
|
|
1080
1177
|
class ThinkingPartDelta:
|
|
1081
1178
|
"""A partial update (delta) for a `ThinkingPart` to append new thinking content."""
|
|
1082
1179
|
|
|
@@ -1128,7 +1225,7 @@ class ThinkingPartDelta:
|
|
|
1128
1225
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1129
1226
|
|
|
1130
1227
|
|
|
1131
|
-
@dataclass(repr=False)
|
|
1228
|
+
@dataclass(repr=False, kw_only=True)
|
|
1132
1229
|
class ToolCallPartDelta:
|
|
1133
1230
|
"""A partial update (delta) for a `ToolCallPart` to modify tool name, arguments, or tool call ID."""
|
|
1134
1231
|
|
|
@@ -1248,12 +1345,12 @@ class ToolCallPartDelta:
|
|
|
1248
1345
|
|
|
1249
1346
|
|
|
1250
1347
|
ModelResponsePartDelta = Annotated[
|
|
1251
|
-
|
|
1348
|
+
TextPartDelta | ThinkingPartDelta | ToolCallPartDelta, pydantic.Discriminator('part_delta_kind')
|
|
1252
1349
|
]
|
|
1253
1350
|
"""A partial update (delta) for any model response part."""
|
|
1254
1351
|
|
|
1255
1352
|
|
|
1256
|
-
@dataclass(repr=False)
|
|
1353
|
+
@dataclass(repr=False, kw_only=True)
|
|
1257
1354
|
class PartStartEvent:
|
|
1258
1355
|
"""An event indicating that a new part has started.
|
|
1259
1356
|
|
|
@@ -1273,7 +1370,7 @@ class PartStartEvent:
|
|
|
1273
1370
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1274
1371
|
|
|
1275
1372
|
|
|
1276
|
-
@dataclass(repr=False)
|
|
1373
|
+
@dataclass(repr=False, kw_only=True)
|
|
1277
1374
|
class PartDeltaEvent:
|
|
1278
1375
|
"""An event indicating a delta update for an existing part."""
|
|
1279
1376
|
|
|
@@ -1289,7 +1386,7 @@ class PartDeltaEvent:
|
|
|
1289
1386
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1290
1387
|
|
|
1291
1388
|
|
|
1292
|
-
@dataclass(repr=False)
|
|
1389
|
+
@dataclass(repr=False, kw_only=True)
|
|
1293
1390
|
class FinalResultEvent:
|
|
1294
1391
|
"""An event indicating the response to the current model request matches the output schema and will produce a result."""
|
|
1295
1392
|
|
|
@@ -1304,7 +1401,7 @@ class FinalResultEvent:
|
|
|
1304
1401
|
|
|
1305
1402
|
|
|
1306
1403
|
ModelResponseStreamEvent = Annotated[
|
|
1307
|
-
|
|
1404
|
+
PartStartEvent | PartDeltaEvent | FinalResultEvent, pydantic.Discriminator('event_kind')
|
|
1308
1405
|
]
|
|
1309
1406
|
"""An event in the model response stream, starting a new part, applying a delta to an existing one, or indicating the final result."""
|
|
1310
1407
|
|
|
@@ -1315,6 +1412,9 @@ class FunctionToolCallEvent:
|
|
|
1315
1412
|
|
|
1316
1413
|
part: ToolCallPart
|
|
1317
1414
|
"""The (function) tool call to make."""
|
|
1415
|
+
|
|
1416
|
+
_: KW_ONLY
|
|
1417
|
+
|
|
1318
1418
|
event_kind: Literal['function_tool_call'] = 'function_tool_call'
|
|
1319
1419
|
"""Event type identifier, used as a discriminator."""
|
|
1320
1420
|
|
|
@@ -1338,6 +1438,9 @@ class FunctionToolResultEvent:
|
|
|
1338
1438
|
|
|
1339
1439
|
result: ToolReturnPart | RetryPromptPart
|
|
1340
1440
|
"""The result of the call to the function tool."""
|
|
1441
|
+
|
|
1442
|
+
_: KW_ONLY
|
|
1443
|
+
|
|
1341
1444
|
event_kind: Literal['function_tool_result'] = 'function_tool_result'
|
|
1342
1445
|
"""Event type identifier, used as a discriminator."""
|
|
1343
1446
|
|
|
@@ -1356,6 +1459,8 @@ class BuiltinToolCallEvent:
|
|
|
1356
1459
|
part: BuiltinToolCallPart
|
|
1357
1460
|
"""The built-in tool call to make."""
|
|
1358
1461
|
|
|
1462
|
+
_: KW_ONLY
|
|
1463
|
+
|
|
1359
1464
|
event_kind: Literal['builtin_tool_call'] = 'builtin_tool_call'
|
|
1360
1465
|
"""Event type identifier, used as a discriminator."""
|
|
1361
1466
|
|
|
@@ -1367,15 +1472,17 @@ class BuiltinToolResultEvent:
|
|
|
1367
1472
|
result: BuiltinToolReturnPart
|
|
1368
1473
|
"""The result of the call to the built-in tool."""
|
|
1369
1474
|
|
|
1475
|
+
_: KW_ONLY
|
|
1476
|
+
|
|
1370
1477
|
event_kind: Literal['builtin_tool_result'] = 'builtin_tool_result'
|
|
1371
1478
|
"""Event type identifier, used as a discriminator."""
|
|
1372
1479
|
|
|
1373
1480
|
|
|
1374
1481
|
HandleResponseEvent = Annotated[
|
|
1375
|
-
|
|
1482
|
+
FunctionToolCallEvent | FunctionToolResultEvent | BuiltinToolCallEvent | BuiltinToolResultEvent,
|
|
1376
1483
|
pydantic.Discriminator('event_kind'),
|
|
1377
1484
|
]
|
|
1378
1485
|
"""An event yielded when handling a model response, indicating tool calls and results."""
|
|
1379
1486
|
|
|
1380
|
-
AgentStreamEvent = Annotated[
|
|
1487
|
+
AgentStreamEvent = Annotated[ModelResponseStreamEvent | HandleResponseEvent, pydantic.Discriminator('event_kind')]
|
|
1381
1488
|
"""An event in the agent stream: model response stream events and response-handling events."""
|
pydantic_ai/models/__init__.py
CHANGED
|
@@ -14,10 +14,10 @@ from contextlib import asynccontextmanager, contextmanager
|
|
|
14
14
|
from dataclasses import dataclass, field, replace
|
|
15
15
|
from datetime import datetime
|
|
16
16
|
from functools import cache, cached_property
|
|
17
|
-
from typing import Any, Generic, TypeVar, overload
|
|
17
|
+
from typing import Any, Generic, Literal, TypeVar, overload
|
|
18
18
|
|
|
19
19
|
import httpx
|
|
20
|
-
from typing_extensions import
|
|
20
|
+
from typing_extensions import TypeAliasType, TypedDict
|
|
21
21
|
|
|
22
22
|
from .. import _utils
|
|
23
23
|
from .._output import OutputObjectDefinition
|
|
@@ -367,7 +367,7 @@ KnownModelName = TypeAliasType(
|
|
|
367
367
|
"""
|
|
368
368
|
|
|
369
369
|
|
|
370
|
-
@dataclass(repr=False)
|
|
370
|
+
@dataclass(repr=False, kw_only=True)
|
|
371
371
|
class ModelRequestParameters:
|
|
372
372
|
"""Configuration for an agent's request to a model, specifically related to tools and output handling."""
|
|
373
373
|
|
|
@@ -552,6 +552,7 @@ class StreamedResponse(ABC):
|
|
|
552
552
|
"""Streamed response from an LLM when calling a tool."""
|
|
553
553
|
|
|
554
554
|
model_request_parameters: ModelRequestParameters
|
|
555
|
+
|
|
555
556
|
final_result_event: FinalResultEvent | None = field(default=None, init=False)
|
|
556
557
|
|
|
557
558
|
_parts_manager: ModelResponsePartsManager = field(default_factory=ModelResponsePartsManager, init=False)
|
|
@@ -727,6 +728,7 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
|
|
|
727
728
|
'openrouter',
|
|
728
729
|
'together',
|
|
729
730
|
'vercel',
|
|
731
|
+
'litellm',
|
|
730
732
|
):
|
|
731
733
|
from .openai import OpenAIChatModel
|
|
732
734
|
|
|
@@ -920,5 +922,5 @@ def _get_final_result_event(e: ModelResponseStreamEvent, params: ModelRequestPar
|
|
|
920
922
|
elif isinstance(new_part, ToolCallPart) and (tool_def := params.tool_defs.get(new_part.tool_name)):
|
|
921
923
|
if tool_def.kind == 'output':
|
|
922
924
|
return FinalResultEvent(tool_name=new_part.tool_name, tool_call_id=new_part.tool_call_id)
|
|
923
|
-
elif tool_def.
|
|
925
|
+
elif tool_def.defer:
|
|
924
926
|
return FinalResultEvent(tool_name=None, tool_call_id=None)
|