pydantic-ai-slim 0.8.0__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.

Files changed (75) hide show
  1. pydantic_ai/__init__.py +28 -2
  2. pydantic_ai/_a2a.py +1 -1
  3. pydantic_ai/_agent_graph.py +323 -156
  4. pydantic_ai/_function_schema.py +5 -5
  5. pydantic_ai/_griffe.py +2 -1
  6. pydantic_ai/_otel_messages.py +2 -2
  7. pydantic_ai/_output.py +31 -35
  8. pydantic_ai/_parts_manager.py +7 -5
  9. pydantic_ai/_run_context.py +3 -1
  10. pydantic_ai/_system_prompt.py +2 -2
  11. pydantic_ai/_tool_manager.py +32 -28
  12. pydantic_ai/_utils.py +14 -26
  13. pydantic_ai/ag_ui.py +82 -51
  14. pydantic_ai/agent/__init__.py +84 -17
  15. pydantic_ai/agent/abstract.py +35 -4
  16. pydantic_ai/agent/wrapper.py +6 -0
  17. pydantic_ai/builtin_tools.py +2 -2
  18. pydantic_ai/common_tools/duckduckgo.py +4 -2
  19. pydantic_ai/durable_exec/temporal/__init__.py +70 -17
  20. pydantic_ai/durable_exec/temporal/_agent.py +93 -11
  21. pydantic_ai/durable_exec/temporal/_function_toolset.py +53 -6
  22. pydantic_ai/durable_exec/temporal/_logfire.py +6 -3
  23. pydantic_ai/durable_exec/temporal/_mcp_server.py +2 -1
  24. pydantic_ai/durable_exec/temporal/_model.py +2 -2
  25. pydantic_ai/durable_exec/temporal/_run_context.py +2 -1
  26. pydantic_ai/durable_exec/temporal/_toolset.py +2 -1
  27. pydantic_ai/exceptions.py +45 -2
  28. pydantic_ai/format_prompt.py +2 -2
  29. pydantic_ai/mcp.py +15 -27
  30. pydantic_ai/messages.py +156 -44
  31. pydantic_ai/models/__init__.py +20 -7
  32. pydantic_ai/models/anthropic.py +10 -17
  33. pydantic_ai/models/bedrock.py +55 -57
  34. pydantic_ai/models/cohere.py +3 -3
  35. pydantic_ai/models/fallback.py +2 -2
  36. pydantic_ai/models/function.py +25 -23
  37. pydantic_ai/models/gemini.py +13 -14
  38. pydantic_ai/models/google.py +19 -5
  39. pydantic_ai/models/groq.py +127 -39
  40. pydantic_ai/models/huggingface.py +5 -5
  41. pydantic_ai/models/instrumented.py +49 -21
  42. pydantic_ai/models/mcp_sampling.py +3 -1
  43. pydantic_ai/models/mistral.py +8 -8
  44. pydantic_ai/models/openai.py +37 -42
  45. pydantic_ai/models/test.py +24 -4
  46. pydantic_ai/output.py +27 -32
  47. pydantic_ai/profiles/__init__.py +3 -3
  48. pydantic_ai/profiles/groq.py +1 -1
  49. pydantic_ai/profiles/openai.py +25 -4
  50. pydantic_ai/providers/__init__.py +4 -0
  51. pydantic_ai/providers/anthropic.py +2 -3
  52. pydantic_ai/providers/bedrock.py +3 -2
  53. pydantic_ai/providers/google_vertex.py +2 -1
  54. pydantic_ai/providers/groq.py +21 -2
  55. pydantic_ai/providers/litellm.py +134 -0
  56. pydantic_ai/result.py +173 -52
  57. pydantic_ai/retries.py +52 -31
  58. pydantic_ai/run.py +12 -5
  59. pydantic_ai/tools.py +127 -23
  60. pydantic_ai/toolsets/__init__.py +4 -1
  61. pydantic_ai/toolsets/_dynamic.py +4 -4
  62. pydantic_ai/toolsets/abstract.py +18 -2
  63. pydantic_ai/toolsets/approval_required.py +32 -0
  64. pydantic_ai/toolsets/combined.py +7 -12
  65. pydantic_ai/toolsets/{deferred.py → external.py} +11 -5
  66. pydantic_ai/toolsets/filtered.py +1 -1
  67. pydantic_ai/toolsets/function.py +58 -21
  68. pydantic_ai/toolsets/wrapper.py +2 -1
  69. pydantic_ai/usage.py +44 -8
  70. {pydantic_ai_slim-0.8.0.dist-info → pydantic_ai_slim-1.0.0.dist-info}/METADATA +8 -9
  71. pydantic_ai_slim-1.0.0.dist-info/RECORD +121 -0
  72. pydantic_ai_slim-0.8.0.dist-info/RECORD +0 -119
  73. {pydantic_ai_slim-0.8.0.dist-info → pydantic_ai_slim-1.0.0.dist-info}/WHEEL +0 -0
  74. {pydantic_ai_slim-0.8.0.dist-info → pydantic_ai_slim-1.0.0.dist-info}/entry_points.txt +0 -0
  75. {pydantic_ai_slim-0.8.0.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, Union, cast, overload
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 TypeAlias, deprecated
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 | None = None
410
- """Identifier for the binary content, such as a URL or unique ID.
411
-
412
- 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`.
413
454
 
414
- 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`.
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
- UserContent: TypeAlias = 'str | ImageUrl | AudioUrl | DocumentUrl | VideoUrl | BinaryContent'
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, (ImageUrl, AudioUrl, DocumentUrl, VideoUrl)):
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
- Union[SystemPromptPart, UserPromptPart, ToolReturnPart, RetryPromptPart], pydantic.Discriminator('part_kind')
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: list[ModelRequestPart]
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
- Union[TextPart, ToolCallPart, BuiltinToolCallPart, BuiltinToolReturnPart, ThinkingPart],
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: list[ModelResponsePart]
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: dict[str, Any] | None = field(default=None)
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_request_id: str | None = None
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
- def price(self) -> genai_types.PriceCalculation:
945
- """Calculate the price of the usage.
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', # TODO https://github.com/pydantic/pydantic-ai/issues/888
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, (TextPart, ThinkingPart)):
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 {})}
@@ -1026,14 +1121,19 @@ class ModelResponse:
1026
1121
  return self.provider_details
