pydantic-ai-slim 0.2.6__py3-none-any.whl → 0.2.8__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.
pydantic_ai/messages.py CHANGED
@@ -13,6 +13,7 @@ import pydantic_core
13
13
  from opentelemetry._events import Event # pyright: ignore[reportPrivateImportUsage]
14
14
  from typing_extensions import TypeAlias
15
15
 
16
+ from . import _utils
16
17
  from ._utils import generate_tool_call_id as _generate_tool_call_id, now_utc as _now_utc
17
18
  from .exceptions import UnexpectedModelBehavior
18
19
  from .usage import Usage
@@ -50,7 +51,7 @@ DocumentFormat: TypeAlias = Literal['csv', 'doc', 'docx', 'html', 'md', 'pdf', '
50
51
  VideoFormat: TypeAlias = Literal['mkv', 'mov', 'mp4', 'webm', 'flv', 'mpeg', 'mpg', 'wmv', 'three_gp']
51
52
 
52
53
 
53
- @dataclass
54
+ @dataclass(repr=False)
54
55
  class SystemPromptPart:
55
56
  """A system prompt, generally written by the application developer.
56
57
 
@@ -75,8 +76,10 @@ class SystemPromptPart:
75
76
  def otel_event(self, _settings: InstrumentationSettings) -> Event:
76
77
  return Event('gen_ai.system.message', body={'content': self.content, 'role': 'system'})
77
78
 
79
+ __repr__ = _utils.dataclasses_no_defaults_repr
78
80
 
79
- @dataclass
81
+
82
+ @dataclass(repr=False)
80
83
  class VideoUrl:
81
84
  """A URL to an video."""
82
85
 
@@ -116,8 +119,10 @@ class VideoUrl:
116
119
  """
117
120
  return _video_format_lookup[self.media_type]
118
121
 
122
+ __repr__ = _utils.dataclasses_no_defaults_repr
123
+
119
124
 
120
- @dataclass
125
+ @dataclass(repr=False)
121
126
  class AudioUrl:
122
127
  """A URL to an audio file."""
123
128
 
@@ -142,8 +147,10 @@ class AudioUrl:
142
147
  """The file format of the audio file."""
143
148
  return _audio_format_lookup[self.media_type]
144
149
 
150
+ __repr__ = _utils.dataclasses_no_defaults_repr
151
+
145
152
 
146
- @dataclass
153
+ @dataclass(repr=False)
147
154
  class ImageUrl:
148
155
  """A URL to an image."""
149
156
 
@@ -175,8 +182,10 @@ class ImageUrl:
175
182
  """
176
183
  return _image_format_lookup[self.media_type]
177
184
 
185
+ __repr__ = _utils.dataclasses_no_defaults_repr
178
186
 
179
- @dataclass
187
+
188
+ @dataclass(repr=False)
180
189
  class DocumentUrl:
181
190
  """The URL of the document."""
182
191
 
@@ -206,8 +215,10 @@ class DocumentUrl:
206
215
  except KeyError as e:
207
216
  raise ValueError(f'Unknown document media type: {media_type}') from e
208
217
 
218
+ __repr__ = _utils.dataclasses_no_defaults_repr
219
+
209
220
 
210
- @dataclass
221
+ @dataclass(repr=False)
211
222
  class BinaryContent:
212
223
  """Binary content, e.g. an audio or image file."""
213
224
 
@@ -255,6 +266,8 @@ class BinaryContent:
255
266
  except KeyError as e:
256
267
  raise ValueError(f'Unknown media type: {self.media_type}') from e
257
268
 
269
+ __repr__ = _utils.dataclasses_no_defaults_repr
270
+
258
271
 
259
272
  UserContent: TypeAlias = 'str | ImageUrl | AudioUrl | DocumentUrl | VideoUrl | BinaryContent'
260
273
 
@@ -292,7 +305,7 @@ _video_format_lookup: dict[str, VideoFormat] = {
292
305
  }
293
306
 
294
307
 
295
- @dataclass
308
+ @dataclass(repr=False)
296
309
  class UserPromptPart:
297
310
  """A user prompt, generally written by the end user.
298
311
 
@@ -329,11 +342,13 @@ class UserPromptPart:
329
342
  content.append({'kind': part.kind}) # pragma: no cover
330
343
  return Event('gen_ai.user.message', body={'content': content, 'role': 'user'})
331
344
 
345
+ __repr__ = _utils.dataclasses_no_defaults_repr
346
+
332
347
 
333
348
  tool_return_ta: pydantic.TypeAdapter[Any] = pydantic.TypeAdapter(Any, config=pydantic.ConfigDict(defer_build=True))
334
349
 
335
350
 
336
- @dataclass
351
+ @dataclass(repr=False)
337
352
  class ToolReturnPart:
338
353
  """A tool return message, this encodes the result of running a tool."""
339
354
 
@@ -373,11 +388,13 @@ class ToolReturnPart:
373
388
  body={'content': self.content, 'role': 'tool', 'id': self.tool_call_id, 'name': self.tool_name},
374
389
  )
