pydantic-ai-slim 0.6.2__py3-none-any.whl → 0.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pydantic-ai-slim might be problematic. Click here for more details.
- pydantic_ai/_a2a.py +6 -4
- pydantic_ai/_agent_graph.py +25 -32
- pydantic_ai/_cli.py +3 -3
- pydantic_ai/_output.py +8 -0
- pydantic_ai/_tool_manager.py +3 -0
- pydantic_ai/ag_ui.py +25 -14
- pydantic_ai/{agent.py → agent/__init__.py} +209 -1027
- pydantic_ai/agent/abstract.py +942 -0
- pydantic_ai/agent/wrapper.py +227 -0
- pydantic_ai/direct.py +9 -9
- pydantic_ai/durable_exec/__init__.py +0 -0
- pydantic_ai/durable_exec/temporal/__init__.py +83 -0
- pydantic_ai/durable_exec/temporal/_agent.py +699 -0
- pydantic_ai/durable_exec/temporal/_function_toolset.py +92 -0
- pydantic_ai/durable_exec/temporal/_logfire.py +48 -0
- pydantic_ai/durable_exec/temporal/_mcp_server.py +145 -0
- pydantic_ai/durable_exec/temporal/_model.py +168 -0
- pydantic_ai/durable_exec/temporal/_run_context.py +50 -0
- pydantic_ai/durable_exec/temporal/_toolset.py +77 -0
- pydantic_ai/ext/aci.py +10 -9
- pydantic_ai/ext/langchain.py +4 -2
- pydantic_ai/mcp.py +203 -75
- pydantic_ai/messages.py +2 -2
- pydantic_ai/models/__init__.py +65 -9
- pydantic_ai/models/anthropic.py +16 -7
- pydantic_ai/models/bedrock.py +8 -5
- pydantic_ai/models/cohere.py +1 -4
- pydantic_ai/models/fallback.py +4 -2
- pydantic_ai/models/function.py +9 -4
- pydantic_ai/models/gemini.py +15 -9
- pydantic_ai/models/google.py +18 -14
- pydantic_ai/models/groq.py +17 -14
- pydantic_ai/models/huggingface.py +18 -12
- pydantic_ai/models/instrumented.py +3 -1
- pydantic_ai/models/mcp_sampling.py +3 -1
- pydantic_ai/models/mistral.py +12 -18
- pydantic_ai/models/openai.py +29 -26
- pydantic_ai/models/test.py +3 -0
- pydantic_ai/models/wrapper.py +6 -2
- pydantic_ai/profiles/openai.py +1 -1
- pydantic_ai/providers/google.py +7 -7
- pydantic_ai/result.py +21 -55
- pydantic_ai/run.py +357 -0
- pydantic_ai/tools.py +0 -1
- pydantic_ai/toolsets/__init__.py +2 -0
- pydantic_ai/toolsets/_dynamic.py +87 -0
- pydantic_ai/toolsets/abstract.py +23 -3
- pydantic_ai/toolsets/combined.py +19 -4
- pydantic_ai/toolsets/deferred.py +10 -2
- pydantic_ai/toolsets/function.py +23 -8
- pydantic_ai/toolsets/prefixed.py +4 -0
- pydantic_ai/toolsets/wrapper.py +14 -1
- {pydantic_ai_slim-0.6.2.dist-info → pydantic_ai_slim-0.7.0.dist-info}/METADATA +6 -4
- {pydantic_ai_slim-0.6.2.dist-info → pydantic_ai_slim-0.7.0.dist-info}/RECORD +57 -44
- {pydantic_ai_slim-0.6.2.dist-info → pydantic_ai_slim-0.7.0.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-0.6.2.dist-info → pydantic_ai_slim-0.7.0.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-0.6.2.dist-info → pydantic_ai_slim-0.7.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -5,22 +5,18 @@ import inspect
|
|
|
5
5
|
import json
|
|
6
6
|
import warnings
|
|
7
7
|
from asyncio import Lock
|
|
8
|
-
from collections.abc import AsyncIterator, Awaitable, Iterator,
|
|
8
|
+
from collections.abc import AsyncIterator, Awaitable, Iterator, Sequence
|
|
9
9
|
from contextlib import AbstractAsyncContextManager, AsyncExitStack, asynccontextmanager, contextmanager
|
|
10
10
|
from contextvars import ContextVar
|
|
11
|
-
from
|
|
12
|
-
from types import FrameType
|
|
13
|
-
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Generic, cast, final, overload
|
|
11
|
+
from typing import TYPE_CHECKING, Any, Callable, ClassVar, cast, overload
|
|
14
12
|
|
|
15
13
|
from opentelemetry.trace import NoOpTracer, use_span
|
|
16
14
|
from pydantic.json_schema import GenerateJsonSchema
|
|
17
|
-
from typing_extensions import
|
|
15
|
+
from typing_extensions import TypeVar, deprecated
|
|
18
16
|
|
|
19
|
-
from
|
|
20
|
-
from pydantic_graph import End, Graph, GraphRun, GraphRunContext
|
|
21
|
-
from pydantic_graph._utils import get_event_loop
|
|
17
|
+
from pydantic_graph import Graph
|
|
22
18
|
|
|
23
|
-
from
|
|
19
|
+
from .. import (
|
|
24
20
|
_agent_graph,
|
|
25
21
|
_output,
|
|
26
22
|
_system_prompt,
|
|
@@ -28,18 +24,19 @@ from . import (
|
|
|
28
24
|
exceptions,
|
|
29
25
|
messages as _messages,
|
|
30
26
|
models,
|
|
31
|
-
result,
|
|
32
27
|
usage as _usage,
|
|
33
28
|
)
|
|
34
|
-
from
|
|
35
|
-
from
|
|
36
|
-
from
|
|
37
|
-
from
|
|
38
|
-
from .
|
|
39
|
-
from
|
|
40
|
-
from
|
|
41
|
-
from
|
|
42
|
-
from
|
|
29
|
+
from .._agent_graph import HistoryProcessor
|
|
30
|
+
from .._output import OutputToolset
|
|
31
|
+
from .._tool_manager import ToolManager
|
|
32
|
+
from ..builtin_tools import AbstractBuiltinTool
|
|
33
|
+
from ..models.instrumented import InstrumentationSettings, InstrumentedModel, instrument_model
|
|
34
|
+
from ..output import OutputDataT, OutputSpec
|
|
35
|
+
from ..profiles import ModelProfile
|
|
36
|
+
from ..result import FinalResult
|
|
37
|
+
from ..run import AgentRun, AgentRunResult
|
|
38
|
+
from ..settings import ModelSettings, merge_model_settings
|
|
39
|
+
from ..tools import (
|
|
43
40
|
AgentDepsT,
|
|
44
41
|
DocstringFormat,
|
|
45
42
|
GenerateToolJsonSchema,
|
|
@@ -52,11 +49,16 @@ from .tools import (
|
|
|
52
49
|
ToolPrepareFunc,
|
|
53
50
|
ToolsPrepareFunc,
|
|
54
51
|
)
|
|
55
|
-
from
|
|
56
|
-
from
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
52
|
+
from ..toolsets import AbstractToolset
|
|
53
|
+
from ..toolsets._dynamic import (
|
|
54
|
+
DynamicToolset,
|
|
55
|
+
ToolsetFunc,
|
|
56
|
+
)
|
|
57
|
+
from ..toolsets.combined import CombinedToolset
|
|
58
|
+
from ..toolsets.function import FunctionToolset
|
|
59
|
+
from ..toolsets.prepared import PreparedToolset
|
|
60
|
+
from .abstract import AbstractAgent, EventStreamHandler, RunOutputDataT
|
|
61
|
+
from .wrapper import WrapperAgent
|
|
60
62
|
|
|
61
63
|
# Re-exporting like this improves auto-import behavior in PyCharm
|
|
62
64
|
capture_run_messages = _agent_graph.capture_run_messages
|
|
@@ -66,17 +68,7 @@ ModelRequestNode = _agent_graph.ModelRequestNode
|
|
|
66
68
|
UserPromptNode = _agent_graph.UserPromptNode
|
|
67
69
|
|
|
68
70
|
if TYPE_CHECKING:
|
|
69
|
-
from
|
|
70
|
-
from fasta2a.broker import Broker
|
|
71
|
-
from fasta2a.schema import AgentProvider, Skill
|
|
72
|
-
from fasta2a.storage import Storage
|
|
73
|
-
from starlette.middleware import Middleware
|
|
74
|
-
from starlette.routing import BaseRoute, Route
|
|
75
|
-
from starlette.types import ExceptionHandler, Lifespan
|
|
76
|
-
|
|
77
|
-
from pydantic_ai.mcp import MCPServer
|
|
78
|
-
|
|
79
|
-
from .ag_ui import AGUIApp
|
|
71
|
+
from ..mcp import MCPServer
|
|
80
72
|
|
|
81
73
|
__all__ = (
|
|
82
74
|
'Agent',
|
|
@@ -88,19 +80,19 @@ __all__ = (
|
|
|
88
80
|
'ModelRequestNode',
|
|
89
81
|
'UserPromptNode',
|
|
90
82
|
'InstrumentationSettings',
|
|
83
|
+
'WrapperAgent',
|
|
84
|
+
'AbstractAgent',
|
|
85
|
+
'EventStreamHandler',
|
|
91
86
|
)
|
|
92
87
|
|
|
93
88
|
|
|
94
89
|
T = TypeVar('T')
|
|
95
90
|
S = TypeVar('S')
|
|
96
91
|
NoneType = type(None)
|
|
97
|
-
RunOutputDataT = TypeVar('RunOutputDataT')
|
|
98
|
-
"""Type variable for the result data of a run where `output_type` was customized on the run call."""
|
|
99
92
|
|
|
100
93
|
|
|
101
|
-
@final
|
|
102
94
|
@dataclasses.dataclass(init=False)
|
|
103
|
-
class Agent(
|
|
95
|
+
class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
104
96
|
"""Class for defining "agents" - a way to have a specific type of "conversation" with an LLM.
|
|
105
97
|
|
|
106
98
|
Agents are generic in the dependency type they take [`AgentDepsT`][pydantic_ai.tools.AgentDepsT]
|
|
@@ -116,21 +108,13 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
116
108
|
agent = Agent('openai:gpt-4o')
|
|
117
109
|
result = agent.run_sync('What is the capital of France?')
|
|
118
110
|
print(result.output)
|
|
119
|
-
#> Paris
|
|
111
|
+
#> The capital of France is Paris.
|
|
120
112
|
```
|
|
121
113
|
"""
|
|
122
114
|
|
|
123
|
-
|
|
124
|
-
"""The default model configured for this agent.
|
|
125
|
-
|
|
126
|
-
We allow `str` here since the actual list of allowed models changes frequently.
|
|
127
|
-
"""
|
|
128
|
-
|
|
129
|
-
name: str | None
|
|
130
|
-
"""The name of the agent, used for logging.
|
|
115
|
+
_model: models.Model | models.KnownModelName | str | None
|
|
131
116
|
|
|
132
|
-
|
|
133
|
-
"""
|
|
117
|
+
_name: str | None
|
|
134
118
|
end_strategy: EndStrategy
|
|
135
119
|
"""Strategy for handling tool calls when a final result is found."""
|
|
136
120
|
|
|
@@ -141,10 +125,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
141
125
|
be merged with this value, with the runtime argument taking priority.
|
|
142
126
|
"""
|
|
143
127
|
|
|
144
|
-
|
|
145
|
-
"""
|
|
146
|
-
The type of data output by agent runs, used to validate the data returned by the model, defaults to `str`.
|
|
147
|
-
"""
|
|
128
|
+
_output_type: OutputSpec[OutputDataT]
|
|
148
129
|
|
|
149
130
|
instrument: InstrumentationSettings | bool | None
|
|
150
131
|
"""Options to automatically instrument with OpenTelemetry."""
|
|
@@ -163,10 +144,13 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
163
144
|
)
|
|
164
145
|
_function_toolset: FunctionToolset[AgentDepsT] = dataclasses.field(repr=False)
|
|
165
146
|
_output_toolset: OutputToolset[AgentDepsT] | None = dataclasses.field(repr=False)
|
|
166
|
-
_user_toolsets:
|
|
147
|
+
_user_toolsets: list[AbstractToolset[AgentDepsT]] = dataclasses.field(repr=False)
|
|
167
148
|
_prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = dataclasses.field(repr=False)
|
|
168
149
|
_prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = dataclasses.field(repr=False)
|
|
169
150
|
_max_result_retries: int = dataclasses.field(repr=False)
|
|
151
|
+
_max_tool_retries: int = dataclasses.field(repr=False)
|
|
152
|
+
|
|
153
|
+
_event_stream_handler: EventStreamHandler[AgentDepsT] | None = dataclasses.field(repr=False)
|
|
170
154
|
|
|
171
155
|
_enter_lock: Lock = dataclasses.field(repr=False)
|
|
172
156
|
_entered_count: int = dataclasses.field(repr=False)
|
|
@@ -192,11 +176,12 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
192
176
|
builtin_tools: Sequence[AbstractBuiltinTool] = (),
|
|
193
177
|
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
|
|
194
178
|
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
|
|
195
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
179
|
+
toolsets: Sequence[AbstractToolset[AgentDepsT] | ToolsetFunc[AgentDepsT]] | None = None,
|
|
196
180
|
defer_model_check: bool = False,
|
|
197
181
|
end_strategy: EndStrategy = 'early',
|
|
198
182
|
instrument: InstrumentationSettings | bool | None = None,
|
|
199
183
|
history_processors: Sequence[HistoryProcessor[AgentDepsT]] | None = None,
|
|
184
|
+
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
|
|
200
185
|
) -> None: ...
|
|
201
186
|
|
|
202
187
|
@overload
|
|
@@ -225,6 +210,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
225
210
|
end_strategy: EndStrategy = 'early',
|
|
226
211
|
instrument: InstrumentationSettings | bool | None = None,
|
|
227
212
|
history_processors: Sequence[HistoryProcessor[AgentDepsT]] | None = None,
|
|
213
|
+
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
|
|
228
214
|
) -> None: ...
|
|
229
215
|
|
|
230
216
|
def __init__(
|
|
@@ -246,11 +232,12 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
246
232
|
builtin_tools: Sequence[AbstractBuiltinTool] = (),
|
|
247
233
|
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
|
|
248
234
|
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
|
|
249
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
235
|
+
toolsets: Sequence[AbstractToolset[AgentDepsT] | ToolsetFunc[AgentDepsT]] | None = None,
|
|
250
236
|
defer_model_check: bool = False,
|
|
251
237
|
end_strategy: EndStrategy = 'early',
|
|
252
238
|
instrument: InstrumentationSettings | bool | None = None,
|
|
253
239
|
history_processors: Sequence[HistoryProcessor[AgentDepsT]] | None = None,
|
|
240
|
+
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
|
|
254
241
|
**_deprecated_kwargs: Any,
|
|
255
242
|
):
|
|
256
243
|
"""Create an agent.
|
|
@@ -283,7 +270,8 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
283
270
|
prepare_output_tools: Custom function to prepare the tool definition of all output tools for each step.
|
|
284
271
|
This is useful if you want to customize the definition of multiple output tools or you want to register
|
|
285
272
|
a subset of output tools for a given step. See [`ToolsPrepareFunc`][pydantic_ai.tools.ToolsPrepareFunc]
|
|
286
|
-
toolsets: Toolsets to register with the agent, including MCP servers
|
|
273
|
+
toolsets: Toolsets to register with the agent, including MCP servers and functions which take a run context
|
|
274
|
+
and return a toolset. See [`ToolsetFunc`][pydantic_ai.toolsets.ToolsetFunc] for more information.
|
|
287
275
|
defer_model_check: by default, if you provide a [named][pydantic_ai.models.KnownModelName] model,
|
|
288
276
|
it's evaluated to create a [`Model`][pydantic_ai.models.Model] instance immediately,
|
|
289
277
|
which checks for the necessary environment variables. Set this to `false`
|
|
@@ -301,17 +289,18 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
301
289
|
history_processors: Optional list of callables to process the message history before sending it to the model.
|
|
302
290
|
Each processor takes a list of messages and returns a modified list of messages.
|
|
303
291
|
Processors can be sync or async and are applied in sequence.
|
|
292
|
+
event_stream_handler: Optional handler for events from the model's streaming response and the agent's execution of tools.
|
|
304
293
|
"""
|
|
305
294
|
if model is None or defer_model_check:
|
|
306
|
-
self.
|
|
295
|
+
self._model = model
|
|
307
296
|
else:
|
|
308
|
-
self.
|
|
297
|
+
self._model = models.infer_model(model)
|
|
309
298
|
|
|
299
|
+
self._name = name
|
|
310
300
|
self.end_strategy = end_strategy
|
|
311
|
-
self.name = name
|
|
312
301
|
self.model_settings = model_settings
|
|
313
302
|
|
|
314
|
-
self.
|
|
303
|
+
self._output_type = output_type
|
|
315
304
|
self.instrument = instrument
|
|
316
305
|
self._deps_type = deps_type
|
|
317
306
|
|
|
@@ -346,6 +335,8 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
346
335
|
self._system_prompt_dynamic_functions = {}
|
|
347
336
|
|
|
348
337
|
self._max_result_retries = output_retries if output_retries is not None else retries
|
|
338
|
+
self._max_tool_retries = retries
|
|
339
|
+
|
|
349
340
|
self._builtin_tools = builtin_tools
|
|
350
341
|
|
|
351
342
|
self._prepare_tools = prepare_tools
|
|
@@ -355,16 +346,26 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
355
346
|
if self._output_toolset:
|
|
356
347
|
self._output_toolset.max_retries = self._max_result_retries
|
|
357
348
|
|
|
358
|
-
self._function_toolset =
|
|
359
|
-
self.
|
|
349
|
+
self._function_toolset = _AgentFunctionToolset(tools, max_retries=self._max_tool_retries)
|
|
350
|
+
self._dynamic_toolsets = [
|
|
351
|
+
DynamicToolset[AgentDepsT](toolset_func=toolset)
|
|
352
|
+
for toolset in toolsets or []
|
|
353
|
+
if not isinstance(toolset, AbstractToolset)
|
|
354
|
+
]
|
|
355
|
+
self._user_toolsets = [toolset for toolset in toolsets or [] if isinstance(toolset, AbstractToolset)]
|
|
360
356
|
|
|
361
357
|
self.history_processors = history_processors or []
|
|
362
358
|
|
|
359
|
+
self._event_stream_handler = event_stream_handler
|
|
360
|
+
|
|
363
361
|
self._override_deps: ContextVar[_utils.Option[AgentDepsT]] = ContextVar('_override_deps', default=None)
|
|
364
362
|
self._override_model: ContextVar[_utils.Option[models.Model]] = ContextVar('_override_model', default=None)
|
|
365
363
|
self._override_toolsets: ContextVar[_utils.Option[Sequence[AbstractToolset[AgentDepsT]]]] = ContextVar(
|
|
366
364
|
'_override_toolsets', default=None
|
|
367
365
|
)
|
|
366
|
+
self._override_tools: ContextVar[
|
|
367
|
+
_utils.Option[Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]]]
|
|
368
|
+
] = ContextVar('_override_tools', default=None)
|
|
368
369
|
|
|
369
370
|
self._enter_lock = _utils.get_async_lock()
|
|
370
371
|
self._entered_count = 0
|
|
@@ -375,107 +376,49 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
375
376
|
"""Set the instrumentation options for all agents where `instrument` is not set."""
|
|
376
377
|
Agent._instrument_default = instrument
|
|
377
378
|
|
|
378
|
-
@
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
*,
|
|
383
|
-
output_type: None = None,
|
|
384
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
385
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
386
|
-
deps: AgentDepsT = None,
|
|
387
|
-
model_settings: ModelSettings | None = None,
|
|
388
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
389
|
-
usage: _usage.Usage | None = None,
|
|
390
|
-
infer_name: bool = True,
|
|
391
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
392
|
-
) -> AgentRunResult[OutputDataT]: ...
|
|
393
|
-
|
|
394
|
-
@overload
|
|
395
|
-
async def run(
|
|
396
|
-
self,
|
|
397
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
398
|
-
*,
|
|
399
|
-
output_type: OutputSpec[RunOutputDataT],
|
|
400
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
401
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
402
|
-
deps: AgentDepsT = None,
|
|
403
|
-
model_settings: ModelSettings | None = None,
|
|
404
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
405
|
-
usage: _usage.Usage | None = None,
|
|
406
|
-
infer_name: bool = True,
|
|
407
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
408
|
-
) -> AgentRunResult[RunOutputDataT]: ...
|
|
409
|
-
|
|
410
|
-
async def run(
|
|
411
|
-
self,
|
|
412
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
413
|
-
*,
|
|
414
|
-
output_type: OutputSpec[RunOutputDataT] | None = None,
|
|
415
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
416
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
417
|
-
deps: AgentDepsT = None,
|
|
418
|
-
model_settings: ModelSettings | None = None,
|
|
419
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
420
|
-
usage: _usage.Usage | None = None,
|
|
421
|
-
infer_name: bool = True,
|
|
422
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
423
|
-
**_deprecated_kwargs: Never,
|
|
424
|
-
) -> AgentRunResult[Any]:
|
|
425
|
-
"""Run the agent with a user prompt in async mode.
|
|
379
|
+
@property
|
|
380
|
+
def model(self) -> models.Model | models.KnownModelName | str | None:
|
|
381
|
+
"""The default model configured for this agent."""
|
|
382
|
+
return self._model
|
|
426
383
|
|
|
427
|
-
|
|
428
|
-
|
|
384
|
+
@model.setter
|
|
385
|
+
def model(self, value: models.Model | models.KnownModelName | str | None) -> None:
|
|
386
|
+
"""Set the default model configured for this agent.
|
|
429
387
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
388
|
+
We allow `str` here since the actual list of allowed models changes frequently.
|
|
389
|
+
"""
|
|
390
|
+
self._model = value
|
|
433
391
|
|
|
434
|
-
|
|
392
|
+
@property
|
|
393
|
+
def name(self) -> str | None:
|
|
394
|
+
"""The name of the agent, used for logging.
|
|
435
395
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
#> Paris
|
|
440
|
-
```
|
|
396
|
+
If `None`, we try to infer the agent name from the call frame when the agent is first run.
|
|
397
|
+
"""
|
|
398
|
+
return self._name
|
|
441
399
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
message_history: History of the conversation so far.
|
|
447
|
-
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
448
|
-
deps: Optional dependencies to use for this run.
|
|
449
|
-
model_settings: Optional settings to use for this model's request.
|
|
450
|
-
usage_limits: Optional limits on model request count or token usage.
|
|
451
|
-
usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
|
|
452
|
-
infer_name: Whether to try to infer the agent name from the call frame if it's not set.
|
|
453
|
-
toolsets: Optional additional toolsets for this run.
|
|
400
|
+
@name.setter
|
|
401
|
+
def name(self, value: str | None) -> None:
|
|
402
|
+
"""Set the name of the agent, used for logging."""
|
|
403
|
+
self._name = value
|
|
454
404
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
"""
|
|
458
|
-
|
|
459
|
-
self._infer_name(inspect.currentframe())
|
|
405
|
+
@property
|
|
406
|
+
def deps_type(self) -> type:
|
|
407
|
+
"""The type of dependencies used by the agent."""
|
|
408
|
+
return self._deps_type
|
|
460
409
|
|
|
461
|
-
|
|
410
|
+
@property
|
|
411
|
+
def output_type(self) -> OutputSpec[OutputDataT]:
|
|
412
|
+
"""The type of data output by agent runs, used to validate the data returned by the model, defaults to `str`."""
|
|
413
|
+
return self._output_type
|
|
462
414
|
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
model=model,
|
|
468
|
-
deps=deps,
|
|
469
|
-
model_settings=model_settings,
|
|
470
|
-
usage_limits=usage_limits,
|
|
471
|
-
usage=usage,
|
|
472
|
-
toolsets=toolsets,
|
|
473
|
-
) as agent_run:
|
|
474
|
-
async for _ in agent_run:
|
|
475
|
-
pass
|
|
415
|
+
@property
|
|
416
|
+
def event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
|
|
417
|
+
"""Optional handler for events from the model's streaming response and the agent's execution of tools."""
|
|
418
|
+
return self._event_stream_handler
|
|
476
419
|
|
|
477
|
-
|
|
478
|
-
return
|
|
420
|
+
def __repr__(self) -> str:
|
|
421
|
+
return f'{type(self).__name__}(model={self.model!r}, name={self.name!r}, end_strategy={self.end_strategy!r}, model_settings={self.model_settings!r}, output_type={self.output_type!r}, instrument={self.instrument!r})'
|
|
479
422
|
|
|
480
423
|
@overload
|
|
481
424
|
def iter(
|
|
@@ -491,7 +434,6 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
491
434
|
usage: _usage.Usage | None = None,
|
|
492
435
|
infer_name: bool = True,
|
|
493
436
|
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
494
|
-
**_deprecated_kwargs: Never,
|
|
495
437
|
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ...
|
|
496
438
|
|
|
497
439
|
@overload
|
|
@@ -508,7 +450,6 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
508
450
|
usage: _usage.Usage | None = None,
|
|
509
451
|
infer_name: bool = True,
|
|
510
452
|
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
511
|
-
**_deprecated_kwargs: Never,
|
|
512
453
|
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ...
|
|
513
454
|
|
|
514
455
|
@asynccontextmanager
|
|
@@ -525,7 +466,6 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
525
466
|
usage: _usage.Usage | None = None,
|
|
526
467
|
infer_name: bool = True,
|
|
527
468
|
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
528
|
-
**_deprecated_kwargs: Never,
|
|
529
469
|
) -> AsyncIterator[AgentRun[AgentDepsT, Any]]:
|
|
530
470
|
"""A contextmanager which can be used to iterate over the agent graph's nodes as they are executed.
|
|
531
471
|
|
|
@@ -573,19 +513,19 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
573
513
|
),
|
|
574
514
|
CallToolsNode(
|
|
575
515
|
model_response=ModelResponse(
|
|
576
|
-
parts=[TextPart(content='Paris')],
|
|
516
|
+
parts=[TextPart(content='The capital of France is Paris.')],
|
|
577
517
|
usage=Usage(
|
|
578
|
-
requests=1, request_tokens=56, response_tokens=
|
|
518
|
+
requests=1, request_tokens=56, response_tokens=7, total_tokens=63
|
|
579
519
|
),
|
|
580
520
|
model_name='gpt-4o',
|
|
581
521
|
timestamp=datetime.datetime(...),
|
|
582
522
|
)
|
|
583
523
|
),
|
|
584
|
-
End(data=FinalResult(output='Paris')),
|
|
524
|
+
End(data=FinalResult(output='The capital of France is Paris.')),
|
|
585
525
|
]
|
|
586
526
|
'''
|
|
587
527
|
print(agent_run.result.output)
|
|
588
|
-
#> Paris
|
|
528
|
+
#> The capital of France is Paris.
|
|
589
529
|
```
|
|
590
530
|
|
|
591
531
|
Args:
|
|
@@ -609,8 +549,6 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
609
549
|
model_used = self._get_model(model)
|
|
610
550
|
del model
|
|
611
551
|
|
|
612
|
-
_utils.validate_empty_kwargs(_deprecated_kwargs)
|
|
613
|
-
|
|
614
552
|
deps = self._get_deps(deps)
|
|
615
553
|
new_message_index = len(message_history) if message_history else 0
|
|
616
554
|
output_schema = self._prepare_output_schema(output_type, model_used.profile)
|
|
@@ -662,9 +600,10 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
662
600
|
)
|
|
663
601
|
|
|
664
602
|
toolset = self._get_toolset(output_toolset=output_toolset, additional_toolsets=toolsets)
|
|
665
|
-
|
|
603
|
+
|
|
666
604
|
async with toolset:
|
|
667
|
-
|
|
605
|
+
# This will raise errors for any name conflicts
|
|
606
|
+
tool_manager = await ToolManager[AgentDepsT].build(toolset, run_context)
|
|
668
607
|
|
|
669
608
|
# Merge model settings in order of precedence: run > agent > model
|
|
670
609
|
merged_settings = merge_model_settings(model_used.settings, self.model_settings)
|
|
@@ -709,7 +648,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
709
648
|
output_validators=output_validators,
|
|
710
649
|
history_processors=self.history_processors,
|
|
711
650
|
builtin_tools=list(self._builtin_tools),
|
|
712
|
-
tool_manager=
|
|
651
|
+
tool_manager=tool_manager,
|
|
713
652
|
tracer=tracer,
|
|
714
653
|
get_instructions=get_instructions,
|
|
715
654
|
instrumentation_settings=instrumentation_settings,
|
|
@@ -769,266 +708,6 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
769
708
|
),
|
|
770
709
|
}
|
|
771
710
|
|
|
772
|
-
@overload
|
|
773
|
-
def run_sync(
|
|
774
|
-
self,
|
|
775
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
776
|
-
*,
|
|
777
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
778
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
779
|
-
deps: AgentDepsT = None,
|
|
780
|
-
model_settings: ModelSettings | None = None,
|
|
781
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
782
|
-
usage: _usage.Usage | None = None,
|
|
783
|
-
infer_name: bool = True,
|
|
784
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
785
|
-
) -> AgentRunResult[OutputDataT]: ...
|
|
786
|
-
|
|
787
|
-
@overload
|
|
788
|
-
def run_sync(
|
|
789
|
-
self,
|
|
790
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
791
|
-
*,
|
|
792
|
-
output_type: OutputSpec[RunOutputDataT] | None = None,
|
|
793
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
794
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
795
|
-
deps: AgentDepsT = None,
|
|
796
|
-
model_settings: ModelSettings | None = None,
|
|
797
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
798
|
-
usage: _usage.Usage | None = None,
|
|
799
|
-
infer_name: bool = True,
|
|
800
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
801
|
-
) -> AgentRunResult[RunOutputDataT]: ...
|
|
802
|
-
|
|
803
|
-
def run_sync(
|
|
804
|
-
self,
|
|
805
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
806
|
-
*,
|
|
807
|
-
output_type: OutputSpec[RunOutputDataT] | None = None,
|
|
808
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
809
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
810
|
-
deps: AgentDepsT = None,
|
|
811
|
-
model_settings: ModelSettings | None = None,
|
|
812
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
813
|
-
usage: _usage.Usage | None = None,
|
|
814
|
-
infer_name: bool = True,
|
|
815
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
816
|
-
**_deprecated_kwargs: Never,
|
|
817
|
-
) -> AgentRunResult[Any]:
|
|
818
|
-
"""Synchronously run the agent with a user prompt.
|
|
819
|
-
|
|
820
|
-
This is a convenience method that wraps [`self.run`][pydantic_ai.Agent.run] with `loop.run_until_complete(...)`.
|
|
821
|
-
You therefore can't use this method inside async code or if there's an active event loop.
|
|
822
|
-
|
|
823
|
-
Example:
|
|
824
|
-
```python
|
|
825
|
-
from pydantic_ai import Agent
|
|
826
|
-
|
|
827
|
-
agent = Agent('openai:gpt-4o')
|
|
828
|
-
|
|
829
|
-
result_sync = agent.run_sync('What is the capital of Italy?')
|
|
830
|
-
print(result_sync.output)
|
|
831
|
-
#> Rome
|
|
832
|
-
```
|
|
833
|
-
|
|
834
|
-
Args:
|
|
835
|
-
user_prompt: User input to start/continue the conversation.
|
|
836
|
-
output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
|
|
837
|
-
output validators since output validators would expect an argument that matches the agent's output type.
|
|
838
|
-
message_history: History of the conversation so far.
|
|
839
|
-
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
840
|
-
deps: Optional dependencies to use for this run.
|
|
841
|
-
model_settings: Optional settings to use for this model's request.
|
|
842
|
-
usage_limits: Optional limits on model request count or token usage.
|
|
843
|
-
usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
|
|
844
|
-
infer_name: Whether to try to infer the agent name from the call frame if it's not set.
|
|
845
|
-
toolsets: Optional additional toolsets for this run.
|
|
846
|
-
|
|
847
|
-
Returns:
|
|
848
|
-
The result of the run.
|
|
849
|
-
"""
|
|
850
|
-
if infer_name and self.name is None:
|
|
851
|
-
self._infer_name(inspect.currentframe())
|
|
852
|
-
|
|
853
|
-
_utils.validate_empty_kwargs(_deprecated_kwargs)
|
|
854
|
-
|
|
855
|
-
return get_event_loop().run_until_complete(
|
|
856
|
-
self.run(
|
|
857
|
-
user_prompt,
|
|
858
|
-
output_type=output_type,
|
|
859
|
-
message_history=message_history,
|
|
860
|
-
model=model,
|
|
861
|
-
deps=deps,
|
|
862
|
-
model_settings=model_settings,
|
|
863
|
-
usage_limits=usage_limits,
|
|
864
|
-
usage=usage,
|
|
865
|
-
infer_name=False,
|
|
866
|
-
toolsets=toolsets,
|
|
867
|
-
)
|
|
868
|
-
)
|
|
869
|
-
|
|
870
|
-
@overload
|
|
871
|
-
def run_stream(
|
|
872
|
-
self,
|
|
873
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
874
|
-
*,
|
|
875
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
876
|
-
model: models.Model | models.KnownModelName | None = None,
|
|
877
|
-
deps: AgentDepsT = None,
|
|
878
|
-
model_settings: ModelSettings | None = None,
|
|
879
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
880
|
-
usage: _usage.Usage | None = None,
|
|
881
|
-
infer_name: bool = True,
|
|
882
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
883
|
-
) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, OutputDataT]]: ...
|
|
884
|
-
|
|
885
|
-
@overload
|
|
886
|
-
def run_stream(
|
|
887
|
-
self,
|
|
888
|
-
user_prompt: str | Sequence[_messages.UserContent],
|
|
889
|
-
*,
|
|
890
|
-
output_type: OutputSpec[RunOutputDataT],
|
|
891
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
892
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
893
|
-
deps: AgentDepsT = None,
|
|
894
|
-
model_settings: ModelSettings | None = None,
|
|
895
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
896
|
-
usage: _usage.Usage | None = None,
|
|
897
|
-
infer_name: bool = True,
|
|
898
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
899
|
-
) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, RunOutputDataT]]: ...
|
|
900
|
-
|
|
901
|
-
@asynccontextmanager
|
|
902
|
-
async def run_stream(
|
|
903
|
-
self,
|
|
904
|
-
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
905
|
-
*,
|
|
906
|
-
output_type: OutputSpec[RunOutputDataT] | None = None,
|
|
907
|
-
message_history: list[_messages.ModelMessage] | None = None,
|
|
908
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
909
|
-
deps: AgentDepsT = None,
|
|
910
|
-
model_settings: ModelSettings | None = None,
|
|
911
|
-
usage_limits: _usage.UsageLimits | None = None,
|
|
912
|
-
usage: _usage.Usage | None = None,
|
|
913
|
-
infer_name: bool = True,
|
|
914
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
915
|
-
**_deprecated_kwargs: Never,
|
|
916
|
-
) -> AsyncIterator[result.StreamedRunResult[AgentDepsT, Any]]:
|
|
917
|
-
"""Run the agent with a user prompt in async mode, returning a streamed response.
|
|
918
|
-
|
|
919
|
-
Example:
|
|
920
|
-
```python
|
|
921
|
-
from pydantic_ai import Agent
|
|
922
|
-
|
|
923
|
-
agent = Agent('openai:gpt-4o')
|
|
924
|
-
|
|
925
|
-
async def main():
|
|
926
|
-
async with agent.run_stream('What is the capital of the UK?') as response:
|
|
927
|
-
print(await response.get_output())
|
|
928
|
-
#> London
|
|
929
|
-
```
|
|
930
|
-
|
|
931
|
-
Args:
|
|
932
|
-
user_prompt: User input to start/continue the conversation.
|
|
933
|
-
output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
|
|
934
|
-
output validators since output validators would expect an argument that matches the agent's output type.
|
|
935
|
-
message_history: History of the conversation so far.
|
|
936
|
-
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
937
|
-
deps: Optional dependencies to use for this run.
|
|
938
|
-
model_settings: Optional settings to use for this model's request.
|
|
939
|
-
usage_limits: Optional limits on model request count or token usage.
|
|
940
|
-
usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
|
|
941
|
-
infer_name: Whether to try to infer the agent name from the call frame if it's not set.
|
|
942
|
-
toolsets: Optional additional toolsets for this run.
|
|
943
|
-
|
|
944
|
-
Returns:
|
|
945
|
-
The result of the run.
|
|
946
|
-
"""
|
|
947
|
-
# TODO: We need to deprecate this now that we have the `iter` method.
|
|
948
|
-
# Before that, though, we should add an event for when we reach the final result of the stream.
|
|
949
|
-
if infer_name and self.name is None:
|
|
950
|
-
# f_back because `asynccontextmanager` adds one frame
|
|
951
|
-
if frame := inspect.currentframe(): # pragma: no branch
|
|
952
|
-
self._infer_name(frame.f_back)
|
|
953
|
-
|
|
954
|
-
_utils.validate_empty_kwargs(_deprecated_kwargs)
|
|
955
|
-
|
|
956
|
-
yielded = False
|
|
957
|
-
async with self.iter(
|
|
958
|
-
user_prompt,
|
|
959
|
-
output_type=output_type,
|
|
960
|
-
message_history=message_history,
|
|
961
|
-
model=model,
|
|
962
|
-
deps=deps,
|
|
963
|
-
model_settings=model_settings,
|
|
964
|
-
usage_limits=usage_limits,
|
|
965
|
-
usage=usage,
|
|
966
|
-
infer_name=False,
|
|
967
|
-
toolsets=toolsets,
|
|
968
|
-
) as agent_run:
|
|
969
|
-
first_node = agent_run.next_node # start with the first node
|
|
970
|
-
assert isinstance(first_node, _agent_graph.UserPromptNode) # the first node should be a user prompt node
|
|
971
|
-
node = first_node
|
|
972
|
-
while True:
|
|
973
|
-
if self.is_model_request_node(node):
|
|
974
|
-
graph_ctx = agent_run.ctx
|
|
975
|
-
async with node.stream(graph_ctx) as stream:
|
|
976
|
-
|
|
977
|
-
async def stream_to_final(s: AgentStream) -> FinalResult[AgentStream] | None:
|
|
978
|
-
async for event in stream:
|
|
979
|
-
if isinstance(event, _messages.FinalResultEvent):
|
|
980
|
-
return FinalResult(s, event.tool_name, event.tool_call_id)
|
|
981
|
-
return None
|
|
982
|
-
|
|
983
|
-
final_result = await stream_to_final(stream)
|
|
984
|
-
if final_result is not None:
|
|
985
|
-
if yielded:
|
|
986
|
-
raise exceptions.AgentRunError('Agent run produced final results') # pragma: no cover
|
|
987
|
-
yielded = True
|
|
988
|
-
|
|
989
|
-
messages = graph_ctx.state.message_history.copy()
|
|
990
|
-
|
|
991
|
-
async def on_complete() -> None:
|
|
992
|
-
"""Called when the stream has completed.
|
|
993
|
-
|
|
994
|
-
The model response will have been added to messages by now
|
|
995
|
-
by `StreamedRunResult._marked_completed`.
|
|
996
|
-
"""
|
|
997
|
-
last_message = messages[-1]
|
|
998
|
-
assert isinstance(last_message, _messages.ModelResponse)
|
|
999
|
-
tool_calls = [
|
|
1000
|
-
part for part in last_message.parts if isinstance(part, _messages.ToolCallPart)
|
|
1001
|
-
]
|
|
1002
|
-
|
|
1003
|
-
parts: list[_messages.ModelRequestPart] = []
|
|
1004
|
-
async for _event in _agent_graph.process_function_tools(
|
|
1005
|
-
graph_ctx.deps.tool_manager,
|
|
1006
|
-
tool_calls,
|
|
1007
|
-
final_result,
|
|
1008
|
-
graph_ctx,
|
|
1009
|
-
parts,
|
|
1010
|
-
):
|
|
1011
|
-
pass
|
|
1012
|
-
if parts:
|
|
1013
|
-
messages.append(_messages.ModelRequest(parts))
|
|
1014
|
-
|
|
1015
|
-
yield StreamedRunResult(
|
|
1016
|
-
messages,
|
|
1017
|
-
graph_ctx.deps.new_message_index,
|
|
1018
|
-
stream,
|
|
1019
|
-
on_complete,
|
|
1020
|
-
)
|
|
1021
|
-
break
|
|
1022
|
-
next_node = await agent_run.next(node)
|
|
1023
|
-
if not isinstance(next_node, _agent_graph.AgentNode):
|
|
1024
|
-
raise exceptions.AgentRunError( # pragma: no cover
|
|
1025
|
-
'Should have produced a StreamedRunResult before getting here'
|
|
1026
|
-
)
|
|
1027
|
-
node = cast(_agent_graph.AgentNode[Any, Any], next_node)
|
|
1028
|
-
|
|
1029
|
-
if not yielded:
|
|
1030
|
-
raise exceptions.AgentRunError('Agent run finished without producing a final result') # pragma: no cover
|
|
1031
|
-
|
|
1032
711
|
@contextmanager
|
|
1033
712
|
def override(
|
|
1034
713
|
self,
|
|
@@ -1036,8 +715,9 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1036
715
|
deps: AgentDepsT | _utils.Unset = _utils.UNSET,
|
|
1037
716
|
model: models.Model | models.KnownModelName | str | _utils.Unset = _utils.UNSET,
|
|
1038
717
|
toolsets: Sequence[AbstractToolset[AgentDepsT]] | _utils.Unset = _utils.UNSET,
|
|
718
|
+
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] | _utils.Unset = _utils.UNSET,
|
|
1039
719
|
) -> Iterator[None]:
|
|
1040
|
-
"""Context manager to temporarily override agent dependencies, model, or
|
|
720
|
+
"""Context manager to temporarily override agent dependencies, model, toolsets, or tools.
|
|
1041
721
|
|
|
1042
722
|
This is particularly useful when testing.
|
|
1043
723
|
You can find an example of this [here](../testing.md#overriding-model-via-pytest-fixtures).
|
|
@@ -1046,6 +726,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1046
726
|
deps: The dependencies to use instead of the dependencies passed to the agent run.
|
|
1047
727
|
model: The model to use instead of the model passed to the agent run.
|
|
1048
728
|
toolsets: The toolsets to use instead of the toolsets passed to the agent constructor and agent run.
|
|
729
|
+
tools: The tools to use instead of the tools registered with the agent.
|
|
1049
730
|
"""
|
|
1050
731
|
if _utils.is_set(deps):
|
|
1051
732
|
deps_token = self._override_deps.set(_utils.Some(deps))
|
|
@@ -1062,6 +743,11 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1062
743
|
else:
|
|
1063
744
|
toolsets_token = None
|
|
1064
745
|
|
|
746
|
+
if _utils.is_set(tools):
|
|
747
|
+
tools_token = self._override_tools.set(_utils.Some(tools))
|
|
748
|
+
else:
|
|
749
|
+
tools_token = None
|
|
750
|
+
|
|
1065
751
|
try:
|
|
1066
752
|
yield
|
|
1067
753
|
finally:
|
|
@@ -1071,6 +757,8 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1071
757
|
self._override_model.reset(model_token)
|
|
1072
758
|
if toolsets_token is not None:
|
|
1073
759
|
self._override_toolsets.reset(toolsets_token)
|
|
760
|
+
if tools_token is not None:
|
|
761
|
+
self._override_tools.reset(tools_token)
|
|
1074
762
|
|
|
1075
763
|
@overload
|
|
1076
764
|
def instructions(
|
|
@@ -1461,6 +1149,53 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1461
1149
|
|
|
1462
1150
|
return tool_decorator if func is None else tool_decorator(func)
|
|
1463
1151
|
|
|
1152
|
+
@overload
|
|
1153
|
+
def toolset(self, func: ToolsetFunc[AgentDepsT], /) -> ToolsetFunc[AgentDepsT]: ...
|
|
1154
|
+
|
|
1155
|
+
@overload
|
|
1156
|
+
def toolset(
|
|
1157
|
+
self,
|
|
1158
|
+
/,
|
|
1159
|
+
*,
|
|
1160
|
+
per_run_step: bool = True,
|
|
1161
|
+
) -> Callable[[ToolsetFunc[AgentDepsT]], ToolsetFunc[AgentDepsT]]: ...
|
|
1162
|
+
|
|
1163
|
+
def toolset(
|
|
1164
|
+
self,
|
|
1165
|
+
func: ToolsetFunc[AgentDepsT] | None = None,
|
|
1166
|
+
/,
|
|
1167
|
+
*,
|
|
1168
|
+
per_run_step: bool = True,
|
|
1169
|
+
) -> Any:
|
|
1170
|
+
"""Decorator to register a toolset function which takes [`RunContext`][pydantic_ai.tools.RunContext] as its only argument.
|
|
1171
|
+
|
|
1172
|
+
Can decorate a sync or async functions.
|
|
1173
|
+
|
|
1174
|
+
The decorator can be used bare (`agent.toolset`).
|
|
1175
|
+
|
|
1176
|
+
Example:
|
|
1177
|
+
```python
|
|
1178
|
+
from pydantic_ai import Agent, RunContext
|
|
1179
|
+
from pydantic_ai.toolsets import AbstractToolset, FunctionToolset
|
|
1180
|
+
|
|
1181
|
+
agent = Agent('test', deps_type=str)
|
|
1182
|
+
|
|
1183
|
+
@agent.toolset
|
|
1184
|
+
async def simple_toolset(ctx: RunContext[str]) -> AbstractToolset[str]:
|
|
1185
|
+
return FunctionToolset()
|
|
1186
|
+
```
|
|
1187
|
+
|
|
1188
|
+
Args:
|
|
1189
|
+
func: The toolset function to register.
|
|
1190
|
+
per_run_step: Whether to re-evaluate the toolset for each run step. Defaults to True.
|
|
1191
|
+
"""
|
|
1192
|
+
|
|
1193
|
+
def toolset_decorator(func_: ToolsetFunc[AgentDepsT]) -> ToolsetFunc[AgentDepsT]:
|
|
1194
|
+
self._dynamic_toolsets.append(DynamicToolset(func_, per_run_step=per_run_step))
|
|
1195
|
+
return func_
|
|
1196
|
+
|
|
1197
|
+
return toolset_decorator if func is None else toolset_decorator(func)
|
|
1198
|
+
|
|
1464
1199
|
def _get_model(self, model: models.Model | models.KnownModelName | str | None) -> models.Model:
|
|
1465
1200
|
"""Create a model configured for this agent.
|
|
1466
1201
|
|
|
@@ -1514,46 +1249,56 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1514
1249
|
|
|
1515
1250
|
Args:
|
|
1516
1251
|
output_toolset: The output toolset to use instead of the one built at agent construction time.
|
|
1517
|
-
additional_toolsets: Additional toolsets to add.
|
|
1252
|
+
additional_toolsets: Additional toolsets to add, unless toolsets have been overridden.
|
|
1518
1253
|
"""
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
else:
|
|
1524
|
-
user_toolsets = self._user_toolsets
|
|
1254
|
+
toolsets = self.toolsets
|
|
1255
|
+
# Don't add additional toolsets if the toolsets have been overridden
|
|
1256
|
+
if additional_toolsets and self._override_toolsets.get() is None:
|
|
1257
|
+
toolsets = [*toolsets, *additional_toolsets]
|
|
1525
1258
|
|
|
1526
|
-
|
|
1259
|
+
toolset = CombinedToolset(toolsets)
|
|
1260
|
+
|
|
1261
|
+
# Copy the dynamic toolsets to ensure each run has its own instances
|
|
1262
|
+
def copy_dynamic_toolsets(toolset: AbstractToolset[AgentDepsT]) -> AbstractToolset[AgentDepsT]:
|
|
1263
|
+
if isinstance(toolset, DynamicToolset):
|
|
1264
|
+
return dataclasses.replace(toolset)
|
|
1265
|
+
else:
|
|
1266
|
+
return toolset
|
|
1267
|
+
|
|
1268
|
+
toolset = toolset.visit_and_replace(copy_dynamic_toolsets)
|
|
1527
1269
|
|
|
1528
1270
|
if self._prepare_tools:
|
|
1529
|
-
|
|
1271
|
+
toolset = PreparedToolset(toolset, self._prepare_tools)
|
|
1530
1272
|
|
|
1531
1273
|
output_toolset = output_toolset if _utils.is_set(output_toolset) else self._output_toolset
|
|
1532
1274
|
if output_toolset is not None:
|
|
1533
1275
|
if self._prepare_output_tools:
|
|
1534
1276
|
output_toolset = PreparedToolset(output_toolset, self._prepare_output_tools)
|
|
1535
|
-
|
|
1277
|
+
toolset = CombinedToolset([output_toolset, toolset])
|
|
1536
1278
|
|
|
1537
|
-
return
|
|
1279
|
+
return toolset
|
|
1538
1280
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1281
|
+
@property
|
|
1282
|
+
def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]:
|
|
1283
|
+
"""All toolsets registered on the agent, including a function toolset holding tools that were registered on the agent directly.
|
|
1541
1284
|
|
|
1542
|
-
|
|
1285
|
+
Output tools are not included.
|
|
1543
1286
|
"""
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1287
|
+
toolsets: list[AbstractToolset[AgentDepsT]] = []
|
|
1288
|
+
|
|
1289
|
+
if some_tools := self._override_tools.get():
|
|
1290
|
+
function_toolset = _AgentFunctionToolset(some_tools.value, max_retries=self._max_tool_retries)
|
|
1291
|
+
else:
|
|
1292
|
+
function_toolset = self._function_toolset
|
|
1293
|
+
toolsets.append(function_toolset)
|
|
1294
|
+
|
|
1295
|
+
if some_user_toolsets := self._override_toolsets.get():
|
|
1296
|
+
user_toolsets = some_user_toolsets.value
|
|
1297
|
+
else:
|
|
1298
|
+
user_toolsets = [*self._user_toolsets, *self._dynamic_toolsets]
|
|
1299
|
+
toolsets.extend(user_toolsets)
|
|
1300
|
+
|
|
1301
|
+
return toolsets
|
|
1557
1302
|
|
|
1558
1303
|
def _prepare_output_schema(
|
|
1559
1304
|
self, output_type: OutputSpec[RunOutputDataT] | None, model_profile: ModelProfile
|
|
@@ -1571,47 +1316,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1571
1316
|
|
|
1572
1317
|
return schema # pyright: ignore[reportReturnType]
|
|
1573
1318
|
|
|
1574
|
-
|
|
1575
|
-
def is_model_request_node(
|
|
1576
|
-
node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
|
|
1577
|
-
) -> TypeIs[_agent_graph.ModelRequestNode[T, S]]:
|
|
1578
|
-
"""Check if the node is a `ModelRequestNode`, narrowing the type if it is.
|
|
1579
|
-
|
|
1580
|
-
This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
|
|
1581
|
-
"""
|
|
1582
|
-
return isinstance(node, _agent_graph.ModelRequestNode)
|
|
1583
|
-
|
|
1584
|
-
@staticmethod
|
|
1585
|
-
def is_call_tools_node(
|
|
1586
|
-
node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
|
|
1587
|
-
) -> TypeIs[_agent_graph.CallToolsNode[T, S]]:
|
|
1588
|
-
"""Check if the node is a `CallToolsNode`, narrowing the type if it is.
|
|
1589
|
-
|
|
1590
|
-
This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
|
|
1591
|
-
"""
|
|
1592
|
-
return isinstance(node, _agent_graph.CallToolsNode)
|
|
1593
|
-
|
|
1594
|
-
@staticmethod
|
|
1595
|
-
def is_user_prompt_node(
|
|
1596
|
-
node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
|
|
1597
|
-
) -> TypeIs[_agent_graph.UserPromptNode[T, S]]:
|
|
1598
|
-
"""Check if the node is a `UserPromptNode`, narrowing the type if it is.
|
|
1599
|
-
|
|
1600
|
-
This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
|
|
1601
|
-
"""
|
|
1602
|
-
return isinstance(node, _agent_graph.UserPromptNode)
|
|
1603
|
-
|
|
1604
|
-
@staticmethod
|
|
1605
|
-
def is_end_node(
|
|
1606
|
-
node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
|
|
1607
|
-
) -> TypeIs[End[result.FinalResult[S]]]:
|
|
1608
|
-
"""Check if the node is a `End`, narrowing the type if it is.
|
|
1609
|
-
|
|
1610
|
-
This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
|
|
1611
|
-
"""
|
|
1612
|
-
return isinstance(node, End)
|
|
1613
|
-
|
|
1614
|
-
async def __aenter__(self) -> Self:
|
|
1319
|
+
async def __aenter__(self) -> AbstractAgent[AgentDepsT, OutputDataT]:
|
|
1615
1320
|
"""Enter the agent context.
|
|
1616
1321
|
|
|
1617
1322
|
This will start all [`MCPServerStdio`s][pydantic_ai.mcp.MCPServerStdio] registered as `toolsets` so they are ready to be used.
|
|
@@ -1645,7 +1350,7 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1645
1350
|
except exceptions.UserError as e:
|
|
1646
1351
|
raise exceptions.UserError('No sampling model provided and no model set on the agent.') from e
|
|
1647
1352
|
|
|
1648
|
-
from
|
|
1353
|
+
from ..mcp import MCPServer
|
|
1649
1354
|
|
|
1650
1355
|
def _set_sampling_model(toolset: AbstractToolset[AgentDepsT]) -> None:
|
|
1651
1356
|
if isinstance(toolset, MCPServer):
|
|
@@ -1676,536 +1381,13 @@ class Agent(Generic[AgentDepsT, OutputDataT]):
|
|
|
1676
1381
|
async with self:
|
|
1677
1382
|
yield
|
|
1678
1383
|
|
|
1679
|
-
def to_ag_ui(
|
|
1680
|
-
self,
|
|
1681
|
-
*,
|
|
1682
|
-
# Agent.iter parameters
|
|
1683
|
-
output_type: OutputSpec[OutputDataT] | None = None,
|
|
1684
|
-
model: models.Model | models.KnownModelName | str | None = None,
|
|
1685
|
-
deps: AgentDepsT = None,
|
|
1686
|
-
model_settings: ModelSettings | None = None,
|
|
1687
|
-
usage_limits: UsageLimits | None = None,
|
|
1688
|
-
usage: Usage | None = None,
|
|
1689
|
-
infer_name: bool = True,
|
|
1690
|
-
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
1691
|
-
# Starlette
|
|
1692
|
-
debug: bool = False,
|
|
1693
|
-
routes: Sequence[BaseRoute] | None = None,
|
|
1694
|
-
middleware: Sequence[Middleware] | None = None,
|
|
1695
|
-
exception_handlers: Mapping[Any, ExceptionHandler] | None = None,
|
|
1696
|
-
on_startup: Sequence[Callable[[], Any]] | None = None,
|
|
1697
|
-
on_shutdown: Sequence[Callable[[], Any]] | None = None,
|
|
1698
|
-
lifespan: Lifespan[AGUIApp[AgentDepsT, OutputDataT]] | None = None,
|
|
1699
|
-
) -> AGUIApp[AgentDepsT, OutputDataT]:
|
|
1700
|
-
"""Returns an ASGI application that handles every AG-UI request by running the agent.
|
|
1701
|
-
|
|
1702
|
-
Note that the `deps` will be the same for each request, with the exception of the AG-UI state that's
|
|
1703
|
-
injected into the `state` field of a `deps` object that implements the [`StateHandler`][pydantic_ai.ag_ui.StateHandler] protocol.
|
|
1704
|
-
To provide different `deps` for each request (e.g. based on the authenticated user),
|
|
1705
|
-
use [`pydantic_ai.ag_ui.run_ag_ui`][pydantic_ai.ag_ui.run_ag_ui] or
|
|
1706
|
-
[`pydantic_ai.ag_ui.handle_ag_ui_request`][pydantic_ai.ag_ui.handle_ag_ui_request] instead.
|
|
1707
|
-
|
|
1708
|
-
Example:
|
|
1709
|
-
```python
|
|
1710
|
-
from pydantic_ai import Agent
|
|
1711
|
-
|
|
1712
|
-
agent = Agent('openai:gpt-4o')
|
|
1713
|
-
app = agent.to_ag_ui()
|
|
1714
|
-
```
|
|
1715
|
-
|
|
1716
|
-
To run the application, you can use the following command:
|
|
1717
|
-
|
|
1718
|
-
```bash
|
|
1719
|
-
uvicorn app:app --host 0.0.0.0 --port 8000
|
|
1720
|
-
```
|
|
1721
|
-
|
|
1722
|
-
See [AG-UI docs](../ag-ui.md) for more information.
|
|
1723
|
-
|
|
1724
|
-
Args:
|
|
1725
|
-
output_type: Custom output type to use for this run, `output_type` may only be used if the agent has
|
|
1726
|
-
no output validators since output validators would expect an argument that matches the agent's
|
|
1727
|
-
output type.
|
|
1728
|
-
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
1729
|
-
deps: Optional dependencies to use for this run.
|
|
1730
|
-
model_settings: Optional settings to use for this model's request.
|
|
1731
|
-
usage_limits: Optional limits on model request count or token usage.
|
|
1732
|
-
usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
|
|
1733
|
-
infer_name: Whether to try to infer the agent name from the call frame if it's not set.
|
|
1734
|
-
toolsets: Optional additional toolsets for this run.
|
|
1735
|
-
|
|
1736
|
-
debug: Boolean indicating if debug tracebacks should be returned on errors.
|
|
1737
|
-
routes: A list of routes to serve incoming HTTP and WebSocket requests.
|
|
1738
|
-
middleware: A list of middleware to run for every request. A starlette application will always
|
|
1739
|
-
automatically include two middleware classes. `ServerErrorMiddleware` is added as the very
|
|
1740
|
-
outermost middleware, to handle any uncaught errors occurring anywhere in the entire stack.
|
|
1741
|
-
`ExceptionMiddleware` is added as the very innermost middleware, to deal with handled
|
|
1742
|
-
exception cases occurring in the routing or endpoints.
|
|
1743
|
-
exception_handlers: A mapping of either integer status codes, or exception class types onto
|
|
1744
|
-
callables which handle the exceptions. Exception handler callables should be of the form
|
|
1745
|
-
`handler(request, exc) -> response` and may be either standard functions, or async functions.
|
|
1746
|
-
on_startup: A list of callables to run on application startup. Startup handler callables do not
|
|
1747
|
-
take any arguments, and may be either standard functions, or async functions.
|
|
1748
|
-
on_shutdown: A list of callables to run on application shutdown. Shutdown handler callables do
|
|
1749
|
-
not take any arguments, and may be either standard functions, or async functions.
|
|
1750
|
-
lifespan: A lifespan context function, which can be used to perform startup and shutdown tasks.
|
|
1751
|
-
This is a newer style that replaces the `on_startup` and `on_shutdown` handlers. Use one or
|
|
1752
|
-
the other, not both.
|
|
1753
|
-
|
|
1754
|
-
Returns:
|
|
1755
|
-
An ASGI application for running Pydantic AI agents with AG-UI protocol support.
|
|
1756
|
-
"""
|
|
1757
|
-
from .ag_ui import AGUIApp
|
|
1758
|
-
|
|
1759
|
-
return AGUIApp(
|
|
1760
|
-
agent=self,
|
|
1761
|
-
# Agent.iter parameters
|
|
1762
|
-
output_type=output_type,
|
|
1763
|
-
model=model,
|
|
1764
|
-
deps=deps,
|
|
1765
|
-
model_settings=model_settings,
|
|
1766
|
-
usage_limits=usage_limits,
|
|
1767
|
-
usage=usage,
|
|
1768
|
-
infer_name=infer_name,
|
|
1769
|
-
toolsets=toolsets,
|
|
1770
|
-
# Starlette
|
|
1771
|
-
debug=debug,
|
|
1772
|
-
routes=routes,
|
|
1773
|
-
middleware=middleware,
|
|
1774
|
-
exception_handlers=exception_handlers,
|
|
1775
|
-
on_startup=on_startup,
|
|
1776
|
-
on_shutdown=on_shutdown,
|
|
1777
|
-
lifespan=lifespan,
|
|
1778
|
-
)
|
|
1779
|
-
|
|
1780
|
-
def to_a2a(
|
|
1781
|
-
self,
|
|
1782
|
-
*,
|
|
1783
|
-
storage: Storage | None = None,
|
|
1784
|
-
broker: Broker | None = None,
|
|
1785
|
-
# Agent card
|
|
1786
|
-
name: str | None = None,
|
|
1787
|
-
url: str = 'http://localhost:8000',
|
|
1788
|
-
version: str = '1.0.0',
|
|
1789
|
-
description: str | None = None,
|
|
1790
|
-
provider: AgentProvider | None = None,
|
|
1791
|
-
skills: list[Skill] | None = None,
|
|
1792
|
-
# Starlette
|
|
1793
|
-
debug: bool = False,
|
|
1794
|
-
routes: Sequence[Route] | None = None,
|
|
1795
|
-
middleware: Sequence[Middleware] | None = None,
|
|
1796
|
-
exception_handlers: dict[Any, ExceptionHandler] | None = None,
|
|
1797
|
-
lifespan: Lifespan[FastA2A] | None = None,
|
|
1798
|
-
) -> FastA2A:
|
|
1799
|
-
"""Convert the agent to a FastA2A application.
|
|
1800
|
-
|
|
1801
|
-
Example:
|
|
1802
|
-
```python
|
|
1803
|
-
from pydantic_ai import Agent
|
|
1804
|
-
|
|
1805
|
-
agent = Agent('openai:gpt-4o')
|
|
1806
|
-
app = agent.to_a2a()
|
|
1807
|
-
```
|
|
1808
|
-
|
|
1809
|
-
The `app` is an ASGI application that can be used with any ASGI server.
|
|
1810
|
-
|
|
1811
|
-
To run the application, you can use the following command:
|
|
1812
|
-
|
|
1813
|
-
```bash
|
|
1814
|
-
uvicorn app:app --host 0.0.0.0 --port 8000
|
|
1815
|
-
```
|
|
1816
|
-
"""
|
|
1817
|
-
from ._a2a import agent_to_a2a
|
|
1818
|
-
|
|
1819
|
-
return agent_to_a2a(
|
|
1820
|
-
self,
|
|
1821
|
-
storage=storage,
|
|
1822
|
-
broker=broker,
|
|
1823
|
-
name=name,
|
|
1824
|
-
url=url,
|
|
1825
|
-
version=version,
|
|
1826
|
-
description=description,
|
|
1827
|
-
provider=provider,
|
|
1828
|
-
skills=skills,
|
|
1829
|
-
debug=debug,
|
|
1830
|
-
routes=routes,
|
|
1831
|
-
middleware=middleware,
|
|
1832
|
-
exception_handlers=exception_handlers,
|
|
1833
|
-
lifespan=lifespan,
|
|
1834
|
-
)
|
|
1835
|
-
|
|
1836
|
-
async def to_cli(self: Self, deps: AgentDepsT = None, prog_name: str = 'pydantic-ai') -> None:
|
|
1837
|
-
"""Run the agent in a CLI chat interface.
|
|
1838
|
-
|
|
1839
|
-
Args:
|
|
1840
|
-
deps: The dependencies to pass to the agent.
|
|
1841
|
-
prog_name: The name of the program to use for the CLI. Defaults to 'pydantic-ai'.
|
|
1842
|
-
|
|
1843
|
-
Example:
|
|
1844
|
-
```python {title="agent_to_cli.py" test="skip"}
|
|
1845
|
-
from pydantic_ai import Agent
|
|
1846
|
-
|
|
1847
|
-
agent = Agent('openai:gpt-4o', instructions='You always respond in Italian.')
|
|
1848
|
-
|
|
1849
|
-
async def main():
|
|
1850
|
-
await agent.to_cli()
|
|
1851
|
-
```
|
|
1852
|
-
"""
|
|
1853
|
-
from rich.console import Console
|
|
1854
|
-
|
|
1855
|
-
from pydantic_ai._cli import run_chat
|
|
1856
|
-
|
|
1857
|
-
await run_chat(stream=True, agent=self, deps=deps, console=Console(), code_theme='monokai', prog_name=prog_name)
|
|
1858
|
-
|
|
1859
|
-
def to_cli_sync(self: Self, deps: AgentDepsT = None, prog_name: str = 'pydantic-ai') -> None:
|
|
1860
|
-
"""Run the agent in a CLI chat interface with the non-async interface.
|
|
1861
|
-
|
|
1862
|
-
Args:
|
|
1863
|
-
deps: The dependencies to pass to the agent.
|
|
1864
|
-
prog_name: The name of the program to use for the CLI. Defaults to 'pydantic-ai'.
|
|
1865
|
-
|
|
1866
|
-
```python {title="agent_to_cli_sync.py" test="skip"}
|
|
1867
|
-
from pydantic_ai import Agent
|
|
1868
|
-
|
|
1869
|
-
agent = Agent('openai:gpt-4o', instructions='You always respond in Italian.')
|
|
1870
|
-
agent.to_cli_sync()
|
|
1871
|
-
agent.to_cli_sync(prog_name='assistant')
|
|
1872
|
-
```
|
|
1873
|
-
"""
|
|
1874
|
-
return get_event_loop().run_until_complete(self.to_cli(deps=deps, prog_name=prog_name))
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
@dataclasses.dataclass(repr=False)
|
|
1878
|
-
class AgentRun(Generic[AgentDepsT, OutputDataT]):
|
|
1879
|
-
"""A stateful, async-iterable run of an [`Agent`][pydantic_ai.agent.Agent].
|
|
1880
|
-
|
|
1881
|
-
You generally obtain an `AgentRun` instance by calling `async with my_agent.iter(...) as agent_run:`.
|
|
1882
|
-
|
|
1883
|
-
Once you have an instance, you can use it to iterate through the run's nodes as they execute. When an
|
|
1884
|
-
[`End`][pydantic_graph.nodes.End] is reached, the run finishes and [`result`][pydantic_ai.agent.AgentRun.result]
|
|
1885
|
-
becomes available.
|
|
1886
|
-
|
|
1887
|
-
Example:
|
|
1888
|
-
```python
|
|
1889
|
-
from pydantic_ai import Agent
|
|
1890
|
-
|
|
1891
|
-
agent = Agent('openai:gpt-4o')
|
|
1892
|
-
|
|
1893
|
-
async def main():
|
|
1894
|
-
nodes = []
|
|
1895
|
-
# Iterate through the run, recording each node along the way:
|
|
1896
|
-
async with agent.iter('What is the capital of France?') as agent_run:
|
|
1897
|
-
async for node in agent_run:
|
|
1898
|
-
nodes.append(node)
|
|
1899
|
-
print(nodes)
|
|
1900
|
-
'''
|
|
1901
|
-
[
|
|
1902
|
-
UserPromptNode(
|
|
1903
|
-
user_prompt='What is the capital of France?',
|
|
1904
|
-
instructions=None,
|
|
1905
|
-
instructions_functions=[],
|
|
1906
|
-
system_prompts=(),
|
|
1907
|
-
system_prompt_functions=[],
|
|
1908
|
-
system_prompt_dynamic_functions={},
|
|
1909
|
-
),
|
|
1910
|
-
ModelRequestNode(
|
|
1911
|
-
request=ModelRequest(
|
|
1912
|
-
parts=[
|
|
1913
|
-
UserPromptPart(
|
|
1914
|
-
content='What is the capital of France?',
|
|
1915
|
-
timestamp=datetime.datetime(...),
|
|
1916
|
-
)
|
|
1917
|
-
]
|
|
1918
|
-
)
|
|
1919
|
-
),
|
|
1920
|
-
CallToolsNode(
|
|
1921
|
-
model_response=ModelResponse(
|
|
1922
|
-
parts=[TextPart(content='Paris')],
|
|
1923
|
-
usage=Usage(
|
|
1924
|
-
requests=1, request_tokens=56, response_tokens=1, total_tokens=57
|
|
1925
|
-
),
|
|
1926
|
-
model_name='gpt-4o',
|
|
1927
|
-
timestamp=datetime.datetime(...),
|
|
1928
|
-
)
|
|
1929
|
-
),
|
|
1930
|
-
End(data=FinalResult(output='Paris')),
|
|
1931
|
-
]
|
|
1932
|
-
'''
|
|
1933
|
-
print(agent_run.result.output)
|
|
1934
|
-
#> Paris
|
|
1935
|
-
```
|
|
1936
|
-
|
|
1937
|
-
You can also manually drive the iteration using the [`next`][pydantic_ai.agent.AgentRun.next] method for
|
|
1938
|
-
more granular control.
|
|
1939
|
-
"""
|
|
1940
|
-
|
|
1941
|
-
_graph_run: GraphRun[
|
|
1942
|
-
_agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any], FinalResult[OutputDataT]
|
|
1943
|
-
]
|
|
1944
|
-
|
|
1945
|
-
@overload
|
|
1946
|
-
def _traceparent(self, *, required: Literal[False]) -> str | None: ...
|
|
1947
|
-
@overload
|
|
1948
|
-
def _traceparent(self) -> str: ...
|
|
1949
|
-
def _traceparent(self, *, required: bool = True) -> str | None:
|
|
1950
|
-
traceparent = self._graph_run._traceparent(required=False) # type: ignore[reportPrivateUsage]
|
|
1951
|
-
if traceparent is None and required: # pragma: no cover
|
|
1952
|
-
raise AttributeError('No span was created for this agent run')
|
|
1953
|
-
return traceparent
|
|
1954
|
-
|
|
1955
|
-
@property
|
|
1956
|
-
def ctx(self) -> GraphRunContext[_agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any]]:
|
|
1957
|
-
"""The current context of the agent run."""
|
|
1958
|
-
return GraphRunContext[_agent_graph.GraphAgentState, _agent_graph.GraphAgentDeps[AgentDepsT, Any]](
|
|
1959
|
-
self._graph_run.state, self._graph_run.deps
|
|
1960
|
-
)
|
|
1961
1384
|
|
|
1385
|
+
@dataclasses.dataclass(init=False)
|
|
1386
|
+
class _AgentFunctionToolset(FunctionToolset[AgentDepsT]):
|
|
1962
1387
|
@property
|
|
1963
|
-
def
|
|
1964
|
-
|
|
1965
|
-
) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
|
|
1966
|
-
"""The next node that will be run in the agent graph.
|
|
1967
|
-
|
|
1968
|
-
This is the next node that will be used during async iteration, or if a node is not passed to `self.next(...)`.
|
|
1969
|
-
"""
|
|
1970
|
-
next_node = self._graph_run.next_node
|
|
1971
|
-
if isinstance(next_node, End):
|
|
1972
|
-
return next_node
|
|
1973
|
-
if _agent_graph.is_agent_node(next_node):
|
|
1974
|
-
return next_node
|
|
1975
|
-
raise exceptions.AgentRunError(f'Unexpected node type: {type(next_node)}') # pragma: no cover
|
|
1388
|
+
def id(self) -> str:
|
|
1389
|
+
return '<agent>'
|
|
1976
1390
|
|
|
1977
1391
|
@property
|
|
1978
|
-
def
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
Once the run returns an [`End`][pydantic_graph.nodes.End] node, `result` is populated
|
|
1982
|
-
with an [`AgentRunResult`][pydantic_ai.agent.AgentRunResult].
|
|
1983
|
-
"""
|
|
1984
|
-
graph_run_result = self._graph_run.result
|
|
1985
|
-
if graph_run_result is None:
|
|
1986
|
-
return None
|
|
1987
|
-
return AgentRunResult(
|
|
1988
|
-
graph_run_result.output.output,
|
|
1989
|
-
graph_run_result.output.tool_name,
|
|
1990
|
-
graph_run_result.state,
|
|
1991
|
-
self._graph_run.deps.new_message_index,
|
|
1992
|
-
self._traceparent(required=False),
|
|
1993
|
-
)
|
|
1994
|
-
|
|
1995
|
-
def __aiter__(
|
|
1996
|
-
self,
|
|
1997
|
-
) -> AsyncIterator[_agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]]:
|
|
1998
|
-
"""Provide async-iteration over the nodes in the agent run."""
|
|
1999
|
-
return self
|
|
2000
|
-
|
|
2001
|
-
async def __anext__(
|
|
2002
|
-
self,
|
|
2003
|
-
) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
|
|
2004
|
-
"""Advance to the next node automatically based on the last returned node."""
|
|
2005
|
-
next_node = await self._graph_run.__anext__()
|
|
2006
|
-
if _agent_graph.is_agent_node(node=next_node):
|
|
2007
|
-
return next_node
|
|
2008
|
-
assert isinstance(next_node, End), f'Unexpected node type: {type(next_node)}'
|
|
2009
|
-
return next_node
|
|
2010
|
-
|
|
2011
|
-
async def next(
|
|
2012
|
-
self,
|
|
2013
|
-
node: _agent_graph.AgentNode[AgentDepsT, OutputDataT],
|
|
2014
|
-
) -> _agent_graph.AgentNode[AgentDepsT, OutputDataT] | End[FinalResult[OutputDataT]]:
|
|
2015
|
-
"""Manually drive the agent run by passing in the node you want to run next.
|
|
2016
|
-
|
|
2017
|
-
This lets you inspect or mutate the node before continuing execution, or skip certain nodes
|
|
2018
|
-
under dynamic conditions. The agent run should be stopped when you return an [`End`][pydantic_graph.nodes.End]
|
|
2019
|
-
node.
|
|
2020
|
-
|
|
2021
|
-
Example:
|
|
2022
|
-
```python
|
|
2023
|
-
from pydantic_ai import Agent
|
|
2024
|
-
from pydantic_graph import End
|
|
2025
|
-
|
|
2026
|
-
agent = Agent('openai:gpt-4o')
|
|
2027
|
-
|
|
2028
|
-
async def main():
|
|
2029
|
-
async with agent.iter('What is the capital of France?') as agent_run:
|
|
2030
|
-
next_node = agent_run.next_node # start with the first node
|
|
2031
|
-
nodes = [next_node]
|
|
2032
|
-
while not isinstance(next_node, End):
|
|
2033
|
-
next_node = await agent_run.next(next_node)
|
|
2034
|
-
nodes.append(next_node)
|
|
2035
|
-
# Once `next_node` is an End, we've finished:
|
|
2036
|
-
print(nodes)
|
|
2037
|
-
'''
|
|
2038
|
-
[
|
|
2039
|
-
UserPromptNode(
|
|
2040
|
-
user_prompt='What is the capital of France?',
|
|
2041
|
-
instructions=None,
|
|
2042
|
-
instructions_functions=[],
|
|
2043
|
-
system_prompts=(),
|
|
2044
|
-
system_prompt_functions=[],
|
|
2045
|
-
system_prompt_dynamic_functions={},
|
|
2046
|
-
),
|
|
2047
|
-
ModelRequestNode(
|
|
2048
|
-
request=ModelRequest(
|
|
2049
|
-
parts=[
|
|
2050
|
-
UserPromptPart(
|
|
2051
|
-
content='What is the capital of France?',
|
|
2052
|
-
timestamp=datetime.datetime(...),
|
|
2053
|
-
)
|
|
2054
|
-
]
|
|
2055
|
-
)
|
|
2056
|
-
),
|
|
2057
|
-
CallToolsNode(
|
|
2058
|
-
model_response=ModelResponse(
|
|
2059
|
-
parts=[TextPart(content='Paris')],
|
|
2060
|
-
usage=Usage(
|
|
2061
|
-
requests=1,
|
|
2062
|
-
request_tokens=56,
|
|
2063
|
-
response_tokens=1,
|
|
2064
|
-
total_tokens=57,
|
|
2065
|
-
),
|
|
2066
|
-
model_name='gpt-4o',
|
|
2067
|
-
timestamp=datetime.datetime(...),
|
|
2068
|
-
)
|
|
2069
|
-
),
|
|
2070
|
-
End(data=FinalResult(output='Paris')),
|
|
2071
|
-
]
|
|
2072
|
-
'''
|
|
2073
|
-
print('Final result:', agent_run.result.output)
|
|
2074
|
-
#> Final result: Paris
|
|
2075
|
-
```
|
|
2076
|
-
|
|
2077
|
-
Args:
|
|
2078
|
-
node: The node to run next in the graph.
|
|
2079
|
-
|
|
2080
|
-
Returns:
|
|
2081
|
-
The next node returned by the graph logic, or an [`End`][pydantic_graph.nodes.End] node if
|
|
2082
|
-
the run has completed.
|
|
2083
|
-
"""
|
|
2084
|
-
# Note: It might be nice to expose a synchronous interface for iteration, but we shouldn't do it
|
|
2085
|
-
# on this class, or else IDEs won't warn you if you accidentally use `for` instead of `async for` to iterate.
|
|
2086
|
-
next_node = await self._graph_run.next(node)
|
|
2087
|
-
if _agent_graph.is_agent_node(next_node):
|
|
2088
|
-
return next_node
|
|
2089
|
-
assert isinstance(next_node, End), f'Unexpected node type: {type(next_node)}'
|
|
2090
|
-
return next_node
|
|
2091
|
-
|
|
2092
|
-
def usage(self) -> _usage.Usage:
|
|
2093
|
-
"""Get usage statistics for the run so far, including token usage, model requests, and so on."""
|
|
2094
|
-
return self._graph_run.state.usage
|
|
2095
|
-
|
|
2096
|
-
def __repr__(self) -> str: # pragma: no cover
|
|
2097
|
-
result = self._graph_run.result
|
|
2098
|
-
result_repr = '<run not finished>' if result is None else repr(result.output)
|
|
2099
|
-
return f'<{type(self).__name__} result={result_repr} usage={self.usage()}>'
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
@dataclasses.dataclass
|
|
2103
|
-
class AgentRunResult(Generic[OutputDataT]):
|
|
2104
|
-
"""The final result of an agent run."""
|
|
2105
|
-
|
|
2106
|
-
output: OutputDataT
|
|
2107
|
-
"""The output data from the agent run."""
|
|
2108
|
-
|
|
2109
|
-
_output_tool_name: str | None = dataclasses.field(repr=False)
|
|
2110
|
-
_state: _agent_graph.GraphAgentState = dataclasses.field(repr=False)
|
|
2111
|
-
_new_message_index: int = dataclasses.field(repr=False)
|
|
2112
|
-
_traceparent_value: str | None = dataclasses.field(repr=False)
|
|
2113
|
-
|
|
2114
|
-
@overload
|
|
2115
|
-
def _traceparent(self, *, required: Literal[False]) -> str | None: ...
|
|
2116
|
-
@overload
|
|
2117
|
-
def _traceparent(self) -> str: ...
|
|
2118
|
-
def _traceparent(self, *, required: bool = True) -> str | None:
|
|
2119
|
-
if self._traceparent_value is None and required: # pragma: no cover
|
|
2120
|
-
raise AttributeError('No span was created for this agent run')
|
|
2121
|
-
return self._traceparent_value
|
|
2122
|
-
|
|
2123
|
-
def _set_output_tool_return(self, return_content: str) -> list[_messages.ModelMessage]:
|
|
2124
|
-
"""Set return content for the output tool.
|
|
2125
|
-
|
|
2126
|
-
Useful if you want to continue the conversation and want to set the response to the output tool call.
|
|
2127
|
-
"""
|
|
2128
|
-
if not self._output_tool_name:
|
|
2129
|
-
raise ValueError('Cannot set output tool return content when the return type is `str`.')
|
|
2130
|
-
|
|
2131
|
-
messages = self._state.message_history
|
|
2132
|
-
last_message = messages[-1]
|
|
2133
|
-
for idx, part in enumerate(last_message.parts):
|
|
2134
|
-
if isinstance(part, _messages.ToolReturnPart) and part.tool_name == self._output_tool_name:
|
|
2135
|
-
# Only do deepcopy when we have to modify
|
|
2136
|
-
copied_messages = list(messages)
|
|
2137
|
-
copied_last = deepcopy(last_message)
|
|
2138
|
-
copied_last.parts[idx].content = return_content # type: ignore[misc]
|
|
2139
|
-
copied_messages[-1] = copied_last
|
|
2140
|
-
return copied_messages
|
|
2141
|
-
|
|
2142
|
-
raise LookupError(f'No tool call found with tool name {self._output_tool_name!r}.')
|
|
2143
|
-
|
|
2144
|
-
def all_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]:
|
|
2145
|
-
"""Return the history of _messages.
|
|
2146
|
-
|
|
2147
|
-
Args:
|
|
2148
|
-
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
2149
|
-
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
2150
|
-
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
2151
|
-
not be modified.
|
|
2152
|
-
|
|
2153
|
-
Returns:
|
|
2154
|
-
List of messages.
|
|
2155
|
-
"""
|
|
2156
|
-
if output_tool_return_content is not None:
|
|
2157
|
-
return self._set_output_tool_return(output_tool_return_content)
|
|
2158
|
-
else:
|
|
2159
|
-
return self._state.message_history
|
|
2160
|
-
|
|
2161
|
-
def all_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:
|
|
2162
|
-
"""Return all messages from [`all_messages`][pydantic_ai.agent.AgentRunResult.all_messages] as JSON bytes.
|
|
2163
|
-
|
|
2164
|
-
Args:
|
|
2165
|
-
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
2166
|
-
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
2167
|
-
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
2168
|
-
not be modified.
|
|
2169
|
-
|
|
2170
|
-
Returns:
|
|
2171
|
-
JSON bytes representing the messages.
|
|
2172
|
-
"""
|
|
2173
|
-
return _messages.ModelMessagesTypeAdapter.dump_json(
|
|
2174
|
-
self.all_messages(output_tool_return_content=output_tool_return_content)
|
|
2175
|
-
)
|
|
2176
|
-
|
|
2177
|
-
def new_messages(self, *, output_tool_return_content: str | None = None) -> list[_messages.ModelMessage]:
|
|
2178
|
-
"""Return new messages associated with this run.
|
|
2179
|
-
|
|
2180
|
-
Messages from older runs are excluded.
|
|
2181
|
-
|
|
2182
|
-
Args:
|
|
2183
|
-
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
2184
|
-
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
2185
|
-
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
2186
|
-
not be modified.
|
|
2187
|
-
|
|
2188
|
-
Returns:
|
|
2189
|
-
List of new messages.
|
|
2190
|
-
"""
|
|
2191
|
-
return self.all_messages(output_tool_return_content=output_tool_return_content)[self._new_message_index :]
|
|
2192
|
-
|
|
2193
|
-
def new_messages_json(self, *, output_tool_return_content: str | None = None) -> bytes:
|
|
2194
|
-
"""Return new messages from [`new_messages`][pydantic_ai.agent.AgentRunResult.new_messages] as JSON bytes.
|
|
2195
|
-
|
|
2196
|
-
Args:
|
|
2197
|
-
output_tool_return_content: The return content of the tool call to set in the last message.
|
|
2198
|
-
This provides a convenient way to modify the content of the output tool call if you want to continue
|
|
2199
|
-
the conversation and want to set the response to the output tool call. If `None`, the last message will
|
|
2200
|
-
not be modified.
|
|
2201
|
-
|
|
2202
|
-
Returns:
|
|
2203
|
-
JSON bytes representing the new messages.
|
|
2204
|
-
"""
|
|
2205
|
-
return _messages.ModelMessagesTypeAdapter.dump_json(
|
|
2206
|
-
self.new_messages(output_tool_return_content=output_tool_return_content)
|
|
2207
|
-
)
|
|
2208
|
-
|
|
2209
|
-
def usage(self) -> _usage.Usage:
|
|
2210
|
-
"""Return the usage of the whole run."""
|
|
2211
|
-
return self._state.usage
|
|
1392
|
+
def label(self) -> str:
|
|
1393
|
+
return 'the agent'
|