1027
1122
 
1028
1123
  @property
1029
- @deprecated('`vendor_id` is deprecated, use `provider_request_id` instead')
1124
+ @deprecated('`vendor_id` is deprecated, use `provider_response_id` instead')
1030
1125
  def vendor_id(self) -> str | None:
1031
- return self.provider_request_id
1126
+ return self.provider_response_id
1127
+
1128
+ @property
1129
+ @deprecated('`provider_request_id` is deprecated, use `provider_response_id` instead')
1130
+ def provider_request_id(self) -> str | None:
1131
+ return self.provider_response_id
1032
1132
 
1033
1133
  __repr__ = _utils.dataclasses_no_defaults_repr
1034
1134
 
1035
1135
 
1036
- ModelMessage = Annotated[Union[ModelRequest, ModelResponse], pydantic.Discriminator('kind')]
1136
+ ModelMessage = Annotated[ModelRequest | ModelResponse, pydantic.Discriminator('kind')]
1037
1137
  """Any message sent to or returned by a model."""
1038
1138
 
1039
1139
  ModelMessagesTypeAdapter = pydantic.TypeAdapter(
@@ -1049,6 +1149,8 @@ class TextPartDelta:
1049
1149
  content_delta: str
1050
1150
  """The incremental text content to add to the existing `TextPart` content."""
1051
1151
 
1152
+ _: KW_ONLY
1153
+
1052
1154
  part_delta_kind: Literal['text'] = 'text'
1053
1155
  """Part delta type identifier, used as a discriminator."""
1054
1156
 
@@ -1071,7 +1173,7 @@ class TextPartDelta:
1071
1173
  __repr__ = _utils.dataclasses_no_defaults_repr
1072
1174
 
1073
1175
 
1074
- @dataclass(repr=False)
1176
+ @dataclass(repr=False, kw_only=True)
1075
1177
  class ThinkingPartDelta:
1076
1178
  """A partial update (delta) for a `ThinkingPart` to append new thinking content."""
1077
1179
 
@@ -1123,7 +1225,7 @@ class ThinkingPartDelta:
1123
1225
  __repr__ = _utils.dataclasses_no_defaults_repr
1124
1226
 
1125
1227
 
1126
- @dataclass(repr=False)
1228
+ @dataclass(repr=False, kw_only=True)
1127
1229
  class ToolCallPartDelta:
1128
1230
  """A partial update (delta) for a `ToolCallPart` to modify tool name, arguments, or tool call ID."""
1129
1231
 
@@ -1243,12 +1345,12 @@ class ToolCallPartDelta:
1243
1345
 
1244
1346
 
1245
1347
  ModelResponsePartDelta = Annotated[
1246
- Union[TextPartDelta, ThinkingPartDelta, ToolCallPartDelta], pydantic.Discriminator('part_delta_kind')
1348
+ TextPartDelta | ThinkingPartDelta | ToolCallPartDelta, pydantic.Discriminator('part_delta_kind')
1247
1349
  ]
1248
1350
  """A partial update (delta) for any model response part."""
1249
1351
 
1250
1352
 
1251
- @dataclass(repr=False)
1353
+ @dataclass(repr=False, kw_only=True)
1252
1354
  class PartStartEvent:
1253
1355
  """An event indicating that a new part has started.
1254
1356
 
@@ -1268,7 +1370,7 @@ class PartStartEvent:
1268
1370
  __repr__ = _utils.dataclasses_no_defaults_repr
1269
1371
 
1270
1372
 
1271
- @dataclass(repr=False)
1373
+ @dataclass(repr=False, kw_only=True)
1272
1374
  class PartDeltaEvent:
1273
1375
  """An event indicating a delta update for an existing part."""
1274
1376
 
@@ -1284,7 +1386,7 @@ class PartDeltaEvent:
1284
1386
  __repr__ = _utils.dataclasses_no_defaults_repr
1285
1387
 
1286
1388
 
1287
- @dataclass(repr=False)
1389
+ @dataclass(repr=False, kw_only=True)
1288
1390
  class FinalResultEvent:
1289
1391
  """An event indicating the response to the current model request matches the output schema and will produce a result."""
1290
1392
 
@@ -1299,7 +1401,7 @@ class FinalResultEvent:
1299
1401
 
1300
1402
 
1301
1403
  ModelResponseStreamEvent = Annotated[
1302
- Union[PartStartEvent, PartDeltaEvent, FinalResultEvent], pydantic.Discriminator('event_kind')
1404
+ PartStartEvent | PartDeltaEvent | FinalResultEvent, pydantic.Discriminator('event_kind')
1303
1405
  ]
1304
1406
  """An event in the model response stream, starting a new part, applying a delta to an existing one, or indicating the final result."""
1305
1407
 
@@ -1310,6 +1412,9 @@ class FunctionToolCallEvent:
1310
1412
 
1311
1413
  part: ToolCallPart
1312
1414
  """The (function) tool call to make."""
1415
+
1416
+ _: KW_ONLY
1417
+
1313
1418
  event_kind: Literal['function_tool_call'] = 'function_tool_call'
1314
1419
  """Event type identifier, used as a discriminator."""
1315
1420
 
@@ -1333,6 +1438,9 @@ class FunctionToolResultEvent:
1333
1438
 
1334
1439
  result: ToolReturnPart | RetryPromptPart
1335
1440
  """The result of the call to the function tool."""
1441
+
1442
+ _: KW_ONLY
1443
+
1336
1444
  event_kind: Literal['function_tool_result'] = 'function_tool_result'
1337
1445
  """Event type identifier, used as a discriminator."""
1338
1446
 
@@ -1351,6 +1459,8 @@ class BuiltinToolCallEvent:
1351
1459
  part: BuiltinToolCallPart
1352
1460
  """The built-in tool call to make."""
1353
1461
 
1462
+ _: KW_ONLY
1463
+
1354
1464
  event_kind: Literal['builtin_tool_call'] = 'builtin_tool_call'
1355
1465
  """Event type identifier, used as a discriminator."""
1356
1466
 
@@ -1362,15 +1472,17 @@ class BuiltinToolResultEvent:
1362
1472
  result: BuiltinToolReturnPart
1363
1473
  """The result of the call to the built-in tool."""
1364
1474
 
1475
+ _: KW_ONLY
1476
+
1365
1477
  event_kind: Literal['builtin_tool_result'] = 'builtin_tool_result'
1366
1478
  """Event type identifier, used as a discriminator."""
1367
1479
 
1368
1480
 
1369
1481
  HandleResponseEvent = Annotated[
1370
- Union[FunctionToolCallEvent, FunctionToolResultEvent, BuiltinToolCallEvent, BuiltinToolResultEvent],
1482
+ FunctionToolCallEvent | FunctionToolResultEvent | BuiltinToolCallEvent | BuiltinToolResultEvent,
1371
1483
  pydantic.Discriminator('event_kind'),
1372
1484
  ]
1373
1485
  """An event yielded when handling a model response, indicating tool calls and results."""
1374
1486
 
1375
- AgentStreamEvent = Annotated[Union[ModelResponseStreamEvent, HandleResponseEvent], pydantic.Discriminator('event_kind')]
1487
+ AgentStreamEvent = Annotated[ModelResponseStreamEvent | HandleResponseEvent, pydantic.Discriminator('event_kind')]
1376
1488
  """An event in the agent stream: model response stream events and response-handling events."""