375
390
 
391
+ __repr__ = _utils.dataclasses_no_defaults_repr
392
+
376
393
 
377
394
  error_details_ta = pydantic.TypeAdapter(list[pydantic_core.ErrorDetails], config=pydantic.ConfigDict(defer_build=True))
378
395
 
379
396
 
380
- @dataclass
397
+ @dataclass(repr=False)
381
398
  class RetryPromptPart:
382
399
  """A message back to a model asking it to try again.
383
400
 
@@ -438,6 +455,8 @@ class RetryPromptPart:
438
455
  },
439
456
  )
440
457
 
458
+ __repr__ = _utils.dataclasses_no_defaults_repr
459
+
441
460
 
442
461
  ModelRequestPart = Annotated[
443
462
  Union[SystemPromptPart, UserPromptPart, ToolReturnPart, RetryPromptPart], pydantic.Discriminator('part_kind')
@@ -445,7 +464,7 @@ ModelRequestPart = Annotated[
445
464
  """A message part sent by PydanticAI to a model."""
446
465
 
447
466
 
448
- @dataclass
467
+ @dataclass(repr=False)
449
468
  class ModelRequest:
450
469
  """A request generated by PydanticAI and sent to a model, e.g. a message from the PydanticAI app to the model."""
451
470
 
@@ -463,8 +482,10 @@ class ModelRequest:
463
482
  """Create a `ModelRequest` with a single user prompt as text."""
464
483
  return cls(parts=[UserPromptPart(user_prompt)], instructions=instructions)
465
484
 
485
+ __repr__ = _utils.dataclasses_no_defaults_repr
466
486
 
467
- @dataclass
487
+
488
+ @dataclass(repr=False)
468
489
  class TextPart:
469
490
  """A plain text response from a model."""
470
491
 
@@ -478,15 +499,17 @@ class TextPart:
478
499
  """Return `True` if the text content is non-empty."""
479
500
  return bool(self.content)
480
501
 
502
+ __repr__ = _utils.dataclasses_no_defaults_repr
503
+
481
504
 
482
- @dataclass
505
+ @dataclass(repr=False)
483
506
  class ToolCallPart:
484
507
  """A tool call from a model."""
485
508
 
486
509
  tool_name: str
487
510
  """The name of the tool to call."""
488
511
 
489
- args: str | dict[str, Any]
512
+ args: str | dict[str, Any] | None = None
490
513
  """The arguments to pass to the tool.
491
514
 
492
515
  This is stored either as a JSON string or a Python dictionary depending on how data was received.
@@ -506,10 +529,10 @@ class ToolCallPart:
506
529
 
507
530
  This is just for convenience with models that require dicts as input.
508
531
  """
532
+ if not self.args:
533
+ return {}
509
534
  if isinstance(self.args, dict):
510
535
  return self.args
511
- if isinstance(self.args, str) and not self.args:
512
- return {}
513
536
  args = pydantic_core.from_json(self.args)
514
537
  assert isinstance(args, dict), 'args should be a dict'
515
538
  return cast(dict[str, Any], args)
@@ -519,6 +542,8 @@ class ToolCallPart:
519
542
 
520
543
  This is just for convenience with models that require JSON strings as input.
521
544
  """
