pydantic-ai-slim 0.7.6__py3-none-any.whl → 0.8.1__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/_cli.py +2 -1
- pydantic_ai/ag_ui.py +2 -2
- pydantic_ai/agent/__init__.py +22 -16
- pydantic_ai/agent/abstract.py +31 -18
- pydantic_ai/direct.py +5 -3
- pydantic_ai/durable_exec/temporal/__init__.py +67 -16
- pydantic_ai/durable_exec/temporal/_function_toolset.py +9 -2
- pydantic_ai/durable_exec/temporal/_logfire.py +5 -2
- pydantic_ai/mcp.py +48 -71
- pydantic_ai/messages.py +54 -13
- pydantic_ai/models/__init__.py +18 -8
- pydantic_ai/models/anthropic.py +1 -1
- pydantic_ai/models/bedrock.py +6 -2
- pydantic_ai/models/gemini.py +1 -1
- pydantic_ai/models/google.py +1 -1
- pydantic_ai/models/groq.py +1 -1
- pydantic_ai/models/huggingface.py +1 -1
- pydantic_ai/models/instrumented.py +14 -5
- pydantic_ai/models/mistral.py +2 -2
- pydantic_ai/models/openai.py +14 -4
- pydantic_ai/result.py +36 -18
- {pydantic_ai_slim-0.7.6.dist-info → pydantic_ai_slim-0.8.1.dist-info}/METADATA +5 -5
- {pydantic_ai_slim-0.7.6.dist-info → pydantic_ai_slim-0.8.1.dist-info}/RECORD +26 -26
- {pydantic_ai_slim-0.7.6.dist-info → pydantic_ai_slim-0.8.1.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-0.7.6.dist-info → pydantic_ai_slim-0.8.1.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-0.7.6.dist-info → pydantic_ai_slim-0.8.1.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/messages.py
CHANGED
|
@@ -110,7 +110,9 @@ class FileUrl(ABC):
|
|
|
110
110
|
- `GoogleModel`: `VideoUrl.vendor_metadata` is used as `video_metadata`: https://ai.google.dev/gemini-api/docs/video-understanding#customize-video-processing
|
|
111
111
|
"""
|
|
112
112
|
|
|
113
|
-
_media_type: str | None
|
|
113
|
+
_media_type: Annotated[str | None, pydantic.Field(alias='media_type', default=None, exclude=True)] = field(
|
|
114
|
+
compare=False, default=None
|
|
115
|
+
)
|
|
114
116
|
|
|
115
117
|
def __init__(
|
|
116
118
|
self,
|
|
@@ -124,6 +126,7 @@ class FileUrl(ABC):
|
|
|
124
126
|
self.force_download = force_download
|
|
125
127
|
self._media_type = media_type
|
|
126
128
|
|
|
129
|
+
@pydantic.computed_field
|
|
127
130
|
@property
|
|
128
131
|
def media_type(self) -> str:
|
|
129
132
|
"""Return the media type of the file, based on the URL or the provided `media_type`."""
|
|
@@ -160,8 +163,16 @@ class VideoUrl(FileUrl):
|
|
|
160
163
|
vendor_metadata: dict[str, Any] | None = None,
|
|
161
164
|
media_type: str | None = None,
|
|
162
165
|
kind: Literal['video-url'] = 'video-url',
|
|
166
|
+
*,
|
|
167
|
+
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
168
|
+
_media_type: str | None = None,
|
|
163
169
|
) -> None:
|
|
164
|
-
super().__init__(
|
|
170
|
+
super().__init__(
|
|
171
|
+
url=url,
|
|
172
|
+
force_download=force_download,
|
|
173
|
+
vendor_metadata=vendor_metadata,
|
|
174
|
+
media_type=media_type or _media_type,
|
|
175
|
+
)
|
|
165
176
|
self.kind = kind
|
|
166
177
|
|
|
167
178
|
def _infer_media_type(self) -> VideoMediaType:
|
|
@@ -223,8 +234,16 @@ class AudioUrl(FileUrl):
|
|
|
223
234
|
vendor_metadata: dict[str, Any] | None = None,
|
|
224
235
|
media_type: str | None = None,
|
|
225
236
|
kind: Literal['audio-url'] = 'audio-url',
|
|
237
|
+
*,
|
|
238
|
+
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
239
|
+
_media_type: str | None = None,
|
|
226
240
|
) -> None:
|
|
227
|
-
super().__init__(
|
|
241
|
+
super().__init__(
|
|
242
|
+
url=url,
|
|
243
|
+
force_download=force_download,
|
|
244
|
+
vendor_metadata=vendor_metadata,
|
|
245
|
+
media_type=media_type or _media_type,
|
|
246
|
+
)
|
|
228
247
|
self.kind = kind
|
|
229
248
|
|
|
230
249
|
def _infer_media_type(self) -> AudioMediaType:
|
|
@@ -273,8 +292,16 @@ class ImageUrl(FileUrl):
|
|
|
273
292
|
vendor_metadata: dict[str, Any] | None = None,
|
|
274
293
|
media_type: str | None = None,
|
|
275
294
|
kind: Literal['image-url'] = 'image-url',
|
|
295
|
+
*,
|
|
296
|
+
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
297
|
+
_media_type: str | None = None,
|
|
276
298
|
) -> None:
|
|
277
|
-
super().__init__(
|
|
299
|
+
super().__init__(
|
|
300
|
+
url=url,
|
|
301
|
+
force_download=force_download,
|
|
302
|
+
vendor_metadata=vendor_metadata,
|
|
303
|
+
media_type=media_type or _media_type,
|
|
304
|
+
)
|
|
278
305
|
self.kind = kind
|
|
279
306
|
|
|
280
307
|
def _infer_media_type(self) -> ImageMediaType:
|
|
@@ -318,8 +345,16 @@ class DocumentUrl(FileUrl):
|
|
|
318
345
|
vendor_metadata: dict[str, Any] | None = None,
|
|
319
346
|
media_type: str | None = None,
|
|
320
347
|
kind: Literal['document-url'] = 'document-url',
|
|
348
|
+
*,
|
|
349
|
+
# Required for inline-snapshot which expects all dataclass `__init__` methods to take all field names as kwargs.
|
|
350
|
+
_media_type: str | None = None,
|
|
321
351
|
) -> None:
|
|
322
|
-
super().__init__(
|
|
352
|
+
super().__init__(
|
|
353
|
+
url=url,
|
|
354
|
+
force_download=force_download,
|
|
355
|
+
vendor_metadata=vendor_metadata,
|
|
356
|
+
media_type=media_type or _media_type,
|
|
357
|
+
)
|
|
323
358
|
self.kind = kind
|
|
324
359
|
|
|
325
360
|
def _infer_media_type(self) -> str:
|
|
@@ -903,7 +938,7 @@ class ModelResponse:
|
|
|
903
938
|
For OpenAI models, this may include 'logprobs', 'finish_reason', etc.
|
|
904
939
|
"""
|
|
905
940
|
|
|
906
|
-
|
|
941
|
+
provider_response_id: str | None = None
|
|
907
942
|
"""request ID as specified by the model provider. This can be used to track the specific request to the model."""
|
|
908
943
|
|
|
909
944
|
def price(self) -> genai_types.PriceCalculation:
|
|
@@ -991,9 +1026,14 @@ class ModelResponse:
|
|
|
991
1026
|
return self.provider_details
|
|
992
1027
|
|
|
993
1028
|
@property
|
|
994
|
-
@deprecated('`vendor_id` is deprecated, use `
|
|
1029
|
+
@deprecated('`vendor_id` is deprecated, use `provider_response_id` instead')
|
|
995
1030
|
def vendor_id(self) -> str | None:
|
|
996
|
-
return self.
|
|
1031
|
+
return self.provider_response_id
|
|
1032
|
+
|
|
1033
|
+
@property
|
|
1034
|
+
@deprecated('`provider_request_id` is deprecated, use `provider_response_id` instead')
|
|
1035
|
+
def provider_request_id(self) -> str | None:
|
|
1036
|
+
return self.provider_response_id
|
|
997
1037
|
|
|
998
1038
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
999
1039
|
|
|
@@ -1263,13 +1303,10 @@ class FinalResultEvent:
|
|
|
1263
1303
|
__repr__ = _utils.dataclasses_no_defaults_repr
|
|
1264
1304
|
|
|
1265
1305
|
|
|
1266
|
-
ModelResponseStreamEvent = Annotated[
|
|
1267
|
-
"""An event in the model response stream, either starting a new part or applying a delta to an existing one."""
|
|
1268
|
-
|
|
1269
|
-
AgentStreamEvent = Annotated[
|
|
1306
|
+
ModelResponseStreamEvent = Annotated[
|
|
1270
1307
|
Union[PartStartEvent, PartDeltaEvent, FinalResultEvent], pydantic.Discriminator('event_kind')
|
|
1271
1308
|
]
|
|
1272
|
-
"""An event in the
|
|
1309
|
+
"""An event in the model response stream, starting a new part, applying a delta to an existing one, or indicating the final result."""
|
|
1273
1310
|
|
|
1274
1311
|
|
|
1275
1312
|
@dataclass(repr=False)
|
|
@@ -1338,3 +1375,7 @@ HandleResponseEvent = Annotated[
|
|
|
1338
1375
|
Union[FunctionToolCallEvent, FunctionToolResultEvent, BuiltinToolCallEvent, BuiltinToolResultEvent],
|
|
1339
1376
|
pydantic.Discriminator('event_kind'),
|
|
1340
1377
|
]
|
|
1378
|
+
"""An event yielded when handling a model response, indicating tool calls and results."""
|
|
1379
|
+
|
|
1380
|
+
AgentStreamEvent = Annotated[Union[ModelResponseStreamEvent, HandleResponseEvent], pydantic.Discriminator('event_kind')]
|
|
1381
|
+
"""An event in the agent stream: model response stream events and response-handling events."""
|
pydantic_ai/models/__init__.py
CHANGED
|
@@ -7,6 +7,7 @@ specific LLM being used.
|
|
|
7
7
|
from __future__ import annotations as _annotations
|
|
8
8
|
|
|
9
9
|
import base64
|
|
10
|
+
import warnings
|
|
10
11
|
from abc import ABC, abstractmethod
|
|
11
12
|
from collections.abc import AsyncIterator, Iterator
|
|
12
13
|
from contextlib import asynccontextmanager, contextmanager
|
|
@@ -25,7 +26,6 @@ from .._run_context import RunContext
|
|
|
25
26
|
from ..builtin_tools import AbstractBuiltinTool
|
|
26
27
|
from ..exceptions import UserError
|
|
27
28
|
from ..messages import (
|
|
28
|
-
AgentStreamEvent,
|
|
29
29
|
FileUrl,
|
|
30
30
|
FinalResultEvent,
|
|
31
31
|
ModelMessage,
|
|
@@ -555,11 +555,11 @@ class StreamedResponse(ABC):
|
|
|
555
555
|
final_result_event: FinalResultEvent | None = field(default=None, init=False)
|
|
556
556
|
|
|
557
557
|
_parts_manager: ModelResponsePartsManager = field(default_factory=ModelResponsePartsManager, init=False)
|
|
558
|
-
_event_iterator: AsyncIterator[
|
|
558
|
+
_event_iterator: AsyncIterator[ModelResponseStreamEvent] | None = field(default=None, init=False)
|
|
559
559
|
_usage: RequestUsage = field(default_factory=RequestUsage, init=False)
|
|
560
560
|
|
|
561
|
-
def __aiter__(self) -> AsyncIterator[
|
|
562
|
-
"""Stream the response as an async iterable of [`
|
|
561
|
+
def __aiter__(self) -> AsyncIterator[ModelResponseStreamEvent]:
|
|
562
|
+
"""Stream the response as an async iterable of [`ModelResponseStreamEvent`][pydantic_ai.messages.ModelResponseStreamEvent]s.
|
|
563
563
|
|
|
564
564
|
This proxies the `_event_iterator()` and emits all events, while also checking for matches
|
|
565
565
|
on the result schema and emitting a [`FinalResultEvent`][pydantic_ai.messages.FinalResultEvent] if/when the
|
|
@@ -569,7 +569,7 @@ class StreamedResponse(ABC):
|
|
|
569
569
|
|
|
570
570
|
async def iterator_with_final_event(
|
|
571
571
|
iterator: AsyncIterator[ModelResponseStreamEvent],
|
|
572
|
-
) -> AsyncIterator[
|
|
572
|
+
) -> AsyncIterator[ModelResponseStreamEvent]:
|
|
573
573
|
async for event in iterator:
|
|
574
574
|
yield event
|
|
575
575
|
if (
|
|
@@ -685,19 +685,29 @@ def infer_model(model: Model | KnownModelName | str) -> Model: # noqa: C901
|
|
|
685
685
|
try:
|
|
686
686
|
provider, model_name = model.split(':', maxsplit=1)
|
|
687
687
|
except ValueError:
|
|
688
|
+
provider = None
|
|
688
689
|
model_name = model
|
|
689
|
-
# TODO(Marcelo): We should deprecate this way.
|
|
690
690
|
if model_name.startswith(('gpt', 'o1', 'o3')):
|
|
691
691
|
provider = 'openai'
|
|
692
692
|
elif model_name.startswith('claude'):
|
|
693
693
|
provider = 'anthropic'
|
|
694
694
|
elif model_name.startswith('gemini'):
|
|
695
695
|
provider = 'google-gla'
|
|
696
|
+
|
|
697
|
+
if provider is not None:
|
|
698
|
+
warnings.warn(
|
|
699
|
+
f"Specifying a model name without a provider prefix is deprecated. Instead of {model_name!r}, use '{provider}:{model_name}'.",
|
|
700
|
+
DeprecationWarning,
|
|
701
|
+
)
|
|
696
702
|
else:
|
|
697
703
|
raise UserError(f'Unknown model: {model}')
|
|
698
704
|
|
|
699
|
-
if provider == 'vertexai':
|
|
700
|
-
|
|
705
|
+
if provider == 'vertexai': # pragma: no cover
|
|
706
|
+
warnings.warn(
|
|
707
|
+
"The 'vertexai' provider name is deprecated. Use 'google-vertex' instead.",
|
|
708
|
+
DeprecationWarning,
|
|
709
|
+
)
|
|
710
|
+
provider = 'google-vertex'
|
|
701
711
|
|
|
702
712
|
if provider == 'cohere':
|
|
703
713
|
from .cohere import CohereModel
|
pydantic_ai/models/anthropic.py
CHANGED
pydantic_ai/models/bedrock.py
CHANGED
|
@@ -301,9 +301,13 @@ class BedrockConverseModel(Model):
|
|
|
301
301
|
input_tokens=response['usage']['inputTokens'],
|
|
302
302
|
output_tokens=response['usage']['outputTokens'],
|
|
303
303
|
)
|
|
304
|
-
|
|
304
|
+
response_id = response.get('ResponseMetadata', {}).get('RequestId', None)
|
|
305
305
|
return ModelResponse(
|
|
306
|
-
items,
|
|
306
|
+
items,
|
|
307
|
+
usage=u,
|
|
308
|
+
model_name=self.model_name,
|
|
309
|
+
provider_response_id=response_id,
|
|
310
|
+
provider_name=self._provider.name,
|
|
307
311
|
)
|
|
308
312
|
|
|
309
313
|
@overload
|
pydantic_ai/models/gemini.py
CHANGED
|
@@ -690,7 +690,7 @@ def _process_response_from_parts(
|
|
|
690
690
|
f'Unsupported response from Gemini, expected all parts to be function calls or text, got: {part!r}'
|
|
691
691
|
)
|
|
692
692
|
return ModelResponse(
|
|
693
|
-
parts=items, usage=usage, model_name=model_name,
|
|
693
|
+
parts=items, usage=usage, model_name=model_name, provider_response_id=vendor_id, provider_details=vendor_details
|
|
694
694
|
)
|
|
695
695
|
|
|
696
696
|
|
pydantic_ai/models/google.py
CHANGED
pydantic_ai/models/groq.py
CHANGED
|
@@ -236,27 +236,36 @@ class InstrumentationSettings:
|
|
|
236
236
|
if response.provider_details and 'finish_reason' in response.provider_details:
|
|
237
237
|
output_message['finish_reason'] = response.provider_details['finish_reason']
|
|
238
238
|
instructions = InstrumentedModel._get_instructions(input_messages) # pyright: ignore [reportPrivateUsage]
|
|
239
|
+
system_instructions_attributes = self.system_instructions_attributes(instructions)
|
|
239
240
|
attributes = {
|
|
240
241
|
'gen_ai.input.messages': json.dumps(self.messages_to_otel_messages(input_messages)),
|
|
241
242
|
'gen_ai.output.messages': json.dumps([output_message]),
|
|
243
|
+
**system_instructions_attributes,
|
|
242
244
|
'logfire.json_schema': json.dumps(
|
|
243
245
|
{
|
|
244
246
|
'type': 'object',
|
|
245
247
|
'properties': {
|
|
246
248
|
'gen_ai.input.messages': {'type': 'array'},
|
|
247
249
|
'gen_ai.output.messages': {'type': 'array'},
|
|
248
|
-
**(
|
|
250
|
+
**(
|
|
251
|
+
{'gen_ai.system_instructions': {'type': 'array'}}
|
|
252
|
+
if system_instructions_attributes
|
|
253
|
+
else {}
|
|
254
|
+
),
|
|
249
255
|
'model_request_parameters': {'type': 'object'},
|
|
250
256
|
},
|
|
251
257
|
}
|
|
252
258
|
),
|
|
253
259
|
}
|
|
254
|
-
if instructions is not None:
|
|
255
|
-
attributes['gen_ai.system_instructions'] = json.dumps(
|
|
256
|
-
[_otel_messages.TextPart(type='text', content=instructions)]
|
|
257
|
-
)
|
|
258
260
|
span.set_attributes(attributes)
|
|
259
261
|
|
|
262
|
+
def system_instructions_attributes(self, instructions: str | None) -> dict[str, str]:
|
|
263
|
+
if instructions and self.include_content:
|
|
264
|
+
return {
|
|
265
|
+
'gen_ai.system_instructions': json.dumps([_otel_messages.TextPart(type='text', content=instructions)]),
|
|
266
|
+
}
|
|
267
|
+
return {}
|
|
268
|
+
|
|
260
269
|
def _emit_events(self, span: Span, events: list[Event]) -> None:
|
|
261
270
|
if self.event_mode == 'logs':
|
|
262
271
|
for event in events:
|
pydantic_ai/models/mistral.py
CHANGED
|
@@ -79,7 +79,7 @@ try:
|
|
|
79
79
|
from mistralai.models.usermessage import UserMessage as MistralUserMessage
|
|
80
80
|
from mistralai.types.basemodel import Unset as MistralUnset
|
|
81
81
|
from mistralai.utils.eventstreaming import EventStreamAsync as MistralEventStreamAsync
|
|
82
|
-
except ImportError as e:
|
|
82
|
+
except ImportError as e:
|
|
83
83
|
raise ImportError(
|
|
84
84
|
'Please install `mistral` to use the Mistral model, '
|
|
85
85
|
'you can use the `mistral` optional group — `pip install "pydantic-ai-slim[mistral]"`'
|
|
@@ -352,7 +352,7 @@ class MistralModel(Model):
|
|
|
352
352
|
usage=_map_usage(response),
|
|
353
353
|
model_name=response.model,
|
|
354
354
|
timestamp=timestamp,
|
|
355
|
-
|
|
355
|
+
provider_response_id=response.id,
|
|
356
356
|
provider_name=self._provider.name,
|
|
357
357
|
)
|
|
358
358
|
|
pydantic_ai/models/openai.py
CHANGED
|
@@ -517,7 +517,7 @@ class OpenAIChatModel(Model):
|
|
|
517
517
|
model_name=response.model,
|
|
518
518
|
timestamp=timestamp,
|
|
519
519
|
provider_details=vendor_details,
|
|
520
|
-
|
|
520
|
+
provider_response_id=response.id,
|
|
521
521
|
provider_name=self._provider.name,
|
|
522
522
|
)
|
|
523
523
|
|
|
@@ -831,7 +831,7 @@ class OpenAIResponsesModel(Model):
|
|
|
831
831
|
items,
|
|
832
832
|
usage=_map_usage(response),
|
|
833
833
|
model_name=response.model,
|
|
834
|
-
|
|
834
|
+
provider_response_id=response.id,
|
|
835
835
|
timestamp=timestamp,
|
|
836
836
|
provider_name=self._provider.name,
|
|
837
837
|
)
|
|
@@ -1375,11 +1375,21 @@ def _map_usage(response: chat.ChatCompletion | ChatCompletionChunk | responses.R
|
|
|
1375
1375
|
).items()
|
|
1376
1376
|
if isinstance(value, int)
|
|
1377
1377
|
}
|
|
1378
|
-
|
|
1378
|
+
# Handle vLLM compatibility - some providers don't include token details
|
|
1379
|
+
if getattr(response_usage, 'input_tokens_details', None) is not None:
|
|
1380
|
+
cache_read_tokens = response_usage.input_tokens_details.cached_tokens
|
|
1381
|
+
else:
|
|
1382
|
+
cache_read_tokens = 0
|
|
1383
|
+
|
|
1384
|
+
if getattr(response_usage, 'output_tokens_details', None) is not None:
|
|
1385
|
+
details['reasoning_tokens'] = response_usage.output_tokens_details.reasoning_tokens
|
|
1386
|
+
else:
|
|
1387
|
+
details['reasoning_tokens'] = 0
|
|
1388
|
+
|
|
1379
1389
|
return usage.RequestUsage(
|
|
1380
1390
|
input_tokens=response_usage.input_tokens,
|
|
1381
1391
|
output_tokens=response_usage.output_tokens,
|
|
1382
|
-
cache_read_tokens=
|
|
1392
|
+
cache_read_tokens=cache_read_tokens,
|
|
1383
1393
|
details=details,
|
|
1384
1394
|
)
|
|
1385
1395
|
else:
|
pydantic_ai/result.py
CHANGED
|
@@ -7,7 +7,7 @@ from datetime import datetime
|
|
|
7
7
|
from typing import Generic, cast
|
|
8
8
|
|
|
9
9
|
from pydantic import ValidationError
|
|
10
|
-
from typing_extensions import TypeVar
|
|
10
|
+
from typing_extensions import TypeVar, deprecated
|
|
11
11
|
|
|
12
12
|
from pydantic_ai._tool_manager import ToolManager
|
|
13
13
|
|
|
@@ -22,7 +22,7 @@ from ._output import (
|
|
|
22
22
|
ToolOutputSchema,
|
|
23
23
|
)
|
|
24
24
|
from ._run_context import AgentDepsT, RunContext
|
|
25
|
-
from .messages import
|
|
25
|
+
from .messages import ModelResponseStreamEvent
|
|
26
26
|
from .output import (
|
|
27
27
|
OutputDataT,
|
|
28
28
|
ToolOutput,
|
|
@@ -51,7 +51,7 @@ class AgentStream(Generic[AgentDepsT, OutputDataT]):
|
|
|
51
51
|
_usage_limits: UsageLimits | None
|
|
52
52
|
_tool_manager: ToolManager[AgentDepsT]
|
|
53
53
|
|
|
54
|
-
_agent_stream_iterator: AsyncIterator[
|
|
54
|
+
_agent_stream_iterator: AsyncIterator[ModelResponseStreamEvent] | None = field(default=None, init=False)
|
|
55
55
|
_initial_run_ctx_usage: RunUsage = field(init=False)
|
|
56
56
|
|
|
57
57
|
def __post_init__(self):
|
|
@@ -62,11 +62,11 @@ class AgentStream(Generic[AgentDepsT, OutputDataT]):
|
|
|
62
62
|
async for response in self.stream_responses(debounce_by=debounce_by):
|
|
63
63
|
if self._raw_stream_response.final_result_event is not None:
|
|
64
64
|
try:
|
|
65
|
-
yield await self.
|
|
65
|
+
yield await self.validate_response_output(response, allow_partial=True)
|
|
66
66
|
except ValidationError:
|
|
67
67
|
pass
|
|
68
68
|
if self._raw_stream_response.final_result_event is not None: # pragma: no branch
|
|
69
|
-
yield await self.
|
|
69
|
+
yield await self.validate_response_output(self._raw_stream_response.get())
|
|
70
70
|
|
|
71
71
|
async def stream_responses(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[_messages.ModelResponse]:
|
|
72
72
|
"""Asynchronously stream the (unvalidated) model responses for the agent."""
|
|
@@ -127,9 +127,11 @@ class AgentStream(Generic[AgentDepsT, OutputDataT]):
|
|
|
127
127
|
async for _ in self:
|
|
128
128
|
pass
|
|
129
129
|
|
|
130
|
-
return await self.
|
|
130
|
+
return await self.validate_response_output(self._raw_stream_response.get())
|
|
131
131
|
|
|
132
|
-
async def
|
|
132
|
+
async def validate_response_output(
|
|
133
|
+
self, message: _messages.ModelResponse, *, allow_partial: bool = False
|
|
134
|
+
) -> OutputDataT:
|
|
133
135
|
"""Validate a structured result message."""
|
|
134
136
|
final_result_event = self._raw_stream_response.final_result_event
|
|
135
137
|
if final_result_event is None:
|
|
@@ -221,8 +223,8 @@ class AgentStream(Generic[AgentDepsT, OutputDataT]):
|
|
|
221
223
|
deltas.append(text)
|
|
222
224
|
yield ''.join(deltas)
|
|
223
225
|
|
|
224
|
-
def __aiter__(self) -> AsyncIterator[
|
|
225
|
-
"""Stream [`
|
|
226
|
+
def __aiter__(self) -> AsyncIterator[ModelResponseStreamEvent]:
|
|
227
|
+
"""Stream [`ModelResponseStreamEvent`][pydantic_ai.messages.ModelResponseStreamEvent]s."""
|
|
226
228
|
if self._agent_stream_iterator is None:
|
|
227
229
|
self._agent_stream_iterator = _get_usage_checking_stream_response(
|
|
228
230
|
self._raw_stream_response, self._usage_limits, self.usage
|
|
@@ -245,9 +247,9 @@ class StreamedRunResult(Generic[AgentDepsT, OutputDataT]):
|
|
|
245
247
|
"""Whether the stream has all been received.
|
|
246
248
|
|
|
247
249
|
This is set to `True` when one of
|
|
248
|
-
[`
|
|
250
|
+
[`stream_output`][pydantic_ai.result.StreamedRunResult.stream_output],
|
|
249
251
|
[`stream_text`][pydantic_ai.result.StreamedRunResult.stream_text],
|
|
250
|
-
[`
|
|
252
|
+
[`stream_responses`][pydantic_ai.result.StreamedRunResult.stream_responses] or
|
|
251
253
|
[`get_output`][pydantic_ai.result.StreamedRunResult.get_output] completes.
|
|
252
254
|
"""
|
|
253
255
|
|
|
@@ -318,16 +320,21 @@ class StreamedRunResult(Generic[AgentDepsT, OutputDataT]):
|
|
|
318
320
|
self.new_messages(output_tool_return_content=output_tool_return_content)
|
|
319
321
|
)
|
|
320
322
|
|
|
323
|
+
@deprecated('`StreamedRunResult.stream` is deprecated, use `stream_output` instead.')
|
|
321
324
|
async def stream(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]:
|
|
322
|
-
|
|
325
|
+
async for output in self.stream_output(debounce_by=debounce_by):
|
|
326
|
+
yield output
|
|
327
|
+
|
|
328
|
+
async def stream_output(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]:
|
|
329
|
+
"""Stream the output as an async iterable.
|
|
323
330
|
|
|
324
331
|
The pydantic validator for structured data will be called in
|
|
325
332
|
[partial mode](https://docs.pydantic.dev/dev/concepts/experimental/#partial-validation)
|
|
326
333
|
on each iteration.
|
|
327
334
|
|
|
328
335
|
Args:
|
|
329
|
-
debounce_by: by how much (if at all) to debounce/group the
|
|
330
|
-
Debouncing is particularly important for long structured
|
|
336
|
+
debounce_by: by how much (if at all) to debounce/group the output chunks by. `None` means no debouncing.
|
|
337
|
+
Debouncing is particularly important for long structured outputs to reduce the overhead of
|
|
331
338
|
performing validation as each token is received.
|
|
332
339
|
|
|
333
340
|
Returns:
|
|
@@ -354,8 +361,15 @@ class StreamedRunResult(Generic[AgentDepsT, OutputDataT]):
|
|
|
354
361
|
yield text
|
|
355
362
|
await self._marked_completed(self._stream_response.get())
|
|
356
363
|
|
|
364
|
+
@deprecated('`StreamedRunResult.stream_structured` is deprecated, use `stream_responses` instead.')
|
|
357
365
|
async def stream_structured(
|
|
358
366
|
self, *, debounce_by: float | None = 0.1
|
|
367
|
+
) -> AsyncIterator[tuple[_messages.ModelResponse, bool]]:
|
|
368
|
+
async for msg, last in self.stream_responses(debounce_by=debounce_by):
|
|
369
|
+
yield msg, last
|
|
370
|
+
|
|
371
|
+
async def stream_responses(
|
|
372
|
+
self, *, debounce_by: float | None = 0.1
|
|
359
373
|
) -> AsyncIterator[tuple[_messages.ModelResponse, bool]]:
|
|
360
374
|
"""Stream the response as an async iterable of Structured LLM Messages.
|
|
361
375
|
|
|
@@ -394,13 +408,17 @@ class StreamedRunResult(Generic[AgentDepsT, OutputDataT]):
|
|
|
394
408
|
"""Get the timestamp of the response."""
|
|
395
409
|
return self._stream_response.timestamp()
|
|
396
410
|
|
|
411
|
+
@deprecated('`validate_structured_output` is deprecated, use `validate_response_output` instead.')
|
|
397
412
|
async def validate_structured_output(
|
|
398
413
|
self, message: _messages.ModelResponse, *, allow_partial: bool = False
|
|
414
|
+
) -> OutputDataT:
|
|
415
|
+
return await self._stream_response.validate_response_output(message, allow_partial=allow_partial)
|
|
416
|
+
|
|
417
|
+
async def validate_response_output(
|
|
418
|
+
self, message: _messages.ModelResponse, *, allow_partial: bool = False
|
|
399
419
|
) -> OutputDataT:
|
|
400
420
|
"""Validate a structured result message."""
|
|
401
|
-
return await self._stream_response.
|
|
402
|
-
message, allow_partial=allow_partial
|
|
403
|
-
)
|
|
421
|
+
return await self._stream_response.validate_response_output(message, allow_partial=allow_partial)
|
|
404
422
|
|
|
405
423
|
async def _marked_completed(self, message: _messages.ModelResponse) -> None:
|
|
406
424
|
self.is_complete = True
|
|
@@ -426,7 +444,7 @@ def _get_usage_checking_stream_response(
|
|
|
426
444
|
stream_response: models.StreamedResponse,
|
|
427
445
|
limits: UsageLimits | None,
|
|
428
446
|
get_usage: Callable[[], RunUsage],
|
|
429
|
-
) -> AsyncIterator[
|
|
447
|
+
) -> AsyncIterator[ModelResponseStreamEvent]:
|
|
430
448
|
if limits is not None and limits.has_token_limits():
|
|
431
449
|
|
|
432
450
|
async def _usage_checking_iterator():
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.1
|
|
4
4
|
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
|
|
5
5
|
Project-URL: Homepage, https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim
|
|
6
6
|
Project-URL: Source, https://github.com/pydantic/pydantic-ai/tree/main/pydantic_ai_slim
|
|
@@ -35,7 +35,7 @@ Requires-Dist: genai-prices>=0.0.22
|
|
|
35
35
|
Requires-Dist: griffe>=1.3.2
|
|
36
36
|
Requires-Dist: httpx>=0.27
|
|
37
37
|
Requires-Dist: opentelemetry-api>=1.28.0
|
|
38
|
-
Requires-Dist: pydantic-graph==0.
|
|
38
|
+
Requires-Dist: pydantic-graph==0.8.1
|
|
39
39
|
Requires-Dist: pydantic>=2.10
|
|
40
40
|
Requires-Dist: typing-inspection>=0.4.0
|
|
41
41
|
Provides-Extra: a2a
|
|
@@ -57,7 +57,7 @@ Requires-Dist: cohere>=5.16.0; (platform_system != 'Emscripten') and extra == 'c
|
|
|
57
57
|
Provides-Extra: duckduckgo
|
|
58
58
|
Requires-Dist: ddgs>=9.0.0; extra == 'duckduckgo'
|
|
59
59
|
Provides-Extra: evals
|
|
60
|
-
Requires-Dist: pydantic-evals==0.
|
|
60
|
+
Requires-Dist: pydantic-evals==0.8.1; extra == 'evals'
|
|
61
61
|
Provides-Extra: google
|
|
62
62
|
Requires-Dist: google-genai>=1.31.0; extra == 'google'
|
|
63
63
|
Provides-Extra: groq
|
|
@@ -67,7 +67,7 @@ Requires-Dist: huggingface-hub[inference]>=0.33.5; extra == 'huggingface'
|
|
|
67
67
|
Provides-Extra: logfire
|
|
68
68
|
Requires-Dist: logfire>=3.14.1; extra == 'logfire'
|
|
69
69
|
Provides-Extra: mcp
|
|
70
|
-
Requires-Dist: mcp>=1.
|
|
70
|
+
Requires-Dist: mcp>=1.12.3; (python_version >= '3.10') and extra == 'mcp'
|
|
71
71
|
Provides-Extra: mistral
|
|
72
72
|
Requires-Dist: mistralai>=1.9.2; extra == 'mistral'
|
|
73
73
|
Provides-Extra: openai
|
|
@@ -77,7 +77,7 @@ Requires-Dist: tenacity>=8.2.3; extra == 'retries'
|
|
|
77
77
|
Provides-Extra: tavily
|
|
78
78
|
Requires-Dist: tavily-python>=0.5.0; extra == 'tavily'
|
|
79
79
|
Provides-Extra: temporal
|
|
80
|
-
Requires-Dist: temporalio==1.
|
|
80
|
+
Requires-Dist: temporalio==1.16.0; extra == 'temporal'
|
|
81
81
|
Provides-Extra: vertexai
|
|
82
82
|
Requires-Dist: google-auth>=2.36.0; extra == 'vertexai'
|
|
83
83
|
Requires-Dist: requests>=2.32.2; extra == 'vertexai'
|