pydantic-ai-slim 0.0.54__py3-none-any.whl → 0.1.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.
- pydantic_ai/__init__.py +10 -3
- pydantic_ai/_agent_graph.py +67 -55
- pydantic_ai/_cli.py +1 -2
- pydantic_ai/{_result.py → _output.py} +69 -47
- pydantic_ai/_utils.py +20 -0
- pydantic_ai/agent.py +503 -163
- pydantic_ai/format_as_xml.py +6 -113
- pydantic_ai/format_prompt.py +116 -0
- pydantic_ai/messages.py +104 -21
- pydantic_ai/models/__init__.py +25 -5
- pydantic_ai/models/_json_schema.py +156 -0
- pydantic_ai/models/anthropic.py +14 -4
- pydantic_ai/models/bedrock.py +100 -22
- pydantic_ai/models/cohere.py +48 -44
- pydantic_ai/models/fallback.py +2 -1
- pydantic_ai/models/function.py +8 -8
- pydantic_ai/models/gemini.py +65 -75
- pydantic_ai/models/groq.py +34 -29
- pydantic_ai/models/instrumented.py +4 -4
- pydantic_ai/models/mistral.py +67 -58
- pydantic_ai/models/openai.py +113 -158
- pydantic_ai/models/test.py +45 -46
- pydantic_ai/models/wrapper.py +3 -0
- pydantic_ai/providers/__init__.py +4 -0
- pydantic_ai/providers/azure.py +2 -2
- pydantic_ai/result.py +203 -90
- pydantic_ai/tools.py +3 -3
- {pydantic_ai_slim-0.0.54.dist-info → pydantic_ai_slim-0.1.0.dist-info}/METADATA +5 -5
- pydantic_ai_slim-0.1.0.dist-info/RECORD +53 -0
- pydantic_ai_slim-0.0.54.dist-info/RECORD +0 -51
- {pydantic_ai_slim-0.0.54.dist-info → pydantic_ai_slim-0.1.0.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-0.0.54.dist-info → pydantic_ai_slim-0.1.0.dist-info}/entry_points.txt +0 -0
pydantic_ai/result.py
CHANGED
|
@@ -1,43 +1,47 @@
|
|
|
1
1
|
from __future__ import annotations as _annotations
|
|
2
2
|
|
|
3
|
+
import warnings
|
|
3
4
|
from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Callable
|
|
4
5
|
from copy import copy
|
|
5
6
|
from dataclasses import dataclass, field
|
|
6
7
|
from datetime import datetime
|
|
7
|
-
from typing import Generic, Union, cast
|
|
8
|
+
from typing import TYPE_CHECKING, Generic, Union, cast
|
|
8
9
|
|
|
9
|
-
from typing_extensions import TypeVar, assert_type
|
|
10
|
+
from typing_extensions import TypeVar, assert_type, deprecated, overload
|
|
10
11
|
|
|
11
|
-
from . import
|
|
12
|
+
from . import _utils, exceptions, messages as _messages, models
|
|
12
13
|
from .messages import AgentStreamEvent, FinalResultEvent
|
|
13
14
|
from .tools import AgentDepsT, RunContext
|
|
14
15
|
from .usage import Usage, UsageLimits
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from . import _output
|
|
19
|
+
|
|
20
|
+
__all__ = 'OutputDataT', 'OutputDataT_inv', 'ToolOutput', 'OutputValidatorFunc'
|
|
17
21
|
|
|
18
22
|
|
|
19
23
|
T = TypeVar('T')
|
|
20
24
|
"""An invariant TypeVar."""
|
|
21
|
-
|
|
25
|
+
OutputDataT_inv = TypeVar('OutputDataT_inv', default=str)
|
|
22
26
|
"""
|
|
23
27
|
An invariant type variable for the result data of a model.
|
|
24
28
|
|
|
25
|
-
We need to use an invariant typevar for `
|
|
26
|
-
in both the input and output of a `
|
|
27
|
-
possessing
|
|
29
|
+
We need to use an invariant typevar for `OutputValidator` and `OutputValidatorFunc` because the output data type is used
|
|
30
|
+
in both the input and output of a `OutputValidatorFunc`. This can theoretically lead to some issues assuming that types
|
|
31
|
+
possessing OutputValidator's are covariant in the result data type, but in practice this is rarely an issue, and
|
|
28
32
|
changing it would have negative consequences for the ergonomics of the library.
|
|
29
33
|
|
|
30
|
-
At some point, it may make sense to change the input to
|
|
34
|
+
At some point, it may make sense to change the input to OutputValidatorFunc to be `Any` or `object` as doing that would
|
|
31
35
|
resolve these potential variance issues.
|
|
32
36
|
"""
|
|
33
|
-
|
|
37
|
+
OutputDataT = TypeVar('OutputDataT', default=str, covariant=True)
|
|
34
38
|
"""Covariant type variable for the result data type of a run."""
|
|
35
39
|
|
|
36
|
-
|
|
37
|
-
Callable[[RunContext[AgentDepsT],
|
|
38
|
-
Callable[[RunContext[AgentDepsT],
|
|
39
|
-
Callable[[
|
|
40
|
-
Callable[[
|
|
40
|
+
OutputValidatorFunc = Union[
|
|
41
|
+
Callable[[RunContext[AgentDepsT], OutputDataT_inv], OutputDataT_inv],
|
|
42
|
+
Callable[[RunContext[AgentDepsT], OutputDataT_inv], Awaitable[OutputDataT_inv]],
|
|
43
|
+
Callable[[OutputDataT_inv], OutputDataT_inv],
|
|
44
|
+
Callable[[OutputDataT_inv], Awaitable[OutputDataT_inv]],
|
|
41
45
|
]
|
|
42
46
|
"""
|
|
43
47
|
A function that always takes and returns the same type of data (which is the result type of an agent run), and:
|
|
@@ -45,15 +49,56 @@ A function that always takes and returns the same type of data (which is the res
|
|
|
45
49
|
* may or may not take [`RunContext`][pydantic_ai.tools.RunContext] as a first argument
|
|
46
50
|
* may or may not be async
|
|
47
51
|
|
|
48
|
-
Usage `
|
|
52
|
+
Usage `OutputValidatorFunc[AgentDepsT, T]`.
|
|
49
53
|
"""
|
|
50
54
|
|
|
55
|
+
DEFAULT_OUTPUT_TOOL_NAME = 'final_result'
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@dataclass(init=False)
|
|
59
|
+
class ToolOutput(Generic[OutputDataT]):
|
|
60
|
+
"""Marker class to use tools for structured outputs, and customize the tool."""
|
|
61
|
+
|
|
62
|
+
output_type: type[OutputDataT]
|
|
63
|
+
# TODO: Add `output_call` support, for calling a function to get the output
|
|
64
|
+
# output_call: Callable[..., OutputDataT] | None
|
|
65
|
+
name: str
|
|
66
|
+
description: str | None
|
|
67
|
+
max_retries: int | None
|
|
68
|
+
strict: bool | None
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self,
|
|
72
|
+
*,
|
|
73
|
+
type_: type[OutputDataT],
|
|
74
|
+
# call: Callable[..., OutputDataT] | None = None,
|
|
75
|
+
name: str = 'final_result',
|
|
76
|
+
description: str | None = None,
|
|
77
|
+
max_retries: int | None = None,
|
|
78
|
+
strict: bool | None = None,
|
|
79
|
+
):
|
|
80
|
+
self.output_type = type_
|
|
81
|
+
self.name = name
|
|
82
|
+
self.description = description
|
|
83
|
+
self.max_retries = max_retries
|
|
84
|
+
self.strict = strict
|
|
85
|
+
|
|
86
|
+
# TODO: add support for call and make type_ optional, with the following logic:
|
|
87
|
+
# if type_ is None and call is None:
|
|
88
|
+
# raise ValueError('Either type_ or call must be provided')
|
|
89
|
+
# if call is not None:
|
|
90
|
+
# if type_ is None:
|
|
91
|
+
# type_ = get_type_hints(call).get('return')
|
|
92
|
+
# if type_ is None:
|
|
93
|
+
# raise ValueError('Unable to determine type_ from call signature; please provide it explicitly')
|
|
94
|
+
# self.output_call = call
|
|
95
|
+
|
|
51
96
|
|
|
52
97
|
@dataclass
|
|
53
|
-
class AgentStream(Generic[AgentDepsT,
|
|
98
|
+
class AgentStream(Generic[AgentDepsT, OutputDataT]):
|
|
54
99
|
_raw_stream_response: models.StreamedResponse
|
|
55
|
-
|
|
56
|
-
|
|
100
|
+
_output_schema: _output.OutputSchema[OutputDataT] | None
|
|
101
|
+
_output_validators: list[_output.OutputValidator[AgentDepsT, OutputDataT]]
|
|
57
102
|
_run_ctx: RunContext[AgentDepsT]
|
|
58
103
|
_usage_limits: UsageLimits | None
|
|
59
104
|
|
|
@@ -64,7 +109,7 @@ class AgentStream(Generic[AgentDepsT, ResultDataT]):
|
|
|
64
109
|
def __post_init__(self):
|
|
65
110
|
self._initial_run_ctx_usage = copy(self._run_ctx.usage)
|
|
66
111
|
|
|
67
|
-
async def stream_output(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[
|
|
112
|
+
async def stream_output(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]:
|
|
68
113
|
"""Asynchronously stream the (validated) agent outputs."""
|
|
69
114
|
async for response in self.stream_responses(debounce_by=debounce_by):
|
|
70
115
|
if self._final_result_event is not None:
|
|
@@ -96,32 +141,32 @@ class AgentStream(Generic[AgentDepsT, ResultDataT]):
|
|
|
96
141
|
return self._initial_run_ctx_usage + self._raw_stream_response.usage()
|
|
97
142
|
|
|
98
143
|
async def _validate_response(
|
|
99
|
-
self, message: _messages.ModelResponse,
|
|
100
|
-
) ->
|
|
144
|
+
self, message: _messages.ModelResponse, output_tool_name: str | None, *, allow_partial: bool = False
|
|
145
|
+
) -> OutputDataT:
|
|
101
146
|
"""Validate a structured result message."""
|
|
102
|
-
if self.
|
|
103
|
-
match = self.
|
|
147
|
+
if self._output_schema is not None and output_tool_name is not None:
|
|
148
|
+
match = self._output_schema.find_named_tool(message.parts, output_tool_name)
|
|
104
149
|
if match is None:
|
|
105
150
|
raise exceptions.UnexpectedModelBehavior(
|
|
106
|
-
f'Invalid response, unable to find tool: {self.
|
|
151
|
+
f'Invalid response, unable to find tool: {self._output_schema.tool_names()}'
|
|
107
152
|
)
|
|
108
153
|
|
|
109
|
-
call,
|
|
110
|
-
result_data =
|
|
154
|
+
call, output_tool = match
|
|
155
|
+
result_data = output_tool.validate(call, allow_partial=allow_partial, wrap_validation_errors=False)
|
|
111
156
|
|
|
112
|
-
for validator in self.
|
|
157
|
+
for validator in self._output_validators:
|
|
113
158
|
result_data = await validator.validate(result_data, call, self._run_ctx)
|
|
114
159
|
return result_data
|
|
115
160
|
else:
|
|
116
161
|
text = '\n\n'.join(x.content for x in message.parts if isinstance(x, _messages.TextPart))
|
|
117
|
-
for validator in self.
|
|
162
|
+
for validator in self._output_validators:
|
|
118
163
|
text = await validator.validate(
|
|
119
164
|
text,
|
|
120
165
|
None,
|
|
121
166
|
self._run_ctx,
|
|
122
167
|
)
|
|
123
|
-
# Since there is no
|
|
124
|
-
return cast(
|
|
168
|
+
# Since there is no output tool, we can assume that str is compatible with OutputDataT
|
|
169
|
+
return cast(OutputDataT, text)
|
|
125
170
|
|
|
126
171
|
def __aiter__(self) -> AsyncIterator[AgentStreamEvent]:
|
|
127
172
|
"""Stream [`AgentStreamEvent`][pydantic_ai.messages.AgentStreamEvent]s.
|
|
@@ -134,20 +179,20 @@ class AgentStream(Generic[AgentDepsT, ResultDataT]):
|
|
|
134
179
|
return self._agent_stream_iterator
|
|
135
180
|
|
|
136
181
|
async def aiter():
|
|
137
|
-
|
|
138
|
-
|
|
182
|
+
output_schema = self._output_schema
|
|
183
|
+
allow_text_output = output_schema is None or output_schema.allow_text_output
|
|
139
184
|
|
|
140
185
|
def _get_final_result_event(e: _messages.ModelResponseStreamEvent) -> _messages.FinalResultEvent | None:
|
|
141
186
|
"""Return an appropriate FinalResultEvent if `e` corresponds to a part that will produce a final result."""
|
|
142
187
|
if isinstance(e, _messages.PartStartEvent):
|
|
143
188
|
new_part = e.part
|
|
144
189
|
if isinstance(new_part, _messages.ToolCallPart):
|
|
145
|
-
if
|
|
146
|
-
for call, _ in
|
|
190
|
+
if output_schema:
|
|
191
|
+
for call, _ in output_schema.find_tool([new_part]):
|
|
147
192
|
return _messages.FinalResultEvent(
|
|
148
193
|
tool_name=call.tool_name, tool_call_id=call.tool_call_id
|
|
149
194
|
)
|
|
150
|
-
elif
|
|
195
|
+
elif allow_text_output:
|
|
151
196
|
assert_type(e, _messages.PartStartEvent)
|
|
152
197
|
return _messages.FinalResultEvent(tool_name=None, tool_call_id=None)
|
|
153
198
|
|
|
@@ -171,7 +216,7 @@ class AgentStream(Generic[AgentDepsT, ResultDataT]):
|
|
|
171
216
|
|
|
172
217
|
|
|
173
218
|
@dataclass
|
|
174
|
-
class StreamedRunResult(Generic[AgentDepsT,
|
|
219
|
+
class StreamedRunResult(Generic[AgentDepsT, OutputDataT]):
|
|
175
220
|
"""Result of a streamed run that returns structured data via a tool call."""
|
|
176
221
|
|
|
177
222
|
_all_messages: list[_messages.ModelMessage]
|
|
@@ -179,10 +224,10 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
179
224
|
|
|
180
225
|
_usage_limits: UsageLimits | None
|
|
181
226
|
_stream_response: models.StreamedResponse
|
|
182
|
-
|
|
227
|
+
_output_schema: _output.OutputSchema[OutputDataT] | None
|
|
183
228
|
_run_ctx: RunContext[AgentDepsT]
|
|
184
|
-
|
|
185
|
-
|
|
229
|
+
_output_validators: list[_output.OutputValidator[AgentDepsT, OutputDataT]]
|
|
230
|
+
_output_tool_name: str | None
|
|
186
231
|
_on_complete: Callable[[], Awaitable[None]]
|
|
187
232
|
|
|
188
233
|
_initial_run_ctx_usage: Usage = field(init=False)
|
|
@@ -193,78 +238,118 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
193
238
|
[`stream`][pydantic_ai.result.StreamedRunResult.stream],
|
|
194
239
|
[`stream_text`][pydantic_ai.result.StreamedRunResult.stream_text],
|
|
195
240
|
[`stream_structured`][pydantic_ai.result.StreamedRunResult.stream_structured] or
|
|
196
|
-
[`
|
|
241
|
+
[`get_output`][pydantic_ai.result.StreamedRunResult.get_output] completes.
|
|
197
242
|
"""
|
|
198
243
|
|
|
199
244
|
def __post_init__(self):
|
|
200
245
|
self._initial_run_ctx_usage = copy(self._run_ctx.usage)
|
|
201
246
|
|
|
202
|
-
|
|
247
|
+
@overload
|
|
248
|
+
def all_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]: ...
|
|
249
|
+
|
|
250
|
+
@overload
|
|
251
|
+
@deprecated('`result_tool_return_content` is deprecated, use `output_tool_return_content` instead.')
|
|
252
|
+
def all_messages(self, *, result_tool_return_content: str | None = None) -> list[_messages.ModelMessage]: ...
|
|
253
|
+
|
|
254
|
+
def all_messages(
|
|
255
|
+
self, *, output_tool_return_content: str | None = None, result_tool_return_content: str | None = None
|
|
256
|
+
) -> list[_messages.ModelMessage]:
|
|
203
257
|
"""Return the history of _messages.
|
|
204
258
|
|
|
205
259
|
Args:
|
|
206
|
-
|
|
207
|
-
This provides a convenient way to modify the content of the
|
|
208
|
-
the conversation and want to set the response to the
|
|
260
|
+
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
261
|
+
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
262
|
+
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
209
263
|
not be modified.
|
|
264
|
+
result_tool_return_content: deprecated, use `output_tool_return_content` instead.
|
|
210
265
|
|
|
211
266
|
Returns:
|
|
212
267
|
List of messages.
|
|
213
268
|
"""
|
|
214
269
|
# this is a method to be consistent with the other methods
|
|
215
|
-
|
|
216
|
-
|
|
270
|
+
content = coalesce_deprecated_return_content(output_tool_return_content, result_tool_return_content)
|
|
271
|
+
if content is not None:
|
|
272
|
+
raise NotImplementedError('Setting output tool return content is not supported for this result type.')
|
|
217
273
|
return self._all_messages
|
|
218
274
|
|
|
219
|
-
|
|
275
|
+
@overload
|
|
276
|
+
def all_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes: ...
|
|
277
|
+
|
|
278
|
+
@overload
|
|
279
|
+
@deprecated('`result_tool_return_content` is deprecated, use `output_tool_return_content` instead.')
|
|
280
|
+
def all_messages_json(self, *, result_tool_return_content: str | None = None) -> bytes: ...
|
|
281
|
+
|
|
282
|
+
def all_messages_json(
|
|
283
|
+
self, *, output_tool_return_content: str | None = None, result_tool_return_content: str | None = None
|
|
284
|
+
) -> bytes: # pragma: no cover
|
|
220
285
|
"""Return all messages from [`all_messages`][pydantic_ai.result.StreamedRunResult.all_messages] as JSON bytes.
|
|
221
286
|
|
|
222
287
|
Args:
|
|
223
|
-
|
|
224
|
-
This provides a convenient way to modify the content of the
|
|
225
|
-
the conversation and want to set the response to the
|
|
288
|
+
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
289
|
+
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
290
|
+
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
226
291
|
not be modified.
|
|
292
|
+
result_tool_return_content: deprecated, use `output_tool_return_content` instead.
|
|
227
293
|
|
|
228
294
|
Returns:
|
|
229
295
|
JSON bytes representing the messages.
|
|
230
296
|
"""
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
297
|
+
content = coalesce_deprecated_return_content(output_tool_return_content, result_tool_return_content)
|
|
298
|
+
return _messages.ModelMessagesTypeAdapter.dump_json(self.all_messages(output_tool_return_content=content))
|
|
299
|
+
|
|
300
|
+
@overload
|
|
301
|
+
def new_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]: ...
|
|
234
302
|
|
|
235
|
-
|
|
303
|
+
@overload
|
|
304
|
+
@deprecated('`result_tool_return_content` is deprecated, use `output_tool_return_content` instead.')
|
|
305
|
+
def new_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]: ...
|
|
306
|
+
|
|
307
|
+
def new_messages(
|
|
308
|
+
self, *, output_tool_return_content: str | None = None, result_tool_return_content: str | None = None
|
|
309
|
+
) -> list[_messages.ModelMessage]: # pragma: no cover
|
|
236
310
|
"""Return new messages associated with this run.
|
|
237
311
|
|
|
238
312
|
Messages from older runs are excluded.
|
|
239
313
|
|
|
240
314
|
Args:
|
|
241
|
-
|
|
242
|
-
This provides a convenient way to modify the content of the
|
|
243
|
-
the conversation and want to set the response to the
|
|
315
|
+
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
316
|
+
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
317
|
+
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
244
318
|
not be modified.
|
|
319
|
+
result_tool_return_content: deprecated, use `output_tool_return_content` instead.
|
|
245
320
|
|
|
246
321
|
Returns:
|
|
247
322
|
List of new messages.
|
|
248
323
|
"""
|
|
249
|
-
|
|
324
|
+
content = coalesce_deprecated_return_content(output_tool_return_content, result_tool_return_content)
|
|
325
|
+
return self.all_messages(output_tool_return_content=content)[self._new_message_index :]
|
|
326
|
+
|
|
327
|
+
@overload
|
|
328
|
+
def new_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes: ...
|
|
250
329
|
|
|
251
|
-
|
|
330
|
+
@overload
|
|
331
|
+
@deprecated('`result_tool_return_content` is deprecated, use `output_tool_return_content` instead.')
|
|
332
|
+
def new_messages_json(self, *, result_tool_return_content: str | None = None) -> bytes: ...
|
|
333
|
+
|
|
334
|
+
def new_messages_json(
|
|
335
|
+
self, *, output_tool_return_content: str | None = None, result_tool_return_content: str | None = None
|
|
336
|
+
) -> bytes: # pragma: no cover
|
|
252
337
|
"""Return new messages from [`new_messages`][pydantic_ai.result.StreamedRunResult.new_messages] as JSON bytes.
|
|
253
338
|
|
|
254
339
|
Args:
|
|
255
|
-
|
|
256
|
-
This provides a convenient way to modify the content of the
|
|
257
|
-
the conversation and want to set the response to the
|
|
340
|
+
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
341
|
+
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
342
|
+
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
258
343
|
not be modified.
|
|
344
|
+
result_tool_return_content: deprecated, use `output_tool_return_content` instead.
|
|
259
345
|
|
|
260
346
|
Returns:
|
|
261
347
|
JSON bytes representing the new messages.
|
|
262
348
|
"""
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
)
|
|
349
|
+
content = coalesce_deprecated_return_content(output_tool_return_content, result_tool_return_content)
|
|
350
|
+
return _messages.ModelMessagesTypeAdapter.dump_json(self.new_messages(output_tool_return_content=content))
|
|
266
351
|
|
|
267
|
-
async def stream(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[
|
|
352
|
+
async def stream(self, *, debounce_by: float | None = 0.1) -> AsyncIterator[OutputDataT]:
|
|
268
353
|
"""Stream the response as an async iterable.
|
|
269
354
|
|
|
270
355
|
The pydantic validator for structured data will be called in
|
|
@@ -280,8 +365,7 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
280
365
|
An async iterable of the response data.
|
|
281
366
|
"""
|
|
282
367
|
async for structured_message, is_last in self.stream_structured(debounce_by=debounce_by):
|
|
283
|
-
|
|
284
|
-
yield result
|
|
368
|
+
yield await self.validate_structured_output(structured_message, allow_partial=not is_last)
|
|
285
369
|
|
|
286
370
|
async def stream_text(self, *, delta: bool = False, debounce_by: float | None = 0.1) -> AsyncIterator[str]:
|
|
287
371
|
"""Stream the text result as an async iterable.
|
|
@@ -296,7 +380,7 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
296
380
|
Debouncing is particularly important for long structured responses to reduce the overhead of
|
|
297
381
|
performing validation as each token is received.
|
|
298
382
|
"""
|
|
299
|
-
if self.
|
|
383
|
+
if self._output_schema and not self._output_schema.allow_text_output:
|
|
300
384
|
raise exceptions.UserError('stream_text() can only be used with text responses')
|
|
301
385
|
|
|
302
386
|
if delta:
|
|
@@ -304,7 +388,7 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
304
388
|
yield text
|
|
305
389
|
else:
|
|
306
390
|
async for text in self._stream_response_text(delta=delta, debounce_by=debounce_by):
|
|
307
|
-
combined_validated_text = await self.
|
|
391
|
+
combined_validated_text = await self._validate_text_output(text)
|
|
308
392
|
yield combined_validated_text
|
|
309
393
|
await self._marked_completed(self._stream_response.get())
|
|
310
394
|
|
|
@@ -336,7 +420,7 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
336
420
|
|
|
337
421
|
await self._marked_completed(msg)
|
|
338
422
|
|
|
339
|
-
async def
|
|
423
|
+
async def get_output(self) -> OutputDataT:
|
|
340
424
|
"""Stream the whole response, validate and return it."""
|
|
341
425
|
usage_checking_stream = _get_usage_checking_stream_response(
|
|
342
426
|
self._stream_response, self._usage_limits, self.usage
|
|
@@ -346,7 +430,11 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
346
430
|
pass
|
|
347
431
|
message = self._stream_response.get()
|
|
348
432
|
await self._marked_completed(message)
|
|
349
|
-
return await self.
|
|
433
|
+
return await self.validate_structured_output(message)
|
|
434
|
+
|
|
435
|
+
@deprecated('`get_data` is deprecated, use `get_output` instead.')
|
|
436
|
+
async def get_data(self) -> OutputDataT:
|
|
437
|
+
return await self.get_output()
|
|
350
438
|
|
|
351
439
|
def usage(self) -> Usage:
|
|
352
440
|
"""Return the usage of the whole run.
|
|
@@ -360,36 +448,42 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
360
448
|
"""Get the timestamp of the response."""
|
|
361
449
|
return self._stream_response.timestamp
|
|
362
450
|
|
|
451
|
+
@deprecated('`validate_structured_result` is deprecated, use `validate_structured_output` instead.')
|
|
363
452
|
async def validate_structured_result(
|
|
364
453
|
self, message: _messages.ModelResponse, *, allow_partial: bool = False
|
|
365
|
-
) ->
|
|
454
|
+
) -> OutputDataT:
|
|
455
|
+
return await self.validate_structured_output(message, allow_partial=allow_partial)
|
|
456
|
+
|
|
457
|
+
async def validate_structured_output(
|
|
458
|
+
self, message: _messages.ModelResponse, *, allow_partial: bool = False
|
|
459
|
+
) -> OutputDataT:
|
|
366
460
|
"""Validate a structured result message."""
|
|
367
|
-
if self.
|
|
368
|
-
match = self.
|
|
461
|
+
if self._output_schema is not None and self._output_tool_name is not None:
|
|
462
|
+
match = self._output_schema.find_named_tool(message.parts, self._output_tool_name)
|
|
369
463
|
if match is None:
|
|
370
464
|
raise exceptions.UnexpectedModelBehavior(
|
|
371
|
-
f'Invalid response, unable to find tool: {self.
|
|
465
|
+
f'Invalid response, unable to find tool: {self._output_schema.tool_names()}'
|
|
372
466
|
)
|
|
373
467
|
|
|
374
|
-
call,
|
|
375
|
-
result_data =
|
|
468
|
+
call, output_tool = match
|
|
469
|
+
result_data = output_tool.validate(call, allow_partial=allow_partial, wrap_validation_errors=False)
|
|
376
470
|
|
|
377
|
-
for validator in self.
|
|
471
|
+
for validator in self._output_validators:
|
|
378
472
|
result_data = await validator.validate(result_data, call, self._run_ctx)
|
|
379
473
|
return result_data
|
|
380
474
|
else:
|
|
381
475
|
text = '\n\n'.join(x.content for x in message.parts if isinstance(x, _messages.TextPart))
|
|
382
|
-
for validator in self.
|
|
476
|
+
for validator in self._output_validators:
|
|
383
477
|
text = await validator.validate(
|
|
384
478
|
text,
|
|
385
479
|
None,
|
|
386
480
|
self._run_ctx,
|
|
387
481
|
)
|
|
388
|
-
# Since there is no
|
|
389
|
-
return cast(
|
|
482
|
+
# Since there is no output tool, we can assume that str is compatible with OutputDataT
|
|
483
|
+
return cast(OutputDataT, text)
|
|
390
484
|
|
|
391
|
-
async def
|
|
392
|
-
for validator in self.
|
|
485
|
+
async def _validate_text_output(self, text: str) -> str:
|
|
486
|
+
for validator in self._output_validators:
|
|
393
487
|
text = await validator.validate(
|
|
394
488
|
text,
|
|
395
489
|
None,
|
|
@@ -459,15 +553,20 @@ class StreamedRunResult(Generic[AgentDepsT, ResultDataT]):
|
|
|
459
553
|
|
|
460
554
|
|
|
461
555
|
@dataclass
|
|
462
|
-
class FinalResult(Generic[
|
|
463
|
-
"""Marker class storing the final
|
|
556
|
+
class FinalResult(Generic[OutputDataT]):
|
|
557
|
+
"""Marker class storing the final output of an agent run and associated metadata."""
|
|
464
558
|
|
|
465
|
-
|
|
559
|
+
output: OutputDataT
|
|
466
560
|
"""The final result data."""
|
|
467
561
|
tool_name: str | None
|
|
468
|
-
"""Name of the final
|
|
562
|
+
"""Name of the final output tool; `None` if the output came from unstructured text content."""
|
|
469
563
|
tool_call_id: str | None
|
|
470
|
-
"""ID of the tool call that produced the final
|
|
564
|
+
"""ID of the tool call that produced the final output; `None` if the output came from unstructured text content."""
|
|
565
|
+
|
|
566
|
+
@property
|
|
567
|
+
@deprecated('`data` is deprecated, use `output` instead.')
|
|
568
|
+
def data(self) -> OutputDataT:
|
|
569
|
+
return self.output
|
|
471
570
|
|
|
472
571
|
|
|
473
572
|
def _get_usage_checking_stream_response(
|
|
@@ -485,3 +584,17 @@ def _get_usage_checking_stream_response(
|
|
|
485
584
|
return _usage_checking_iterator()
|
|
486
585
|
else:
|
|
487
586
|
return stream_response
|
|
587
|
+
|
|
588
|
+
|
|
589
|
+
def coalesce_deprecated_return_content(
|
|
590
|
+
output_tool_return_content: T | None, result_tool_return_content: T | None
|
|
591
|
+
) -> T | None:
|
|
592
|
+
"""Return the first non-None value."""
|
|
593
|
+
if output_tool_return_content is None:
|
|
594
|
+
if result_tool_return_content is not None: # pragma: no cover
|
|
595
|
+
warnings.warn(
|
|
596
|
+
'`result_tool_return_content` is deprecated, use `output_tool_return_content` instead.',
|
|
597
|
+
DeprecationWarning,
|
|
598
|
+
)
|
|
599
|
+
return result_tool_return_content
|
|
600
|
+
return output_tool_return_content
|
pydantic_ai/tools.py
CHANGED
|
@@ -411,7 +411,7 @@ With PEP-728 this should be a TypedDict with `type: Literal['object']`, and `ext
|
|
|
411
411
|
class ToolDefinition:
|
|
412
412
|
"""Definition of a tool passed to a model.
|
|
413
413
|
|
|
414
|
-
This is used for both function tools and
|
|
414
|
+
This is used for both function tools and output tools.
|
|
415
415
|
"""
|
|
416
416
|
|
|
417
417
|
name: str
|
|
@@ -424,9 +424,9 @@ class ToolDefinition:
|
|
|
424
424
|
"""The JSON schema for the tool's parameters."""
|
|
425
425
|
|
|
426
426
|
outer_typed_dict_key: str | None = None
|
|
427
|
-
"""The key in the outer [TypedDict] that wraps
|
|
427
|
+
"""The key in the outer [TypedDict] that wraps an output tool.
|
|
428
428
|
|
|
429
|
-
This will only be set for
|
|
429
|
+
This will only be set for output tools which don't have an `object` JSON schema.
|
|
430
430
|
"""
|
|
431
431
|
|
|
432
432
|
strict: bool | None = None
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pydantic-ai-slim
|
|
3
|
-
Version: 0.0
|
|
3
|
+
Version: 0.1.0
|
|
4
4
|
Summary: Agent Framework / shim to use Pydantic with LLMs, slim package
|
|
5
5
|
Author-email: Samuel Colvin <samuel@pydantic.dev>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -29,13 +29,13 @@ Requires-Dist: exceptiongroup; python_version < '3.11'
|
|
|
29
29
|
Requires-Dist: griffe>=1.3.2
|
|
30
30
|
Requires-Dist: httpx>=0.27
|
|
31
31
|
Requires-Dist: opentelemetry-api>=1.28.0
|
|
32
|
-
Requires-Dist: pydantic-graph==0.0
|
|
32
|
+
Requires-Dist: pydantic-graph==0.1.0
|
|
33
33
|
Requires-Dist: pydantic>=2.10
|
|
34
34
|
Requires-Dist: typing-inspection>=0.4.0
|
|
35
35
|
Provides-Extra: anthropic
|
|
36
36
|
Requires-Dist: anthropic>=0.49.0; extra == 'anthropic'
|
|
37
37
|
Provides-Extra: bedrock
|
|
38
|
-
Requires-Dist: boto3>=1.
|
|
38
|
+
Requires-Dist: boto3>=1.35.74; extra == 'bedrock'
|
|
39
39
|
Provides-Extra: cli
|
|
40
40
|
Requires-Dist: argcomplete>=3.5.0; extra == 'cli'
|
|
41
41
|
Requires-Dist: prompt-toolkit>=3; extra == 'cli'
|
|
@@ -45,7 +45,7 @@ Requires-Dist: cohere>=5.13.11; (platform_system != 'Emscripten') and extra == '
|
|
|
45
45
|
Provides-Extra: duckduckgo
|
|
46
46
|
Requires-Dist: duckduckgo-search>=7.0.0; extra == 'duckduckgo'
|
|
47
47
|
Provides-Extra: evals
|
|
48
|
-
Requires-Dist: pydantic-evals==0.0
|
|
48
|
+
Requires-Dist: pydantic-evals==0.1.0; extra == 'evals'
|
|
49
49
|
Provides-Extra: groq
|
|
50
50
|
Requires-Dist: groq>=0.15.0; extra == 'groq'
|
|
51
51
|
Provides-Extra: logfire
|
|
@@ -55,7 +55,7 @@ Requires-Dist: mcp>=1.4.1; (python_version >= '3.10') and extra == 'mcp'
|
|
|
55
55
|
Provides-Extra: mistral
|
|
56
56
|
Requires-Dist: mistralai>=1.2.5; extra == 'mistral'
|
|
57
57
|
Provides-Extra: openai
|
|
58
|
-
Requires-Dist: openai>=1.
|
|
58
|
+
Requires-Dist: openai>=1.74.0; extra == 'openai'
|
|
59
59
|
Provides-Extra: tavily
|
|
60
60
|
Requires-Dist: tavily-python>=0.5.0; extra == 'tavily'
|
|
61
61
|
Provides-Extra: vertexai
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
pydantic_ai/__init__.py,sha256=5flxyMQJVrHRMQ3MYaZf1el2ctNs0JmPClKbw2Q-Lsk,1160
|
|
2
|
+
pydantic_ai/__main__.py,sha256=AW8FzscUWPFtIrQBG0QExLxTQehKtt5FnFVnOT200OE,122
|
|
3
|
+
pydantic_ai/_agent_graph.py,sha256=9REW7Al14aL34Z6iL8n8wvxXVBSH_dliU4Yl5ExlJK4,33308
|
|
4
|
+
pydantic_ai/_cli.py,sha256=j3uSu5lZMNKb876HHMHwVZc1nzJPn6NER5ysrDMFrvo,10730
|
|
5
|
+
pydantic_ai/_griffe.py,sha256=Sf_DisE9k2TA0VFeVIK2nf1oOct5MygW86PBCACJkFA,5244
|
|
6
|
+
pydantic_ai/_output.py,sha256=w_kBc5Lx5AmI0APbohxxYgpFd5VAwh6K0IjP7QIOu9U,11209
|
|
7
|
+
pydantic_ai/_parts_manager.py,sha256=HIi6eth7z2g0tOn6iQYc633xMqy4d_xZ8vwka8J8150,12016
|
|
8
|
+
pydantic_ai/_pydantic.py,sha256=12hX5hON88meO1QxbWrEPXSvr6RTNgr6ubKY6KRwab4,8890
|
|
9
|
+
pydantic_ai/_system_prompt.py,sha256=602c2jyle2R_SesOrITBDETZqsLk4BZ8Cbo8yEhmx04,1120
|
|
10
|
+
pydantic_ai/_utils.py,sha256=t0P9DFX3MulMyoqyZDRinmLCOB1q6rvRnMRqQP2MUpI,10585
|
|
11
|
+
pydantic_ai/agent.py,sha256=jq8ZPlsM-P3gqP-R59W47eA9xKNep0CyiF5_rpQ6ffc,86354
|
|
12
|
+
pydantic_ai/exceptions.py,sha256=gvbFsFkAzSXOo_d1nfjy09kDHUGv1j5q70Uk-wKYGi8,3167
|
|
13
|
+
pydantic_ai/format_as_xml.py,sha256=IINfh1evWDphGahqHNLBArB5dQ4NIqS3S-kru35ztGg,372
|
|
14
|
+
pydantic_ai/format_prompt.py,sha256=qdKep95Sjlr7u1-qag4JwPbjoURbG0GbeU_l5ODTNw4,4466
|
|
15
|
+
pydantic_ai/mcp.py,sha256=wlu3GCdcjgsag75cyuTron1kdTQOWkM0j5hNJxf0JkE,8242
|
|
16
|
+
pydantic_ai/messages.py,sha256=TUfs1AsogZu1YkoJjKwjbKCcaaDg-0gcbIj_wVLUW0E,29748
|
|
17
|
+
pydantic_ai/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
+
pydantic_ai/result.py,sha256=DgoUd0LqNd9DWPab6iwculKYvZ5JZHuGvToj0kkibvs,27625
|
|
19
|
+
pydantic_ai/settings.py,sha256=RMQxjsJqyiExxtxCr_L3avojAiANdFE0ME1eitLrjhk,3242
|
|
20
|
+
pydantic_ai/tools.py,sha256=YIdB1CuyoBcd2oG9NvL66nwpCBR3ii6I4KqMoThHiAE,16915
|
|
21
|
+
pydantic_ai/usage.py,sha256=9sqoIv_RVVUhKXQScTDqUJc074gifsuSzc9_NOt7C3g,5394
|
|
22
|
+
pydantic_ai/common_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
pydantic_ai/common_tools/duckduckgo.py,sha256=Iw8Dl2YQ28S483mzfa8CXs-dc-ujS8un085R2O6oOEw,2241
|
|
24
|
+
pydantic_ai/common_tools/tavily.py,sha256=h8deBDrpG-8BGzydM_zXs7z1ASrhdVvUxL4-CAbncBo,2589
|
|
25
|
+
pydantic_ai/models/__init__.py,sha256=R1YV2gE8l_EDlnmLhfxhdojF6gjGWMC14ocRrxiz5XM,19699
|
|
26
|
+
pydantic_ai/models/_json_schema.py,sha256=5xqysWdE1uGwrztVzhkOJ8uSKgKnONBe88004ufbWqM,6319
|
|
27
|
+
pydantic_ai/models/anthropic.py,sha256=gkeeIEqI0dk72EIL9zYRtvBd9ipDvT0v1-AcdMF_l20,20272
|
|
28
|
+
pydantic_ai/models/bedrock.py,sha256=KSmukt2jeKb6uABVR5BRz5Sm7YGYSh56NF0Ftzk11UI,24977
|
|
29
|
+
pydantic_ai/models/cohere.py,sha256=0clDIXPETo0pjfNgtI-sWqjjZWPkHqFalag_nN3HSNI,11685
|
|
30
|
+
pydantic_ai/models/fallback.py,sha256=sKHyQ1P6zjWORjWgbuhaxyntOfQyDCS8km8FMrlNy3U,4998
|
|
31
|
+
pydantic_ai/models/function.py,sha256=FJvTMwT7p8I_h14ZrudLAI5mmbXxF8AX-Nhz8LXy5U0,11373
|
|
32
|
+
pydantic_ai/models/gemini.py,sha256=CdhlEpyyDUSEfbx82juJxP0Us0GotNnCQ-0W-RfQD6o,34330
|
|
33
|
+
pydantic_ai/models/groq.py,sha256=UNyZPqmHRZ9LgtMhV5f9QI1txZ7TCMyu8Abn1ojk5lE,17034
|
|
34
|
+
pydantic_ai/models/instrumented.py,sha256=_5bV2kkwCix5C25cCAblbhZy1wiKaYBUStmHso4T22g,11163
|
|
35
|
+
pydantic_ai/models/mistral.py,sha256=sNujNLxpPFZA_vrh-74rDRl2syE1QTgILm6hMZNR4SI,28226
|
|
36
|
+
pydantic_ai/models/openai.py,sha256=tfOOtShuPVrxgwUIg4p59Sak6gC-TEpHyEd2idrvDuc,45759
|
|
37
|
+
pydantic_ai/models/test.py,sha256=_Fd7oKNA2p_1zXBMvQStnizGlx-ii-zisJx1nIEZn7c,16973
|
|
38
|
+
pydantic_ai/models/wrapper.py,sha256=8wm4RF-MRZOxRVLefwMsxToopCX5Y4Xq2-Ugs5MtCK4,1710
|
|
39
|
+
pydantic_ai/providers/__init__.py,sha256=lLlHq6B8qmu6Ag5biaZmJGDKELO46KjwP7-CDrz_T4Y,2592
|
|
40
|
+
pydantic_ai/providers/anthropic.py,sha256=0WzWEDseBaJ5eyEatvnDXBtDZKA9-od4BuPZn9NoTPw,2812
|
|
41
|
+
pydantic_ai/providers/azure.py,sha256=2tAE-bLjXY-DvVrVc4ilQe15HhoHP9neAbvwCaCx_uo,4225
|
|
42
|
+
pydantic_ai/providers/bedrock.py,sha256=BV1Zi4asU4Bmcv4t7VRIy2U44Tk_Jrf26x8_mPJiYHQ,3216
|
|
43
|
+
pydantic_ai/providers/cohere.py,sha256=WOFZCllgVbWciF4nNkG3pCqw4poy57VEGyux2mVntbQ,2667
|
|
44
|
+
pydantic_ai/providers/deepseek.py,sha256=_5JPzDGWsyVyTBX-yYYdy5aZwUOWNCVgoWI-UoBamms,2193
|
|
45
|
+
pydantic_ai/providers/google_gla.py,sha256=MJM7aRZRdP4kFlNg0ZHgC95O0wH02OQgbNiDQeK9fZo,1600
|
|
46
|
+
pydantic_ai/providers/google_vertex.py,sha256=WAwPxKTARVzs8DFs2veEUOJSur0krDOo9-JWRHvfHew,9135
|
|
47
|
+
pydantic_ai/providers/groq.py,sha256=DoY6qkfhuemuKB5JXhUkqG-3t1HQkxwSXoE_kHQIAK0,2788
|
|
48
|
+
pydantic_ai/providers/mistral.py,sha256=fcR1uSwORo0jtevX7-wOjvcfT8ojMAaKY81uN5uYymM,2661
|
|
49
|
+
pydantic_ai/providers/openai.py,sha256=ePF-QWwLkGkSE5w245gTTDVR3VoTIUqFoIhQ0TAoUiA,2866
|
|
50
|
+
pydantic_ai_slim-0.1.0.dist-info/METADATA,sha256=4R4qDPDb8_150bXpKlHbSyO_uIrQVFOud2BgWCLS61k,3551
|
|
51
|
+
pydantic_ai_slim-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
52
|
+
pydantic_ai_slim-0.1.0.dist-info/entry_points.txt,sha256=KxQSmlMS8GMTkwTsl4_q9a5nJvBjj3HWeXx688wLrKg,45
|
|
53
|
+
pydantic_ai_slim-0.1.0.dist-info/RECORD,,
|