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

Files changed (63) hide show
  1. pydantic_ai/__init__.py +5 -0
  2. pydantic_ai/_a2a.py +6 -4
  3. pydantic_ai/_agent_graph.py +32 -32
  4. pydantic_ai/_cli.py +3 -3
  5. pydantic_ai/_output.py +8 -0
  6. pydantic_ai/_tool_manager.py +3 -0
  7. pydantic_ai/_utils.py +7 -1
  8. pydantic_ai/ag_ui.py +25 -14
  9. pydantic_ai/{agent.py → agent/__init__.py} +217 -1026
  10. pydantic_ai/agent/abstract.py +942 -0
  11. pydantic_ai/agent/wrapper.py +227 -0
  12. pydantic_ai/builtin_tools.py +105 -0
  13. pydantic_ai/direct.py +9 -9
  14. pydantic_ai/durable_exec/__init__.py +0 -0
  15. pydantic_ai/durable_exec/temporal/__init__.py +83 -0
  16. pydantic_ai/durable_exec/temporal/_agent.py +699 -0
  17. pydantic_ai/durable_exec/temporal/_function_toolset.py +92 -0
  18. pydantic_ai/durable_exec/temporal/_logfire.py +48 -0
  19. pydantic_ai/durable_exec/temporal/_mcp_server.py +145 -0
  20. pydantic_ai/durable_exec/temporal/_model.py +168 -0
  21. pydantic_ai/durable_exec/temporal/_run_context.py +50 -0
  22. pydantic_ai/durable_exec/temporal/_toolset.py +77 -0
  23. pydantic_ai/ext/aci.py +10 -9
  24. pydantic_ai/ext/langchain.py +4 -2
  25. pydantic_ai/mcp.py +203 -75
  26. pydantic_ai/messages.py +75 -13
  27. pydantic_ai/models/__init__.py +66 -8
  28. pydantic_ai/models/anthropic.py +135 -18
  29. pydantic_ai/models/bedrock.py +16 -5
  30. pydantic_ai/models/cohere.py +11 -4
  31. pydantic_ai/models/fallback.py +4 -2
  32. pydantic_ai/models/function.py +18 -4
  33. pydantic_ai/models/gemini.py +20 -9
  34. pydantic_ai/models/google.py +53 -15
  35. pydantic_ai/models/groq.py +47 -11
  36. pydantic_ai/models/huggingface.py +26 -11
  37. pydantic_ai/models/instrumented.py +3 -1
  38. pydantic_ai/models/mcp_sampling.py +3 -1
  39. pydantic_ai/models/mistral.py +27 -17
  40. pydantic_ai/models/openai.py +97 -33
  41. pydantic_ai/models/test.py +12 -0
  42. pydantic_ai/models/wrapper.py +6 -2
  43. pydantic_ai/profiles/groq.py +23 -0
  44. pydantic_ai/profiles/openai.py +1 -1
  45. pydantic_ai/providers/google.py +7 -7
  46. pydantic_ai/providers/groq.py +2 -0
  47. pydantic_ai/result.py +21 -55
  48. pydantic_ai/run.py +357 -0
  49. pydantic_ai/tools.py +0 -1
  50. pydantic_ai/toolsets/__init__.py +2 -0
  51. pydantic_ai/toolsets/_dynamic.py +87 -0
  52. pydantic_ai/toolsets/abstract.py +23 -3
  53. pydantic_ai/toolsets/combined.py +19 -4
  54. pydantic_ai/toolsets/deferred.py +10 -2
  55. pydantic_ai/toolsets/function.py +23 -8
  56. pydantic_ai/toolsets/prefixed.py +4 -0
  57. pydantic_ai/toolsets/wrapper.py +14 -1
  58. {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/METADATA +7 -5
  59. pydantic_ai_slim-0.7.0.dist-info/RECORD +115 -0
  60. pydantic_ai_slim-0.6.1.dist-info/RECORD +0 -100
  61. {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/WHEEL +0 -0
  62. {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/entry_points.txt +0 -0
  63. {pydantic_ai_slim-0.6.1.dist-info → pydantic_ai_slim-0.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,942 @@
1
+ from __future__ import annotations as _annotations
2
+
3
+ import inspect
4
+ from abc import ABC, abstractmethod
5
+ from collections.abc import AsyncIterable, AsyncIterator, Awaitable, Iterator, Mapping, Sequence
6
+ from contextlib import AbstractAsyncContextManager, asynccontextmanager, contextmanager
7
+ from types import FrameType
8
+ from typing import TYPE_CHECKING, Any, Callable, Generic, Union, cast, overload
9
+
10
+ from typing_extensions import Self, TypeAlias, TypeIs, TypeVar
11
+
12
+ from pydantic_graph import End
13
+ from pydantic_graph._utils import get_event_loop
14
+
15
+ from .. import (
16
+ _agent_graph,
17
+ _utils,
18
+ exceptions,
19
+ messages as _messages,
20
+ models,
21
+ result,
22
+ usage as _usage,
23
+ )
24
+ from ..output import OutputDataT, OutputSpec
25
+ from ..result import AgentStream, FinalResult, StreamedRunResult
26
+ from ..run import AgentRun, AgentRunResult
27
+ from ..settings import ModelSettings
28
+ from ..tools import (
29
+ AgentDepsT,
30
+ RunContext,
31
+ Tool,
32
+ ToolFuncEither,
33
+ )
34
+ from ..toolsets import AbstractToolset
35
+ from ..usage import Usage, UsageLimits
36
+
37
+ # Re-exporting like this improves auto-import behavior in PyCharm
38
+ capture_run_messages = _agent_graph.capture_run_messages
39
+ EndStrategy = _agent_graph.EndStrategy
40
+ CallToolsNode = _agent_graph.CallToolsNode
41
+ ModelRequestNode = _agent_graph.ModelRequestNode
42
+ UserPromptNode = _agent_graph.UserPromptNode
43
+
44
+ if TYPE_CHECKING:
45
+ from fasta2a.applications import FastA2A
46
+ from fasta2a.broker import Broker
47
+ from fasta2a.schema import AgentProvider, Skill
48
+ from fasta2a.storage import Storage
49
+ from starlette.middleware import Middleware
50
+ from starlette.routing import BaseRoute, Route
51
+ from starlette.types import ExceptionHandler, Lifespan
52
+
53
+ from ..ag_ui import AGUIApp
54
+
55
+
56
+ T = TypeVar('T')
57
+ S = TypeVar('S')
58
+ NoneType = type(None)
59
+ RunOutputDataT = TypeVar('RunOutputDataT')
60
+ """Type variable for the result data of a run where `output_type` was customized on the run call."""
61
+
62
+ EventStreamHandler: TypeAlias = Callable[
63
+ [
64
+ RunContext[AgentDepsT],
65
+ AsyncIterable[Union[_messages.AgentStreamEvent, _messages.HandleResponseEvent]],
66
+ ],
67
+ Awaitable[None],
68
+ ]
69
+ """A function that receives agent [`RunContext`][pydantic_ai.tools.RunContext] and an async iterable of events from the model's streaming response and the agent's execution of tools."""
70
+
71
+
72
+ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
73
+ """Abstract superclass for [`Agent`][pydantic_ai.agent.Agent], [`WrapperAgent`][pydantic_ai.agent.WrapperAgent], and your own custom agent implementations."""
74
+
75
+ @property
76
+ @abstractmethod
77
+ def model(self) -> models.Model | models.KnownModelName | str | None:
78
+ """The default model configured for this agent."""
79
+ raise NotImplementedError
80
+
81
+ @property
82
+ @abstractmethod
83
+ def name(self) -> str | None:
84
+ """The name of the agent, used for logging.
85
+
86
+ If `None`, we try to infer the agent name from the call frame when the agent is first run.
87
+ """
88
+ raise NotImplementedError
89
+
90
+ @name.setter
91
+ @abstractmethod
92
+ def name(self, value: str | None) -> None:
93
+ """Set the name of the agent, used for logging."""
94
+ raise NotImplementedError
95
+
96
+ @property
97
+ @abstractmethod
98
+ def deps_type(self) -> type:
99
+ """The type of dependencies used by the agent."""
100
+ raise NotImplementedError
101
+
102
+ @property
103
+ @abstractmethod
104
+ def output_type(self) -> OutputSpec[OutputDataT]:
105
+ """The type of data output by agent runs, used to validate the data returned by the model, defaults to `str`."""
106
+ raise NotImplementedError
107
+
108
+ @property
109
+ @abstractmethod
110
+ def event_stream_handler(self) -> EventStreamHandler[AgentDepsT] | None:
111
+ """Optional handler for events from the model's streaming response and the agent's execution of tools."""
112
+ raise NotImplementedError
113
+
114
+ @property
115
+ @abstractmethod
116
+ def toolsets(self) -> Sequence[AbstractToolset[AgentDepsT]]:
117
+ """All toolsets registered on the agent.
118
+
119
+ Output tools are not included.
120
+ """
121
+ raise NotImplementedError
122
+
123
+ @overload
124
+ async def run(
125
+ self,
126
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
127
+ *,
128
+ output_type: None = None,
129
+ message_history: list[_messages.ModelMessage] | None = None,
130
+ model: models.Model | models.KnownModelName | str | None = None,
131
+ deps: AgentDepsT = None,
132
+ model_settings: ModelSettings | None = None,
133
+ usage_limits: _usage.UsageLimits | None = None,
134
+ usage: _usage.Usage | None = None,
135
+ infer_name: bool = True,
136
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
137
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
138
+ ) -> AgentRunResult[OutputDataT]: ...
139
+
140
+ @overload
141
+ async def run(
142
+ self,
143
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
144
+ *,
145
+ output_type: OutputSpec[RunOutputDataT],
146
+ message_history: list[_messages.ModelMessage] | None = None,
147
+ model: models.Model | models.KnownModelName | str | None = None,
148
+ deps: AgentDepsT = None,
149
+ model_settings: ModelSettings | None = None,
150
+ usage_limits: _usage.UsageLimits | None = None,
151
+ usage: _usage.Usage | None = None,
152
+ infer_name: bool = True,
153
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
154
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
155
+ ) -> AgentRunResult[RunOutputDataT]: ...
156
+
157
+ async def run(
158
+ self,
159
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
160
+ *,
161
+ output_type: OutputSpec[RunOutputDataT] | None = None,
162
+ message_history: list[_messages.ModelMessage] | None = None,
163
+ model: models.Model | models.KnownModelName | str | None = None,
164
+ deps: AgentDepsT = None,
165
+ model_settings: ModelSettings | None = None,
166
+ usage_limits: _usage.UsageLimits | None = None,
167
+ usage: _usage.Usage | None = None,
168
+ infer_name: bool = True,
169
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
170
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
171
+ ) -> AgentRunResult[Any]:
172
+ """Run the agent with a user prompt in async mode.
173
+
174
+ This method builds an internal agent graph (using system prompts, tools and output schemas) and then
175
+ runs the graph to completion. The result of the run is returned.
176
+
177
+ Example:
178
+ ```python
179
+ from pydantic_ai import Agent
180
+
181
+ agent = Agent('openai:gpt-4o')
182
+
183
+ async def main():
184
+ agent_run = await agent.run('What is the capital of France?')
185
+ print(agent_run.output)
186
+ #> The capital of France is Paris.
187
+ ```
188
+
189
+ Args:
190
+ user_prompt: User input to start/continue the conversation.
191
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
192
+ output validators since output validators would expect an argument that matches the agent's output type.
193
+ message_history: History of the conversation so far.
194
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
195
+ deps: Optional dependencies to use for this run.
196
+ model_settings: Optional settings to use for this model's request.
197
+ usage_limits: Optional limits on model request count or token usage.
198
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
199
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
200
+ toolsets: Optional additional toolsets for this run.
201
+ event_stream_handler: Optional handler for events from the model's streaming response and the agent's execution of tools to use for this run.
202
+
203
+ Returns:
204
+ The result of the run.
205
+ """
206
+ if infer_name and self.name is None:
207
+ self._infer_name(inspect.currentframe())
208
+
209
+ event_stream_handler = event_stream_handler or self.event_stream_handler
210
+
211
+ async with self.iter(
212
+ user_prompt=user_prompt,
213
+ output_type=output_type,
214
+ message_history=message_history,
215
+ model=model,
216
+ deps=deps,
217
+ model_settings=model_settings,
218
+ usage_limits=usage_limits,
219
+ usage=usage,
220
+ toolsets=toolsets,
221
+ ) as agent_run:
222
+ async for node in agent_run:
223
+ if event_stream_handler is not None and (
224
+ self.is_model_request_node(node) or self.is_call_tools_node(node)
225
+ ):
226
+ async with node.stream(agent_run.ctx) as stream:
227
+ await event_stream_handler(_agent_graph.build_run_context(agent_run.ctx), stream)
228
+
229
+ assert agent_run.result is not None, 'The graph run did not finish properly'
230
+ return agent_run.result
231
+
232
+ @overload
233
+ def run_sync(
234
+ self,
235
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
236
+ *,
237
+ output_type: None = None,
238
+ message_history: list[_messages.ModelMessage] | None = None,
239
+ model: models.Model | models.KnownModelName | str | None = None,
240
+ deps: AgentDepsT = None,
241
+ model_settings: ModelSettings | None = None,
242
+ usage_limits: _usage.UsageLimits | None = None,
243
+ usage: _usage.Usage | None = None,
244
+ infer_name: bool = True,
245
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
246
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
247
+ ) -> AgentRunResult[OutputDataT]: ...
248
+
249
+ @overload
250
+ def run_sync(
251
+ self,
252
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
253
+ *,
254
+ output_type: OutputSpec[RunOutputDataT],
255
+ message_history: list[_messages.ModelMessage] | None = None,
256
+ model: models.Model | models.KnownModelName | str | None = None,
257
+ deps: AgentDepsT = None,
258
+ model_settings: ModelSettings | None = None,
259
+ usage_limits: _usage.UsageLimits | None = None,
260
+ usage: _usage.Usage | None = None,
261
+ infer_name: bool = True,
262
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
263
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
264
+ ) -> AgentRunResult[RunOutputDataT]: ...
265
+
266
+ def run_sync(
267
+ self,
268
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
269
+ *,
270
+ output_type: OutputSpec[RunOutputDataT] | None = None,
271
+ message_history: list[_messages.ModelMessage] | None = None,
272
+ model: models.Model | models.KnownModelName | str | None = None,
273
+ deps: AgentDepsT = None,
274
+ model_settings: ModelSettings | None = None,
275
+ usage_limits: _usage.UsageLimits | None = None,
276
+ usage: _usage.Usage | None = None,
277
+ infer_name: bool = True,
278
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
279
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
280
+ ) -> AgentRunResult[Any]:
281
+ """Synchronously run the agent with a user prompt.
282
+
283
+ This is a convenience method that wraps [`self.run`][pydantic_ai.agent.AbstractAgent.run] with `loop.run_until_complete(...)`.
284
+ You therefore can't use this method inside async code or if there's an active event loop.
285
+
286
+ Example:
287
+ ```python
288
+ from pydantic_ai import Agent
289
+
290
+ agent = Agent('openai:gpt-4o')
291
+
292
+ result_sync = agent.run_sync('What is the capital of Italy?')
293
+ print(result_sync.output)
294
+ #> The capital of Italy is Rome.
295
+ ```
296
+
297
+ Args:
298
+ user_prompt: User input to start/continue the conversation.
299
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
300
+ output validators since output validators would expect an argument that matches the agent's output type.
301
+ message_history: History of the conversation so far.
302
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
303
+ deps: Optional dependencies to use for this run.
304
+ model_settings: Optional settings to use for this model's request.
305
+ usage_limits: Optional limits on model request count or token usage.
306
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
307
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
308
+ toolsets: Optional additional toolsets for this run.
309
+ event_stream_handler: Optional handler for events from the model's streaming response and the agent's execution of tools to use for this run.
310
+
311
+ Returns:
312
+ The result of the run.
313
+ """
314
+ if infer_name and self.name is None:
315
+ self._infer_name(inspect.currentframe())
316
+
317
+ return get_event_loop().run_until_complete(
318
+ self.run(
319
+ user_prompt,
320
+ output_type=output_type,
321
+ message_history=message_history,
322
+ model=model,
323
+ deps=deps,
324
+ model_settings=model_settings,
325
+ usage_limits=usage_limits,
326
+ usage=usage,
327
+ infer_name=False,
328
+ toolsets=toolsets,
329
+ event_stream_handler=event_stream_handler,
330
+ )
331
+ )
332
+
333
+ @overload
334
+ def run_stream(
335
+ self,
336
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
337
+ *,
338
+ output_type: None = None,
339
+ message_history: list[_messages.ModelMessage] | None = None,
340
+ model: models.Model | models.KnownModelName | str | None = None,
341
+ deps: AgentDepsT = None,
342
+ model_settings: ModelSettings | None = None,
343
+ usage_limits: _usage.UsageLimits | None = None,
344
+ usage: _usage.Usage | None = None,
345
+ infer_name: bool = True,
346
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
347
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
348
+ ) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, OutputDataT]]: ...
349
+
350
+ @overload
351
+ def run_stream(
352
+ self,
353
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
354
+ *,
355
+ output_type: OutputSpec[RunOutputDataT],
356
+ message_history: list[_messages.ModelMessage] | None = None,
357
+ model: models.Model | models.KnownModelName | str | None = None,
358
+ deps: AgentDepsT = None,
359
+ model_settings: ModelSettings | None = None,
360
+ usage_limits: _usage.UsageLimits | None = None,
361
+ usage: _usage.Usage | None = None,
362
+ infer_name: bool = True,
363
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
364
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
365
+ ) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, RunOutputDataT]]: ...
366
+
367
+ @asynccontextmanager
368
+ async def run_stream( # noqa C901
369
+ self,
370
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
371
+ *,
372
+ output_type: OutputSpec[RunOutputDataT] | None = None,
373
+ message_history: list[_messages.ModelMessage] | None = None,
374
+ model: models.Model | models.KnownModelName | str | None = None,
375
+ deps: AgentDepsT = None,
376
+ model_settings: ModelSettings | None = None,
377
+ usage_limits: _usage.UsageLimits | None = None,
378
+ usage: _usage.Usage | None = None,
379
+ infer_name: bool = True,
380
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
381
+ event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
382
+ ) -> AsyncIterator[result.StreamedRunResult[AgentDepsT, Any]]:
383
+ """Run the agent with a user prompt in async streaming mode.
384
+
385
+ This method builds an internal agent graph (using system prompts, tools and output schemas) and then
386
+ runs the graph until the model produces output matching the `output_type`, for example text or structured data.
387
+ At this point, a streaming run result object is yielded from which you can stream the output as it comes in,
388
+ and -- once this output has completed streaming -- get the complete output, message history, and usage.
389
+
390
+ As this method will consider the first output matching the `output_type` to be the final output,
391
+ it will stop running the agent graph and will not execute any tool calls made by the model after this "final" output.
392
+ If you want to always run the agent graph to completion and stream events and output at the same time,
393
+ use [`agent.run()`][pydantic_ai.agent.AbstractAgent.run] with an `event_stream_handler` or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead.
394
+
395
+ Example:
396
+ ```python
397
+ from pydantic_ai import Agent
398
+
399
+ agent = Agent('openai:gpt-4o')
400
+
401
+ async def main():
402
+ async with agent.run_stream('What is the capital of the UK?') as response:
403
+ print(await response.get_output())
404
+ #> The capital of the UK is London.
405
+ ```
406
+
407
+ Args:
408
+ user_prompt: User input to start/continue the conversation.
409
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
410
+ output validators since output validators would expect an argument that matches the agent's output type.
411
+ message_history: History of the conversation so far.
412
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
413
+ deps: Optional dependencies to use for this run.
414
+ model_settings: Optional settings to use for this model's request.
415
+ usage_limits: Optional limits on model request count or token usage.
416
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
417
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
418
+ toolsets: Optional additional toolsets for this run.
419
+ event_stream_handler: Optional handler for events from the model's streaming response and the agent's execution of tools to use for this run.
420
+ It will receive all the events up until the final result is found, which you can then read or stream from inside the context manager.
421
+ Note that it does _not_ receive any events after the final result is found.
422
+
423
+ Returns:
424
+ The result of the run.
425
+ """
426
+ if infer_name and self.name is None:
427
+ # f_back because `asynccontextmanager` adds one frame
428
+ if frame := inspect.currentframe(): # pragma: no branch
429
+ self._infer_name(frame.f_back)
430
+
431
+ event_stream_handler = event_stream_handler or self.event_stream_handler
432
+
433
+ yielded = False
434
+ async with self.iter(
435
+ user_prompt,
436
+ output_type=output_type,
437
+ message_history=message_history,
438
+ model=model,
439
+ deps=deps,
440
+ model_settings=model_settings,
441
+ usage_limits=usage_limits,
442
+ usage=usage,
443
+ infer_name=False,
444
+ toolsets=toolsets,
445
+ ) as agent_run:
446
+ first_node = agent_run.next_node # start with the first node
447
+ assert isinstance(first_node, _agent_graph.UserPromptNode) # the first node should be a user prompt node
448
+ node = first_node
449
+ while True:
450
+ if self.is_model_request_node(node):
451
+ graph_ctx = agent_run.ctx
452
+ async with node.stream(graph_ctx) as stream:
453
+ final_result_event = None
454
+
455
+ async def stream_to_final(stream: AgentStream) -> AsyncIterator[_messages.AgentStreamEvent]:
456
+ nonlocal final_result_event
457
+ async for event in stream:
458
+ yield event
459
+ if isinstance(event, _messages.FinalResultEvent):
460
+ final_result_event = event
461
+ break
462
+
463
+ if event_stream_handler is not None:
464
+ await event_stream_handler(
465
+ _agent_graph.build_run_context(graph_ctx), stream_to_final(stream)
466
+ )
467
+ else:
468
+ async for _ in stream_to_final(stream):
469
+ pass
470
+
471
+ if final_result_event is not None:
472
+ final_result = FinalResult(
473
+ stream, final_result_event.tool_name, final_result_event.tool_call_id
474
+ )
475
+ if yielded:
476
+ raise exceptions.AgentRunError('Agent run produced final results') # pragma: no cover
477
+ yielded = True
478
+
479
+ messages = graph_ctx.state.message_history.copy()
480
+
481
+ async def on_complete() -> None:
482
+ """Called when the stream has completed.
483
+
484
+ The model response will have been added to messages by now
485
+ by `StreamedRunResult._marked_completed`.
486
+ """
487
+ last_message = messages[-1]
488
+ assert isinstance(last_message, _messages.ModelResponse)
489
+ tool_calls = [
490
+ part for part in last_message.parts if isinstance(part, _messages.ToolCallPart)
491
+ ]
492
+
493
+ parts: list[_messages.ModelRequestPart] = []
494
+ async for _event in _agent_graph.process_function_tools(
495
+ graph_ctx.deps.tool_manager,
496
+ tool_calls,
497
+ final_result,
498
+ graph_ctx,
499
+ parts,
500
+ ):
501
+ pass
502
+ if parts:
503
+ messages.append(_messages.ModelRequest(parts))
504
+
505
+ yield StreamedRunResult(
506
+ messages,
507
+ graph_ctx.deps.new_message_index,
508
+ stream,
509
+ on_complete,
510
+ )
511
+ break
512
+ elif self.is_call_tools_node(node) and event_stream_handler is not None:
513
+ async with node.stream(agent_run.ctx) as stream:
514
+ await event_stream_handler(_agent_graph.build_run_context(agent_run.ctx), stream)
515
+
516
+ next_node = await agent_run.next(node)
517
+ if not isinstance(next_node, _agent_graph.AgentNode):
518
+ raise exceptions.AgentRunError( # pragma: no cover
519
+ 'Should have produced a StreamedRunResult before getting here'
520
+ )
521
+ node = cast(_agent_graph.AgentNode[Any, Any], next_node)
522
+
523
+ if not yielded:
524
+ raise exceptions.AgentRunError('Agent run finished without producing a final result') # pragma: no cover
525
+
526
+ @overload
527
+ def iter(
528
+ self,
529
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
530
+ *,
531
+ output_type: None = None,
532
+ message_history: list[_messages.ModelMessage] | None = None,
533
+ model: models.Model | models.KnownModelName | str | None = None,
534
+ deps: AgentDepsT = None,
535
+ model_settings: ModelSettings | None = None,
536
+ usage_limits: _usage.UsageLimits | None = None,
537
+ usage: _usage.Usage | None = None,
538
+ infer_name: bool = True,
539
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
540
+ ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ...
541
+
542
+ @overload
543
+ def iter(
544
+ self,
545
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
546
+ *,
547
+ output_type: OutputSpec[RunOutputDataT],
548
+ message_history: list[_messages.ModelMessage] | None = None,
549
+ model: models.Model | models.KnownModelName | str | None = None,
550
+ deps: AgentDepsT = None,
551
+ model_settings: ModelSettings | None = None,
552
+ usage_limits: _usage.UsageLimits | None = None,
553
+ usage: _usage.Usage | None = None,
554
+ infer_name: bool = True,
555
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
556
+ ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ...
557
+
558
+ @asynccontextmanager
559
+ @abstractmethod
560
+ async def iter(
561
+ self,
562
+ user_prompt: str | Sequence[_messages.UserContent] | None = None,
563
+ *,
564
+ output_type: OutputSpec[RunOutputDataT] | None = None,
565
+ message_history: list[_messages.ModelMessage] | None = None,
566
+ model: models.Model | models.KnownModelName | str | None = None,
567
+ deps: AgentDepsT = None,
568
+ model_settings: ModelSettings | None = None,
569
+ usage_limits: _usage.UsageLimits | None = None,
570
+ usage: _usage.Usage | None = None,
571
+ infer_name: bool = True,
572
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
573
+ ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]:
574
+ """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed.
575
+
576
+ This method builds an internal agent graph (using system prompts, tools and output schemas) and then returns an
577
+ `AgentRun` object. The `AgentRun` can be used to async-iterate over the nodes of the graph as they are
578
+ executed. This is the API to use if you want to consume the outputs coming from each LLM model response, or the
579
+ stream of events coming from the execution of tools.
580
+
581
+ The `AgentRun` also provides methods to access the full message history, new messages, and usage statistics,
582
+ and the final result of the run once it has completed.
583
+
584
+ For more details, see the documentation of `AgentRun`.
585
+
586
+ Example:
587
+ ```python
588
+ from pydantic_ai import Agent
589
+
590
+ agent = Agent('openai:gpt-4o')
591
+
592
+ async def main():
593
+ nodes = []
594
+ async with agent.iter('What is the capital of France?') as agent_run:
595
+ async for node in agent_run:
596
+ nodes.append(node)
597
+ print(nodes)
598
+ '''
599
+ [
600
+ UserPromptNode(
601
+ user_prompt='What is the capital of France?',
602
+ instructions=None,
603
+ instructions_functions=[],
604
+ system_prompts=(),
605
+ system_prompt_functions=[],
606
+ system_prompt_dynamic_functions={},
607
+ ),
608
+ ModelRequestNode(
609
+ request=ModelRequest(
610
+ parts=[
611
+ UserPromptPart(
612
+ content='What is the capital of France?',
613
+ timestamp=datetime.datetime(...),
614
+ )
615
+ ]
616
+ )
617
+ ),
618
+ CallToolsNode(
619
+ model_response=ModelResponse(
620
+ parts=[TextPart(content='The capital of France is Paris.')],
621
+ usage=Usage(
622
+ requests=1, request_tokens=56, response_tokens=7, total_tokens=63
623
+ ),
624
+ model_name='gpt-4o',
625
+ timestamp=datetime.datetime(...),
626
+ )
627
+ ),
628
+ End(data=FinalResult(output='The capital of France is Paris.')),
629
+ ]
630
+ '''
631
+ print(agent_run.result.output)
632
+ #> The capital of France is Paris.
633
+ ```
634
+
635
+ Args:
636
+ user_prompt: User input to start/continue the conversation.
637
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
638
+ output validators since output validators would expect an argument that matches the agent's output type.
639
+ message_history: History of the conversation so far.
640
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
641
+ deps: Optional dependencies to use for this run.
642
+ model_settings: Optional settings to use for this model's request.
643
+ usage_limits: Optional limits on model request count or token usage.
644
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
645
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
646
+ toolsets: Optional additional toolsets for this run.
647
+
648
+ Returns:
649
+ The result of the run.
650
+ """
651
+ raise NotImplementedError
652
+ yield
653
+
654
+ @contextmanager
655
+ @abstractmethod
656
+ def override(
657
+ self,
658
+ *,
659
+ deps: AgentDepsT | _utils.Unset = _utils.UNSET,
660
+ model: models.Model | models.KnownModelName | str | _utils.Unset = _utils.UNSET,
661
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | _utils.Unset = _utils.UNSET,
662
+ tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] | _utils.Unset = _utils.UNSET,
663
+ ) -> Iterator[None]:
664
+ """Context manager to temporarily override agent dependencies, model, toolsets, or tools.
665
+
666
+ This is particularly useful when testing.
667
+ You can find an example of this [here](../testing.md#overriding-model-via-pytest-fixtures).
668
+
669
+ Args:
670
+ deps: The dependencies to use instead of the dependencies passed to the agent run.
671
+ model: The model to use instead of the model passed to the agent run.
672
+ toolsets: The toolsets to use instead of the toolsets passed to the agent constructor and agent run.
673
+ tools: The tools to use instead of the tools registered with the agent.
674
+ """
675
+ raise NotImplementedError
676
+ yield
677
+
678
+ def _infer_name(self, function_frame: FrameType | None) -> None:
679
+ """Infer the agent name from the call frame.
680
+
681
+ Usage should be `self._infer_name(inspect.currentframe())`.
682
+ """
683
+ assert self.name is None, 'Name already set'
684
+ if function_frame is not None: # pragma: no branch
685
+ if parent_frame := function_frame.f_back: # pragma: no branch
686
+ for name, item in parent_frame.f_locals.items():
687
+ if item is self:
688
+ self.name = name
689
+ return
690
+ if parent_frame.f_locals != parent_frame.f_globals: # pragma: no branch
691
+ # if we couldn't find the agent in locals and globals are a different dict, try globals
692
+ for name, item in parent_frame.f_globals.items():
693
+ if item is self:
694
+ self.name = name
695
+ return
696
+
697
+ @staticmethod
698
+ def is_model_request_node(
699
+ node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
700
+ ) -> TypeIs[_agent_graph.ModelRequestNode[T, S]]:
701
+ """Check if the node is a `ModelRequestNode`, narrowing the type if it is.
702
+
703
+ This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
704
+ """
705
+ return isinstance(node, _agent_graph.ModelRequestNode)
706
+
707
+ @staticmethod
708
+ def is_call_tools_node(
709
+ node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
710
+ ) -> TypeIs[_agent_graph.CallToolsNode[T, S]]:
711
+ """Check if the node is a `CallToolsNode`, narrowing the type if it is.
712
+
713
+ This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
714
+ """
715
+ return isinstance(node, _agent_graph.CallToolsNode)
716
+
717
+ @staticmethod
718
+ def is_user_prompt_node(
719
+ node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
720
+ ) -> TypeIs[_agent_graph.UserPromptNode[T, S]]:
721
+ """Check if the node is a `UserPromptNode`, narrowing the type if it is.
722
+
723
+ This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
724
+ """
725
+ return isinstance(node, _agent_graph.UserPromptNode)
726
+
727
+ @staticmethod
728
+ def is_end_node(
729
+ node: _agent_graph.AgentNode[T, S] | End[result.FinalResult[S]],
730
+ ) -> TypeIs[End[result.FinalResult[S]]]:
731
+ """Check if the node is a `End`, narrowing the type if it is.
732
+
733
+ This method preserves the generic parameters while narrowing the type, unlike a direct call to `isinstance`.
734
+ """
735
+ return isinstance(node, End)
736
+
737
+ @abstractmethod
738
+ async def __aenter__(self) -> AbstractAgent[AgentDepsT, OutputDataT]:
739
+ raise NotImplementedError
740
+
741
+ @abstractmethod
742
+ async def __aexit__(self, *args: Any) -> bool | None:
743
+ raise NotImplementedError
744
+
745
+ def to_ag_ui(
746
+ self,
747
+ *,
748
+ # Agent.iter parameters
749
+ output_type: OutputSpec[OutputDataT] | None = None,
750
+ model: models.Model | models.KnownModelName | str | None = None,
751
+ deps: AgentDepsT = None,
752
+ model_settings: ModelSettings | None = None,
753
+ usage_limits: UsageLimits | None = None,
754
+ usage: Usage | None = None,
755
+ infer_name: bool = True,
756
+ toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
757
+ # Starlette
758
+ debug: bool = False,
759
+ routes: Sequence[BaseRoute] | None = None,
760
+ middleware: Sequence[Middleware] | None = None,
761
+ exception_handlers: Mapping[Any, ExceptionHandler] | None = None,
762
+ on_startup: Sequence[Callable[[], Any]] | None = None,
763
+ on_shutdown: Sequence[Callable[[], Any]] | None = None,
764
+ lifespan: Lifespan[AGUIApp[AgentDepsT, OutputDataT]] | None = None,
765
+ ) -> AGUIApp[AgentDepsT, OutputDataT]:
766
+ """Returns an ASGI application that handles every AG-UI request by running the agent.
767
+
768
+ Note that the `deps` will be the same for each request, with the exception of the AG-UI state that's
769
+ injected into the `state` field of a `deps` object that implements the [`StateHandler`][pydantic_ai.ag_ui.StateHandler] protocol.
770
+ To provide different `deps` for each request (e.g. based on the authenticated user),
771
+ use [`pydantic_ai.ag_ui.run_ag_ui`][pydantic_ai.ag_ui.run_ag_ui] or
772
+ [`pydantic_ai.ag_ui.handle_ag_ui_request`][pydantic_ai.ag_ui.handle_ag_ui_request] instead.
773
+
774
+ Example:
775
+ ```python
776
+ from pydantic_ai import Agent
777
+
778
+ agent = Agent('openai:gpt-4o')
779
+ app = agent.to_ag_ui()
780
+ ```
781
+
782
+ The `app` is an ASGI application that can be used with any ASGI server.
783
+
784
+ To run the application, you can use the following command:
785
+
786
+ ```bash
787
+ uvicorn app:app --host 0.0.0.0 --port 8000
788
+ ```
789
+
790
+ See [AG-UI docs](../ag-ui.md) for more information.
791
+
792
+ Args:
793
+ output_type: Custom output type to use for this run, `output_type` may only be used if the agent has
794
+ no output validators since output validators would expect an argument that matches the agent's
795
+ output type.
796
+ model: Optional model to use for this run, required if `model` was not set when creating the agent.
797
+ deps: Optional dependencies to use for this run.
798
+ model_settings: Optional settings to use for this model's request.
799
+ usage_limits: Optional limits on model request count or token usage.
800
+ usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
801
+ infer_name: Whether to try to infer the agent name from the call frame if it's not set.
802
+ toolsets: Optional additional toolsets for this run.
803
+
804
+ debug: Boolean indicating if debug tracebacks should be returned on errors.
805
+ routes: A list of routes to serve incoming HTTP and WebSocket requests.
806
+ middleware: A list of middleware to run for every request. A starlette application will always
807
+ automatically include two middleware classes. `ServerErrorMiddleware` is added as the very
808
+ outermost middleware, to handle any uncaught errors occurring anywhere in the entire stack.
809
+ `ExceptionMiddleware` is added as the very innermost middleware, to deal with handled
810
+ exception cases occurring in the routing or endpoints.
811
+ exception_handlers: A mapping of either integer status codes, or exception class types onto
812
+ callables which handle the exceptions. Exception handler callables should be of the form
813
+ `handler(request, exc) -> response` and may be either standard functions, or async functions.
814
+ on_startup: A list of callables to run on application startup. Startup handler callables do not
815
+ take any arguments, and may be either standard functions, or async functions.
816
+ on_shutdown: A list of callables to run on application shutdown. Shutdown handler callables do
817
+ not take any arguments, and may be either standard functions, or async functions.
818
+ lifespan: A lifespan context function, which can be used to perform startup and shutdown tasks.
819
+ This is a newer style that replaces the `on_startup` and `on_shutdown` handlers. Use one or
820
+ the other, not both.
821
+
822
+ Returns:
823
+ An ASGI application for running Pydantic AI agents with AG-UI protocol support.
824
+ """
825
+ from ..ag_ui import AGUIApp
826
+
827
+ return AGUIApp(
828
+ agent=self,
829
+ # Agent.iter parameters
830
+ output_type=output_type,
831
+ model=model,
832
+ deps=deps,
833
+ model_settings=model_settings,
834
+ usage_limits=usage_limits,
835
+ usage=usage,
836
+ infer_name=infer_name,
837
+ toolsets=toolsets,
838
+ # Starlette
839
+ debug=debug,
840
+ routes=routes,
841
+ middleware=middleware,
842
+ exception_handlers=exception_handlers,
843
+ on_startup=on_startup,
844
+ on_shutdown=on_shutdown,
845
+ lifespan=lifespan,
846
+ )
847
+
848
+ def to_a2a(
849
+ self,
850
+ *,
851
+ storage: Storage | None = None,
852
+ broker: Broker | None = None,
853
+ # Agent card
854
+ name: str | None = None,
855
+ url: str = 'http://localhost:8000',
856
+ version: str = '1.0.0',
857
+ description: str | None = None,
858
+ provider: AgentProvider | None = None,
859
+ skills: list[Skill] | None = None,
860
+ # Starlette
861
+ debug: bool = False,
862
+ routes: Sequence[Route] | None = None,
863
+ middleware: Sequence[Middleware] | None = None,
864
+ exception_handlers: dict[Any, ExceptionHandler] | None = None,
865
+ lifespan: Lifespan[FastA2A] | None = None,
866
+ ) -> FastA2A:
867
+ """Convert the agent to a FastA2A application.
868
+
869
+ Example:
870
+ ```python
871
+ from pydantic_ai import Agent
872
+
873
+ agent = Agent('openai:gpt-4o')
874
+ app = agent.to_a2a()
875
+ ```
876
+
877
+ The `app` is an ASGI application that can be used with any ASGI server.
878
+
879
+ To run the application, you can use the following command:
880
+
881
+ ```bash
882
+ uvicorn app:app --host 0.0.0.0 --port 8000
883
+ ```
884
+ """
885
+ from .._a2a import agent_to_a2a
886
+
887
+ return agent_to_a2a(
888
+ self,
889
+ storage=storage,
890
+ broker=broker,
891
+ name=name,
892
+ url=url,
893
+ version=version,
894
+ description=description,
895
+ provider=provider,
896
+ skills=skills,
897
+ debug=debug,
898
+ routes=routes,
899
+ middleware=middleware,
900
+ exception_handlers=exception_handlers,
901
+ lifespan=lifespan,
902
+ )
903
+
904
+ async def to_cli(self: Self, deps: AgentDepsT = None, prog_name: str = 'pydantic-ai') -> None:
905
+ """Run the agent in a CLI chat interface.
906
+
907
+ Args:
908
+ deps: The dependencies to pass to the agent.
909
+ prog_name: The name of the program to use for the CLI. Defaults to 'pydantic-ai'.
910
+
911
+ Example:
912
+ ```python {title="agent_to_cli.py" test="skip"}
913
+ from pydantic_ai import Agent
914
+
915
+ agent = Agent('openai:gpt-4o', instructions='You always respond in Italian.')
916
+
917
+ async def main():
918
+ await agent.to_cli()
919
+ ```
920
+ """
921
+ from rich.console import Console
922
+
923
+ from pydantic_ai._cli import run_chat
924
+
925
+ await run_chat(stream=True, agent=self, deps=deps, console=Console(), code_theme='monokai', prog_name=prog_name)
926
+
927
+ def to_cli_sync(self: Self, deps: AgentDepsT = None, prog_name: str = 'pydantic-ai') -> None:
928
+ """Run the agent in a CLI chat interface with the non-async interface.
929
+
930
+ Args:
931
+ deps: The dependencies to pass to the agent.
932
+ prog_name: The name of the program to use for the CLI. Defaults to 'pydantic-ai'.
933
+
934
+ ```python {title="agent_to_cli_sync.py" test="skip"}
935
+ from pydantic_ai import Agent
936
+
937
+ agent = Agent('openai:gpt-4o', instructions='You always respond in Italian.')
938
+ agent.to_cli_sync()
939
+ agent.to_cli_sync(prog_name='assistant')
940
+ ```
941
+ """
942
+ return get_event_loop().run_until_complete(self.to_cli(deps=deps, prog_name=prog_name))