545
+ if not self.args:
546
+ return '{}'
522
547
  if isinstance(self.args, str):
523
548
  return self.args
524
549
  return pydantic_core.to_json(self.args).decode()
@@ -532,12 +557,14 @@ class ToolCallPart:
532
557
  else:
533
558
  return bool(self.args)
534
559
 
560
+ __repr__ = _utils.dataclasses_no_defaults_repr
561
+
535
562
 
536
563
  ModelResponsePart = Annotated[Union[TextPart, ToolCallPart], pydantic.Discriminator('part_kind')]
537
564
  """A message part returned by a model."""
538
565
 
539
566
 
540
- @dataclass
567
+ @dataclass(repr=False)
541
568
  class ModelResponse:
542
569
  """A response from a model, e.g. a message from the model to the PydanticAI app."""
543
570
 
@@ -602,6 +629,8 @@ class ModelResponse:
602
629
 
603
630
  return result
604
631
 
632
+ __repr__ = _utils.dataclasses_no_defaults_repr
633
+
605
634
 
606
635
  ModelMessage = Annotated[Union[ModelRequest, ModelResponse], pydantic.Discriminator('kind')]
607
636
  """Any message sent to or returned by a model."""
@@ -612,7 +641,7 @@ ModelMessagesTypeAdapter = pydantic.TypeAdapter(
612
641
  """Pydantic [`TypeAdapter`][pydantic.type_adapter.TypeAdapter] for (de)serializing messages."""
613
642
 
614
643
 
615
- @dataclass
644
+ @dataclass(repr=False)
616
645
  class TextPartDelta:
617
646
  """A partial update (delta) for a `TextPart` to append new text content."""
618
647
 
@@ -638,8 +667,10 @@ class TextPartDelta:
638
667
  raise ValueError('Cannot apply TextPartDeltas to non-TextParts') # pragma: no cover
639
668
  return replace(part, content=part.content + self.content_delta)
640
669
 
670
+ __repr__ = _utils.dataclasses_no_defaults_repr
671
+
641
672
 
642
- @dataclass
673
+ @dataclass(repr=False)
643
674
  class ToolCallPartDelta:
644
675
  """A partial update (delta) for a `ToolCallPart` to modify tool name, arguments, or tool call ID."""
645
676
 
@@ -666,9 +697,9 @@ class ToolCallPartDelta:
666
697
  """Convert this delta to a fully formed `ToolCallPart` if possible, otherwise return `None`.
667
698
 
668
699
  Returns:
669
- A `ToolCallPart` if both `tool_name_delta` and `args_delta` are set, otherwise `None`.
700
+ A `ToolCallPart` if `tool_name_delta` is set, otherwise `None`.
670
701
  """
671
- if self.tool_name_delta is None or self.args_delta is None:
702
+ if self.tool_name_delta is None:
672
703
  return None
673
704
 
674
705
  return ToolCallPart(self.tool_name_delta, self.args_delta, self.tool_call_id or _generate_tool_call_id())
@@ -728,7 +759,7 @@ class ToolCallPartDelta:
728
759
  delta = replace(delta, tool_call_id=self.tool_call_id)
729
760
 
730
761
  # If we now have enough data to create a full ToolCallPart, do so
731
- if delta.tool_name_delta is not None and delta.args_delta is not None:
762
+ if delta.tool_name_delta is not None:
732
763
  return ToolCallPart(delta.tool_name_delta, delta.args_delta, delta.tool_call_id or _generate_tool_call_id())
733
764
 
734
765
  return delta
@@ -741,12 +772,12 @@ class ToolCallPartDelta:
741
772
  part = replace(part, tool_name=tool_name)
742
773
 
743
774
  if isinstance(self.args_delta, str):
744
- if not isinstance(part.args, str):
775
+ if isinstance(part.args, dict):
745
776
  raise UnexpectedModelBehavior(f'Cannot apply JSON deltas to non-JSON tool arguments ({part=}, {self=})')
746
- updated_json = part.args + self.args_delta
777
+ updated_json = (part.args or '') + self.args_delta
747
778
  part = replace(part, args=updated_json)
748
779
  elif isinstance(self.args_delta, dict):
749
- if not isinstance(part.args, dict):
780
+ if isinstance(part.args, str):
750
781
  raise UnexpectedModelBehavior(f'Cannot apply dict deltas to non-dict tool arguments ({part=}, {self=})')
751
782
  updated_dict = {**(part.args or {}), **self.args_delta}
752
783
  part = replace(part, args=updated_dict)
@@ -755,12 +786,14 @@ class ToolCallPartDelta:
755
786
  part = replace(part, tool_call_id=self.tool_call_id)
756
787
  return part
757
788
 
789
+ __repr__ = _utils.dataclasses_no_defaults_repr
790
+
758
791
 
759
792
  ModelResponsePartDelta = Annotated[Union[TextPartDelta, ToolCallPartDelta], pydantic.Discriminator('part_delta_kind')]
760
793
  """A partial update (delta) for any model response part."""
761
794
 
762
795
 
763
- @dataclass
796
+ @dataclass(repr=False)
764
797
  class PartStartEvent:
765
798
  """An event indicating that a new part has started.
766
799
 
@@ -777,8 +810,10 @@ class PartStartEvent:
777
810
  event_kind: Literal['part_start'] = 'part_start'
778
811
  """Event type identifier, used as a discriminator."""
779
812
 
813
+ __repr__ = _utils.dataclasses_no_defaults_repr
780
814
 
781
- @dataclass
815
+
816
+ @dataclass(repr=False)
782
817
  class PartDeltaEvent:
783
818
  """An event indicating a delta update for an existing part."""
784
819
 
@@ -791,8 +826,10 @@ class PartDeltaEvent:
791
826
  event_kind: Literal['part_delta'] = 'part_delta'
792
827
  """Event type identifier, used as a discriminator."""
793
828
 
829
+ __repr__ = _utils.dataclasses_no_defaults_repr
830
+
794
831
 
795
- @dataclass
832
+ @dataclass(repr=False)
796
833
  class FinalResultEvent:
797
834
  """An event indicating the response to the current model request matches the output schema and will produce a result."""
798
835
 
@@ -803,6 +840,8 @@ class FinalResultEvent:
803
840
  event_kind: Literal['final_result'] = 'final_result'
804
841
  """Event type identifier, used as a discriminator."""
805
842
 
843
+ __repr__ = _utils.dataclasses_no_defaults_repr
844
+
806
845
 
807
846
  ModelResponseStreamEvent = Annotated[Union[PartStartEvent, PartDeltaEvent], pydantic.Discriminator('event_kind')]
808
847
  """An event in the model response stream, either starting a new part or applying a delta to an existing one."""
@@ -813,7 +852,7 @@ AgentStreamEvent = Annotated[
813
852
  """An event in the agent stream."""
814
853
 
815
854
 
816
- @dataclass
855
+ @dataclass(repr=False)
817
856
  class FunctionToolCallEvent:
818
857
  """An event indicating the start to a call to a function tool."""
819
858
 
@@ -827,8 +866,10 @@ class FunctionToolCallEvent:
827
866
  def __post_init__(self):
828
867
  self.call_id = self.part.tool_call_id or str(uuid.uuid4())
829
868
 
869
+ __repr__ = _utils.dataclasses_no_defaults_repr
830
870
 
831
- @dataclass
871
+
872
+ @dataclass(repr=False)
832
873
  class FunctionToolResultEvent:
833
874
  """An event indicating the result of a function tool call."""
834
875
 
@@ -839,6 +880,8 @@ class FunctionToolResultEvent:
839
880
  event_kind: Literal['function_tool_result'] = 'function_tool_result'
840
881
  """Event type identifier, used as a discriminator."""
841
882
 
883
+ __repr__ = _utils.dataclasses_no_defaults_repr
884
+
842
885
 
843
886
  HandleResponseEvent = Annotated[
844
887
  Union[FunctionToolCallEvent, FunctionToolResultEvent], pydantic.Discriminator('event_kind')