pydantic-ai-slim 1.2.1__py3-none-any.whl → 1.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pydantic_ai/__init__.py +6 -0
- pydantic_ai/_agent_graph.py +67 -20
- pydantic_ai/_cli.py +2 -2
- pydantic_ai/_output.py +20 -12
- pydantic_ai/_run_context.py +6 -2
- pydantic_ai/_utils.py +26 -8
- pydantic_ai/ag_ui.py +50 -696
- pydantic_ai/agent/__init__.py +13 -25
- pydantic_ai/agent/abstract.py +146 -9
- pydantic_ai/builtin_tools.py +106 -4
- pydantic_ai/direct.py +16 -4
- pydantic_ai/durable_exec/dbos/_agent.py +3 -0
- pydantic_ai/durable_exec/prefect/_agent.py +3 -0
- pydantic_ai/durable_exec/temporal/__init__.py +11 -0
- pydantic_ai/durable_exec/temporal/_agent.py +3 -0
- pydantic_ai/durable_exec/temporal/_function_toolset.py +23 -72
- pydantic_ai/durable_exec/temporal/_mcp_server.py +30 -30
- pydantic_ai/durable_exec/temporal/_run_context.py +7 -2
- pydantic_ai/durable_exec/temporal/_toolset.py +67 -3
- pydantic_ai/exceptions.py +6 -1
- pydantic_ai/mcp.py +1 -22
- pydantic_ai/messages.py +46 -8
- pydantic_ai/models/__init__.py +87 -38
- pydantic_ai/models/anthropic.py +132 -11
- pydantic_ai/models/bedrock.py +4 -4
- pydantic_ai/models/cohere.py +0 -7
- pydantic_ai/models/gemini.py +9 -2
- pydantic_ai/models/google.py +26 -23
- pydantic_ai/models/groq.py +13 -5
- pydantic_ai/models/huggingface.py +2 -2
- pydantic_ai/models/openai.py +251 -52
- pydantic_ai/models/outlines.py +563 -0
- pydantic_ai/models/test.py +6 -3
- pydantic_ai/profiles/openai.py +7 -0
- pydantic_ai/providers/__init__.py +25 -12
- pydantic_ai/providers/anthropic.py +2 -2
- pydantic_ai/providers/bedrock.py +60 -16
- pydantic_ai/providers/gateway.py +60 -72
- pydantic_ai/providers/google.py +91 -24
- pydantic_ai/providers/openrouter.py +3 -0
- pydantic_ai/providers/outlines.py +40 -0
- pydantic_ai/providers/ovhcloud.py +95 -0
- pydantic_ai/result.py +173 -8
- pydantic_ai/run.py +40 -24
- pydantic_ai/settings.py +8 -0
- pydantic_ai/tools.py +10 -6
- pydantic_ai/toolsets/fastmcp.py +215 -0
- pydantic_ai/ui/__init__.py +16 -0
- pydantic_ai/ui/_adapter.py +386 -0
- pydantic_ai/ui/_event_stream.py +591 -0
- pydantic_ai/ui/_messages_builder.py +28 -0
- pydantic_ai/ui/ag_ui/__init__.py +9 -0
- pydantic_ai/ui/ag_ui/_adapter.py +187 -0
- pydantic_ai/ui/ag_ui/_event_stream.py +236 -0
- pydantic_ai/ui/ag_ui/app.py +148 -0
- pydantic_ai/ui/vercel_ai/__init__.py +16 -0
- pydantic_ai/ui/vercel_ai/_adapter.py +199 -0
- pydantic_ai/ui/vercel_ai/_event_stream.py +187 -0
- pydantic_ai/ui/vercel_ai/_utils.py +16 -0
- pydantic_ai/ui/vercel_ai/request_types.py +275 -0
- pydantic_ai/ui/vercel_ai/response_types.py +230 -0
- pydantic_ai/usage.py +13 -2
- {pydantic_ai_slim-1.2.1.dist-info → pydantic_ai_slim-1.10.0.dist-info}/METADATA +23 -5
- {pydantic_ai_slim-1.2.1.dist-info → pydantic_ai_slim-1.10.0.dist-info}/RECORD +67 -49
- {pydantic_ai_slim-1.2.1.dist-info → pydantic_ai_slim-1.10.0.dist-info}/WHEEL +0 -0
- {pydantic_ai_slim-1.2.1.dist-info → pydantic_ai_slim-1.10.0.dist-info}/entry_points.txt +0 -0
- {pydantic_ai_slim-1.2.1.dist-info → pydantic_ai_slim-1.10.0.dist-info}/licenses/LICENSE +0 -0
pydantic_ai/agent/__init__.py
CHANGED
|
@@ -15,7 +15,6 @@ from pydantic.json_schema import GenerateJsonSchema
|
|
|
15
15
|
from typing_extensions import Self, TypeVar, deprecated
|
|
16
16
|
|
|
17
17
|
from pydantic_ai._instrumentation import DEFAULT_INSTRUMENTATION_VERSION, InstrumentationNames
|
|
18
|
-
from pydantic_graph import Graph
|
|
19
18
|
|
|
20
19
|
from .. import (
|
|
21
20
|
_agent_graph,
|
|
@@ -41,7 +40,6 @@ from ..builtin_tools import AbstractBuiltinTool
|
|
|
41
40
|
from ..models.instrumented import InstrumentationSettings, InstrumentedModel, instrument_model
|
|
42
41
|
from ..output import OutputDataT, OutputSpec
|
|
43
42
|
from ..profiles import ModelProfile
|
|
44
|
-
from ..result import FinalResult
|
|
45
43
|
from ..run import AgentRun, AgentRunResult
|
|
46
44
|
from ..settings import ModelSettings, merge_model_settings
|
|
47
45
|
from ..tools import (
|
|
@@ -542,6 +540,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
542
540
|
"""
|
|
543
541
|
if infer_name and self.name is None:
|
|
544
542
|
self._infer_name(inspect.currentframe())
|
|
543
|
+
|
|
545
544
|
model_used = self._get_model(model)
|
|
546
545
|
del model
|
|
547
546
|
|
|
@@ -565,9 +564,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
565
564
|
tool_manager = ToolManager[AgentDepsT](toolset)
|
|
566
565
|
|
|
567
566
|
# Build the graph
|
|
568
|
-
graph
|
|
569
|
-
_agent_graph.build_agent_graph(self.name, self._deps_type, output_type_)
|
|
570
|
-
)
|
|
567
|
+
graph = _agent_graph.build_agent_graph(self.name, self._deps_type, output_type_)
|
|
571
568
|
|
|
572
569
|
# Build the initial state
|
|
573
570
|
usage = usage or _usage.RunUsage()
|
|
@@ -607,16 +604,7 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
607
604
|
else:
|
|
608
605
|
instrumentation_settings = None
|
|
609
606
|
tracer = NoOpTracer()
|
|
610
|
-
|
|
611
|
-
# Deduplicate builtin tools passed to the agent and the run based on type
|
|
612
|
-
builtin_tools = list(
|
|
613
|
-
{
|
|
614
|
-
**({type(tool): tool for tool in self._builtin_tools or []}),
|
|
615
|
-
**({type(tool): tool for tool in builtin_tools}),
|
|
616
|
-
}.values()
|
|
617
|
-
)
|
|
618
|
-
else:
|
|
619
|
-
builtin_tools = list(self._builtin_tools)
|
|
607
|
+
|
|
620
608
|
graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT, RunOutputDataT](
|
|
621
609
|
user_deps=deps,
|
|
622
610
|
prompt=user_prompt,
|
|
@@ -629,14 +617,14 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
629
617
|
output_schema=output_schema,
|
|
630
618
|
output_validators=output_validators,
|
|
631
619
|
history_processors=self.history_processors,
|
|
632
|
-
builtin_tools=builtin_tools,
|
|
620
|
+
builtin_tools=[*self._builtin_tools, *(builtin_tools or [])],
|
|
633
621
|
tool_manager=tool_manager,
|
|
634
622
|
tracer=tracer,
|
|
635
623
|
get_instructions=get_instructions,
|
|
636
624
|
instrumentation_settings=instrumentation_settings,
|
|
637
625
|
)
|
|
638
626
|
|
|
639
|
-
|
|
627
|
+
user_prompt_node = _agent_graph.UserPromptNode[AgentDepsT](
|
|
640
628
|
user_prompt=user_prompt,
|
|
641
629
|
deferred_tool_results=deferred_tool_results,
|
|
642
630
|
instructions=instructions_literal,
|
|
@@ -662,14 +650,14 @@ class Agent(AbstractAgent[AgentDepsT, OutputDataT]):
|
|
|
662
650
|
)
|
|
663
651
|
|
|
664
652
|
try:
|
|
665
|
-
async with
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
653
|
+
async with graph.iter(
|
|
654
|
+
inputs=user_prompt_node,
|
|
655
|
+
state=state,
|
|
656
|
+
deps=graph_deps,
|
|
657
|
+
span=use_span(run_span) if run_span.is_recording() else None,
|
|
658
|
+
infer_name=False,
|
|
659
|
+
) as graph_run:
|
|
660
|
+
async with toolset:
|
|
673
661
|
agent_run = AgentRun(graph_run)
|
|
674
662
|
yield agent_run
|
|
675
663
|
if (final_result := agent_run.result) is not None and run_span.is_recording():
|
pydantic_ai/agent/abstract.py
CHANGED
|
@@ -12,7 +12,6 @@ import anyio
|
|
|
12
12
|
from typing_extensions import Self, TypeIs, TypeVar
|
|
13
13
|
|
|
14
14
|
from pydantic_graph import End
|
|
15
|
-
from pydantic_graph._utils import get_event_loop
|
|
16
15
|
|
|
17
16
|
from .. import (
|
|
18
17
|
_agent_graph,
|
|
@@ -49,7 +48,7 @@ if TYPE_CHECKING:
|
|
|
49
48
|
from starlette.routing import BaseRoute, Route
|
|
50
49
|
from starlette.types import ExceptionHandler, Lifespan
|
|
51
50
|
|
|
52
|
-
from
|
|
51
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
53
52
|
|
|
54
53
|
|
|
55
54
|
T = TypeVar('T')
|
|
@@ -335,7 +334,7 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
335
334
|
if infer_name and self.name is None:
|
|
336
335
|
self._infer_name(inspect.currentframe())
|
|
337
336
|
|
|
338
|
-
return get_event_loop().run_until_complete(
|
|
337
|
+
return _utils.get_event_loop().run_until_complete(
|
|
339
338
|
self.run(
|
|
340
339
|
user_prompt,
|
|
341
340
|
output_type=output_type,
|
|
@@ -581,6 +580,133 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
581
580
|
if not yielded:
|
|
582
581
|
raise exceptions.AgentRunError('Agent run finished without producing a final result') # pragma: no cover
|
|
583
582
|
|
|
583
|
+
@overload
|
|
584
|
+
def run_stream_sync(
|
|
585
|
+
self,
|
|
586
|
+
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
587
|
+
*,
|
|
588
|
+
output_type: None = None,
|
|
589
|
+
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
590
|
+
deferred_tool_results: DeferredToolResults | None = None,
|
|
591
|
+
model: models.Model | models.KnownModelName | str | None = None,
|
|
592
|
+
deps: AgentDepsT = None,
|
|
593
|
+
model_settings: ModelSettings | None = None,
|
|
594
|
+
usage_limits: _usage.UsageLimits | None = None,
|
|
595
|
+
usage: _usage.RunUsage | None = None,
|
|
596
|
+
infer_name: bool = True,
|
|
597
|
+
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
598
|
+
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
|
|
599
|
+
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
|
|
600
|
+
) -> result.StreamedRunResultSync[AgentDepsT, OutputDataT]: ...
|
|
601
|
+
|
|
602
|
+
@overload
|
|
603
|
+
def run_stream_sync(
|
|
604
|
+
self,
|
|
605
|
+
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
606
|
+
*,
|
|
607
|
+
output_type: OutputSpec[RunOutputDataT],
|
|
608
|
+
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
609
|
+
deferred_tool_results: DeferredToolResults | None = None,
|
|
610
|
+
model: models.Model | models.KnownModelName | str | None = None,
|
|
611
|
+
deps: AgentDepsT = None,
|
|
612
|
+
model_settings: ModelSettings | None = None,
|
|
613
|
+
usage_limits: _usage.UsageLimits | None = None,
|
|
614
|
+
usage: _usage.RunUsage | None = None,
|
|
615
|
+
infer_name: bool = True,
|
|
616
|
+
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
617
|
+
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
|
|
618
|
+
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
|
|
619
|
+
) -> result.StreamedRunResultSync[AgentDepsT, RunOutputDataT]: ...
|
|
620
|
+
|
|
621
|
+
def run_stream_sync(
|
|
622
|
+
self,
|
|
623
|
+
user_prompt: str | Sequence[_messages.UserContent] | None = None,
|
|
624
|
+
*,
|
|
625
|
+
output_type: OutputSpec[RunOutputDataT] | None = None,
|
|
626
|
+
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
627
|
+
deferred_tool_results: DeferredToolResults | None = None,
|
|
628
|
+
model: models.Model | models.KnownModelName | str | None = None,
|
|
629
|
+
deps: AgentDepsT = None,
|
|
630
|
+
model_settings: ModelSettings | None = None,
|
|
631
|
+
usage_limits: _usage.UsageLimits | None = None,
|
|
632
|
+
usage: _usage.RunUsage | None = None,
|
|
633
|
+
infer_name: bool = True,
|
|
634
|
+
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
635
|
+
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
|
|
636
|
+
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
|
|
637
|
+
) -> result.StreamedRunResultSync[AgentDepsT, Any]:
|
|
638
|
+
"""Run the agent with a user prompt in sync streaming mode.
|
|
639
|
+
|
|
640
|
+
This is a convenience method that wraps [`run_stream()`][pydantic_ai.agent.AbstractAgent.run_stream] with `loop.run_until_complete(...)`.
|
|
641
|
+
You therefore can't use this method inside async code or if there's an active event loop.
|
|
642
|
+
|
|
643
|
+
This method builds an internal agent graph (using system prompts, tools and output schemas) and then
|
|
644
|
+
runs the graph until the model produces output matching the `output_type`, for example text or structured data.
|
|
645
|
+
At this point, a streaming run result object is yielded from which you can stream the output as it comes in,
|
|
646
|
+
and -- once this output has completed streaming -- get the complete output, message history, and usage.
|
|
647
|
+
|
|
648
|
+
As this method will consider the first output matching the `output_type` to be the final output,
|
|
649
|
+
it will stop running the agent graph and will not execute any tool calls made by the model after this "final" output.
|
|
650
|
+
If you want to always run the agent graph to completion and stream events and output at the same time,
|
|
651
|
+
use [`agent.run()`][pydantic_ai.agent.AbstractAgent.run] with an `event_stream_handler` or [`agent.iter()`][pydantic_ai.agent.AbstractAgent.iter] instead.
|
|
652
|
+
|
|
653
|
+
Example:
|
|
654
|
+
```python
|
|
655
|
+
from pydantic_ai import Agent
|
|
656
|
+
|
|
657
|
+
agent = Agent('openai:gpt-4o')
|
|
658
|
+
|
|
659
|
+
def main():
|
|
660
|
+
response = agent.run_stream_sync('What is the capital of the UK?')
|
|
661
|
+
print(response.get_output())
|
|
662
|
+
#> The capital of the UK is London.
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
Args:
|
|
666
|
+
user_prompt: User input to start/continue the conversation.
|
|
667
|
+
output_type: Custom output type to use for this run, `output_type` may only be used if the agent has no
|
|
668
|
+
output validators since output validators would expect an argument that matches the agent's output type.
|
|
669
|
+
message_history: History of the conversation so far.
|
|
670
|
+
deferred_tool_results: Optional results for deferred tool calls in the message history.
|
|
671
|
+
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
672
|
+
deps: Optional dependencies to use for this run.
|
|
673
|
+
model_settings: Optional settings to use for this model's request.
|
|
674
|
+
usage_limits: Optional limits on model request count or token usage.
|
|
675
|
+
usage: Optional usage to start with, useful for resuming a conversation or agents used in tools.
|
|
676
|
+
infer_name: Whether to try to infer the agent name from the call frame if it's not set.
|
|
677
|
+
toolsets: Optional additional toolsets for this run.
|
|
678
|
+
builtin_tools: Optional additional builtin tools for this run.
|
|
679
|
+
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.
|
|
680
|
+
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.
|
|
681
|
+
Note that it does _not_ receive any events after the final result is found.
|
|
682
|
+
|
|
683
|
+
Returns:
|
|
684
|
+
The result of the run.
|
|
685
|
+
"""
|
|
686
|
+
if infer_name and self.name is None:
|
|
687
|
+
self._infer_name(inspect.currentframe())
|
|
688
|
+
|
|
689
|
+
async def _consume_stream():
|
|
690
|
+
async with self.run_stream(
|
|
691
|
+
user_prompt,
|
|
692
|
+
output_type=output_type,
|
|
693
|
+
message_history=message_history,
|
|
694
|
+
deferred_tool_results=deferred_tool_results,
|
|
695
|
+
model=model,
|
|
696
|
+
deps=deps,
|
|
697
|
+
model_settings=model_settings,
|
|
698
|
+
usage_limits=usage_limits,
|
|
699
|
+
usage=usage,
|
|
700
|
+
infer_name=infer_name,
|
|
701
|
+
toolsets=toolsets,
|
|
702
|
+
builtin_tools=builtin_tools,
|
|
703
|
+
event_stream_handler=event_stream_handler,
|
|
704
|
+
) as stream_result:
|
|
705
|
+
yield stream_result
|
|
706
|
+
|
|
707
|
+
async_result = _utils.get_event_loop().run_until_complete(anext(_consume_stream()))
|
|
708
|
+
return result.StreamedRunResultSync(async_result)
|
|
709
|
+
|
|
584
710
|
@overload
|
|
585
711
|
def run_stream_events(
|
|
586
712
|
self,
|
|
@@ -654,6 +780,9 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
654
780
|
PartStartEvent(index=0, part=TextPart(content='The capital of ')),
|
|
655
781
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
656
782
|
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
|
|
783
|
+
PartEndEvent(
|
|
784
|
+
index=0, part=TextPart(content='The capital of France is Paris. ')
|
|
785
|
+
),
|
|
657
786
|
AgentRunResultEvent(
|
|
658
787
|
result=AgentRunResult(output='The capital of France is Paris. ')
|
|
659
788
|
),
|
|
@@ -683,6 +812,9 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
683
812
|
An async iterable of stream events `AgentStreamEvent` and finally a `AgentRunResultEvent` with the final
|
|
684
813
|
run result.
|
|
685
814
|
"""
|
|
815
|
+
if infer_name and self.name is None:
|
|
816
|
+
self._infer_name(inspect.currentframe())
|
|
817
|
+
|
|
686
818
|
# unfortunately this hack of returning a generator rather than defining it right here is
|
|
687
819
|
# required to allow overloads of this method to work in python's typing system, or at least with pyright
|
|
688
820
|
# or at least I couldn't make it work without
|
|
@@ -696,7 +828,6 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
696
828
|
model_settings=model_settings,
|
|
697
829
|
usage_limits=usage_limits,
|
|
698
830
|
usage=usage,
|
|
699
|
-
infer_name=infer_name,
|
|
700
831
|
toolsets=toolsets,
|
|
701
832
|
builtin_tools=builtin_tools,
|
|
702
833
|
)
|
|
@@ -713,7 +844,6 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
713
844
|
model_settings: ModelSettings | None = None,
|
|
714
845
|
usage_limits: _usage.UsageLimits | None = None,
|
|
715
846
|
usage: _usage.RunUsage | None = None,
|
|
716
|
-
infer_name: bool = True,
|
|
717
847
|
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
|
|
718
848
|
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
|
|
719
849
|
) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]:
|
|
@@ -739,7 +869,7 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
739
869
|
model_settings=model_settings,
|
|
740
870
|
usage_limits=usage_limits,
|
|
741
871
|
usage=usage,
|
|
742
|
-
infer_name=
|
|
872
|
+
infer_name=False,
|
|
743
873
|
toolsets=toolsets,
|
|
744
874
|
builtin_tools=builtin_tools,
|
|
745
875
|
event_stream_handler=event_stream_handler,
|
|
@@ -989,11 +1119,14 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
989
1119
|
async def __aexit__(self, *args: Any) -> bool | None:
|
|
990
1120
|
raise NotImplementedError
|
|
991
1121
|
|
|
1122
|
+
# TODO (v2): Remove in favor of using `AGUIApp` directly -- we don't have `to_temporal()` or `to_vercel_ai()` either.
|
|
992
1123
|
def to_ag_ui(
|
|
993
1124
|
self,
|
|
994
1125
|
*,
|
|
995
1126
|
# Agent.iter parameters
|
|
996
1127
|
output_type: OutputSpec[OutputDataT] | None = None,
|
|
1128
|
+
message_history: Sequence[_messages.ModelMessage] | None = None,
|
|
1129
|
+
deferred_tool_results: DeferredToolResults | None = None,
|
|
997
1130
|
model: models.Model | models.KnownModelName | str | None = None,
|
|
998
1131
|
deps: AgentDepsT = None,
|
|
999
1132
|
model_settings: ModelSettings | None = None,
|
|
@@ -1034,12 +1167,14 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
1034
1167
|
uvicorn app:app --host 0.0.0.0 --port 8000
|
|
1035
1168
|
```
|
|
1036
1169
|
|
|
1037
|
-
See [AG-UI docs](../ag-ui.md) for more information.
|
|
1170
|
+
See [AG-UI docs](../ui/ag-ui.md) for more information.
|
|
1038
1171
|
|
|
1039
1172
|
Args:
|
|
1040
1173
|
output_type: Custom output type to use for this run, `output_type` may only be used if the agent has
|
|
1041
1174
|
no output validators since output validators would expect an argument that matches the agent's
|
|
1042
1175
|
output type.
|
|
1176
|
+
message_history: History of the conversation so far.
|
|
1177
|
+
deferred_tool_results: Optional results for deferred tool calls in the message history.
|
|
1043
1178
|
model: Optional model to use for this run, required if `model` was not set when creating the agent.
|
|
1044
1179
|
deps: Optional dependencies to use for this run.
|
|
1045
1180
|
model_settings: Optional settings to use for this model's request.
|
|
@@ -1069,12 +1204,14 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
1069
1204
|
Returns:
|
|
1070
1205
|
An ASGI application for running Pydantic AI agents with AG-UI protocol support.
|
|
1071
1206
|
"""
|
|
1072
|
-
from
|
|
1207
|
+
from pydantic_ai.ui.ag_ui.app import AGUIApp
|
|
1073
1208
|
|
|
1074
1209
|
return AGUIApp(
|
|
1075
1210
|
agent=self,
|
|
1076
1211
|
# Agent.iter parameters
|
|
1077
1212
|
output_type=output_type,
|
|
1213
|
+
message_history=message_history,
|
|
1214
|
+
deferred_tool_results=deferred_tool_results,
|
|
1078
1215
|
model=model,
|
|
1079
1216
|
deps=deps,
|
|
1080
1217
|
model_settings=model_settings,
|
|
@@ -1206,6 +1343,6 @@ class AbstractAgent(Generic[AgentDepsT, OutputDataT], ABC):
|
|
|
1206
1343
|
agent.to_cli_sync(prog_name='assistant')
|
|
1207
1344
|
```
|
|
1208
1345
|
"""
|
|
1209
|
-
return get_event_loop().run_until_complete(
|
|
1346
|
+
return _utils.get_event_loop().run_until_complete(
|
|
1210
1347
|
self.to_cli(deps=deps, prog_name=prog_name, message_history=message_history)
|
|
1211
1348
|
)
|
pydantic_ai/builtin_tools.py
CHANGED
|
@@ -2,13 +2,12 @@ from __future__ import annotations as _annotations
|
|
|
2
2
|
|
|
3
3
|
from abc import ABC
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import
|
|
5
|
+
from typing import Annotated, Any, Literal, Union
|
|
6
6
|
|
|
7
|
+
import pydantic
|
|
8
|
+
from pydantic_core import core_schema
|
|
7
9
|
from typing_extensions import TypedDict
|
|
8
10
|
|
|
9
|
-
if TYPE_CHECKING:
|
|
10
|
-
from .builtin_tools import AbstractBuiltinTool
|
|
11
|
-
|
|
12
11
|
__all__ = (
|
|
13
12
|
'AbstractBuiltinTool',
|
|
14
13
|
'WebSearchTool',
|
|
@@ -17,8 +16,11 @@ __all__ = (
|
|
|
17
16
|
'UrlContextTool',
|
|
18
17
|
'ImageGenerationTool',
|
|
19
18
|
'MemoryTool',
|
|
19
|
+
'MCPServerTool',
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
_BUILTIN_TOOL_TYPES: dict[str, type[AbstractBuiltinTool]] = {}
|
|
23
|
+
|
|
22
24
|
|
|
23
25
|
@dataclass(kw_only=True)
|
|
24
26
|
class AbstractBuiltinTool(ABC):
|
|
@@ -32,6 +34,34 @@ class AbstractBuiltinTool(ABC):
|
|
|
32
34
|
kind: str = 'unknown_builtin_tool'
|
|
33
35
|
"""Built-in tool identifier, this should be available on all built-in tools as a discriminator."""
|
|
34
36
|
|
|
37
|
+
@property
|
|
38
|
+
def unique_id(self) -> str:
|
|
39
|
+
"""A unique identifier for the builtin tool.
|
|
40
|
+
|
|
41
|
+
If multiple instances of the same builtin tool can be passed to the model, subclasses should override this property to allow them to be distinguished.
|
|
42
|
+
"""
|
|
43
|
+
return self.kind
|
|
44
|
+
|
|
45
|
+
def __init_subclass__(cls, **kwargs: Any) -> None:
|
|
46
|
+
super().__init_subclass__(**kwargs)
|
|
47
|
+
_BUILTIN_TOOL_TYPES[cls.kind] = cls
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def __get_pydantic_core_schema__(
|
|
51
|
+
cls, _source_type: Any, handler: pydantic.GetCoreSchemaHandler
|
|
52
|
+
) -> core_schema.CoreSchema:
|
|
53
|
+
if cls is not AbstractBuiltinTool:
|
|
54
|
+
return handler(cls)
|
|
55
|
+
|
|
56
|
+
tools = _BUILTIN_TOOL_TYPES.values()
|
|
57
|
+
if len(tools) == 1: # pragma: no cover
|
|
58
|
+
tools_type = next(iter(tools))
|
|
59
|
+
else:
|
|
60
|
+
tools_annotated = [Annotated[tool, pydantic.Tag(tool.kind)] for tool in tools]
|
|
61
|
+
tools_type = Annotated[Union[tuple(tools_annotated)], pydantic.Discriminator(_tool_discriminator)] # noqa: UP007
|
|
62
|
+
|
|
63
|
+
return handler(tools_type)
|
|
64
|
+
|
|
35
65
|
|
|
36
66
|
@dataclass(kw_only=True)
|
|
37
67
|
class WebSearchTool(AbstractBuiltinTool):
|
|
@@ -120,6 +150,7 @@ class WebSearchUserLocation(TypedDict, total=False):
|
|
|
120
150
|
"""The timezone of the user's location."""
|
|
121
151
|
|
|
122
152
|
|
|
153
|
+
@dataclass(kw_only=True)
|
|
123
154
|
class CodeExecutionTool(AbstractBuiltinTool):
|
|
124
155
|
"""A builtin tool that allows your agent to execute code.
|
|
125
156
|
|
|
@@ -134,6 +165,7 @@ class CodeExecutionTool(AbstractBuiltinTool):
|
|
|
134
165
|
"""The kind of tool."""
|
|
135
166
|
|
|
136
167
|
|
|
168
|
+
@dataclass(kw_only=True)
|
|
137
169
|
class UrlContextTool(AbstractBuiltinTool):
|
|
138
170
|
"""Allows your agent to access contents from URLs.
|
|
139
171
|
|
|
@@ -227,6 +259,7 @@ class ImageGenerationTool(AbstractBuiltinTool):
|
|
|
227
259
|
"""The kind of tool."""
|
|
228
260
|
|
|
229
261
|
|
|
262
|
+
@dataclass(kw_only=True)
|
|
230
263
|
class MemoryTool(AbstractBuiltinTool):
|
|
231
264
|
"""A builtin tool that allows your agent to use memory.
|
|
232
265
|
|
|
@@ -237,3 +270,72 @@ class MemoryTool(AbstractBuiltinTool):
|
|
|
237
270
|
|
|
238
271
|
kind: str = 'memory'
|
|
239
272
|
"""The kind of tool."""
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
@dataclass(kw_only=True)
|
|
276
|
+
class MCPServerTool(AbstractBuiltinTool):
|
|
277
|
+
"""A builtin tool that allows your agent to use MCP servers.
|
|
278
|
+
|
|
279
|
+
Supported by:
|
|
280
|
+
|
|
281
|
+
* OpenAI Responses
|
|
282
|
+
* Anthropic
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
id: str
|
|
286
|
+
"""A unique identifier for the MCP server."""
|
|
287
|
+
|
|
288
|
+
url: str
|
|
289
|
+
"""The URL of the MCP server to use.
|
|
290
|
+
|
|
291
|
+
For OpenAI Responses, it is possible to use `connector_id` by providing it as `x-openai-connector:<connector_id>`.
|
|
292
|
+
"""
|
|
293
|
+
|
|
294
|
+
authorization_token: str | None = None
|
|
295
|
+
"""Authorization header to use when making requests to the MCP server.
|
|
296
|
+
|
|
297
|
+
Supported by:
|
|
298
|
+
|
|
299
|
+
* OpenAI Responses
|
|
300
|
+
* Anthropic
|
|
301
|
+
"""
|
|
302
|
+
|
|
303
|
+
description: str | None = None
|
|
304
|
+
"""A description of the MCP server.
|
|
305
|
+
|
|
306
|
+
Supported by:
|
|
307
|
+
|
|
308
|
+
* OpenAI Responses
|
|
309
|
+
"""
|
|
310
|
+
|
|
311
|
+
allowed_tools: list[str] | None = None
|
|
312
|
+
"""A list of tools that the MCP server can use.
|
|
313
|
+
|
|
314
|
+
Supported by:
|
|
315
|
+
|
|
316
|
+
* OpenAI Responses
|
|
317
|
+
* Anthropic
|
|
318
|
+
"""
|
|
319
|
+
|
|
320
|
+
headers: dict[str, str] | None = None
|
|
321
|
+
"""Optional HTTP headers to send to the MCP server.
|
|
322
|
+
|
|
323
|
+
Use for authentication or other purposes.
|
|
324
|
+
|
|
325
|
+
Supported by:
|
|
326
|
+
|
|
327
|
+
* OpenAI Responses
|
|
328
|
+
"""
|
|
329
|
+
|
|
330
|
+
kind: str = 'mcp_server'
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def unique_id(self) -> str:
|
|
334
|
+
return ':'.join([self.kind, self.id])
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _tool_discriminator(tool_data: dict[str, Any] | AbstractBuiltinTool) -> str:
|
|
338
|
+
if isinstance(tool_data, dict):
|
|
339
|
+
return tool_data.get('kind', AbstractBuiltinTool.kind)
|
|
340
|
+
else:
|
|
341
|
+
return tool_data.kind
|
pydantic_ai/direct.py
CHANGED
|
@@ -50,7 +50,7 @@ async def model_request(
|
|
|
50
50
|
|
|
51
51
|
async def main():
|
|
52
52
|
model_response = await model_request(
|
|
53
|
-
'anthropic:claude-
|
|
53
|
+
'anthropic:claude-haiku-4-5',
|
|
54
54
|
[ModelRequest.user_text_prompt('What is the capital of France?')] # (1)!
|
|
55
55
|
)
|
|
56
56
|
print(model_response)
|
|
@@ -58,7 +58,7 @@ async def model_request(
|
|
|
58
58
|
ModelResponse(
|
|
59
59
|
parts=[TextPart(content='The capital of France is Paris.')],
|
|
60
60
|
usage=RequestUsage(input_tokens=56, output_tokens=7),
|
|
61
|
-
model_name='claude-
|
|
61
|
+
model_name='claude-haiku-4-5',
|
|
62
62
|
timestamp=datetime.datetime(...),
|
|
63
63
|
)
|
|
64
64
|
'''
|
|
@@ -103,7 +103,7 @@ def model_request_sync(
|
|
|
103
103
|
from pydantic_ai.direct import model_request_sync
|
|
104
104
|
|
|
105
105
|
model_response = model_request_sync(
|
|
106
|
-
'anthropic:claude-
|
|
106
|
+
'anthropic:claude-haiku-4-5',
|
|
107
107
|
[ModelRequest.user_text_prompt('What is the capital of France?')] # (1)!
|
|
108
108
|
)
|
|
109
109
|
print(model_response)
|
|
@@ -111,7 +111,7 @@ def model_request_sync(
|
|
|
111
111
|
ModelResponse(
|
|
112
112
|
parts=[TextPart(content='The capital of France is Paris.')],
|
|
113
113
|
usage=RequestUsage(input_tokens=56, output_tokens=7),
|
|
114
|
-
model_name='claude-
|
|
114
|
+
model_name='claude-haiku-4-5',
|
|
115
115
|
timestamp=datetime.datetime(...),
|
|
116
116
|
)
|
|
117
117
|
'''
|
|
@@ -172,6 +172,12 @@ def model_request_stream(
|
|
|
172
172
|
index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
|
|
173
173
|
),
|
|
174
174
|
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
|
|
175
|
+
PartEndEvent(
|
|
176
|
+
index=0,
|
|
177
|
+
part=TextPart(
|
|
178
|
+
content='Albert Einstein was a German-born theoretical physicist.'
|
|
179
|
+
),
|
|
180
|
+
),
|
|
175
181
|
]
|
|
176
182
|
'''
|
|
177
183
|
```
|
|
@@ -229,6 +235,12 @@ def model_request_stream_sync(
|
|
|
229
235
|
index=0, delta=TextPartDelta(content_delta='a German-born theoretical ')
|
|
230
236
|
),
|
|
231
237
|
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='physicist.')),
|
|
238
|
+
PartEndEvent(
|
|
239
|
+
index=0,
|
|
240
|
+
part=TextPart(
|
|
241
|
+
content='Albert Einstein was a German-born theoretical physicist.'
|
|
242
|
+
),
|
|
243
|
+
),
|
|
232
244
|
]
|
|
233
245
|
'''
|
|
234
246
|
```
|
|
@@ -640,6 +640,9 @@ class DBOSAgent(WrapperAgent[AgentDepsT, OutputDataT], DBOSConfiguredInstance):
|
|
|
640
640
|
PartStartEvent(index=0, part=TextPart(content='The capital of ')),
|
|
641
641
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
642
642
|
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
|
|
643
|
+
PartEndEvent(
|
|
644
|
+
index=0, part=TextPart(content='The capital of France is Paris. ')
|
|
645
|
+
),
|
|
643
646
|
AgentRunResultEvent(
|
|
644
647
|
result=AgentRunResult(output='The capital of France is Paris. ')
|
|
645
648
|
),
|
|
@@ -598,6 +598,9 @@ class PrefectAgent(WrapperAgent[AgentDepsT, OutputDataT]):
|
|
|
598
598
|
PartStartEvent(index=0, part=TextPart(content='The capital of ')),
|
|
599
599
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
600
600
|
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
|
|
601
|
+
PartEndEvent(
|
|
602
|
+
index=0, part=TextPart(content='The capital of France is Paris. ')
|
|
603
|
+
),
|
|
601
604
|
AgentRunResultEvent(
|
|
602
605
|
result=AgentRunResult(output='The capital of France is Paris. ')
|
|
603
606
|
),
|
|
@@ -36,6 +36,17 @@ __all__ = [
|
|
|
36
36
|
'TemporalWrapperToolset',
|
|
37
37
|
]
|
|
38
38
|
|
|
39
|
+
# We need eagerly import the anyio backends or it will happens inside workflow code and temporal has issues
|
|
40
|
+
# Note: It's difficult to add a test that covers this because pytest presumably does these imports itself
|
|
41
|
+
# when you have a @pytest.mark.anyio somewhere.
|
|
42
|
+
# I suppose we could add a test that runs a python script in a separate process, but I have not done that...
|
|
43
|
+
import anyio._backends._asyncio # pyright: ignore[reportUnusedImport]
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
import anyio._backends._trio # noqa F401 # pyright: ignore[reportUnusedImport]
|
|
47
|
+
except ImportError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
39
50
|
|
|
40
51
|
class PydanticAIPlugin(ClientPlugin, WorkerPlugin):
|
|
41
52
|
"""Temporal client and worker plugin for Pydantic AI."""
|
|
@@ -669,6 +669,9 @@ class TemporalAgent(WrapperAgent[AgentDepsT, OutputDataT]):
|
|
|
669
669
|
PartStartEvent(index=0, part=TextPart(content='The capital of ')),
|
|
670
670
|
FinalResultEvent(tool_name=None, tool_call_id=None),
|
|
671
671
|
PartDeltaEvent(index=0, delta=TextPartDelta(content_delta='France is Paris. ')),
|
|
672
|
+
PartEndEvent(
|
|
673
|
+
index=0, part=TextPart(content='The capital of France is Paris. ')
|
|
674
|
+
),
|
|
672
675
|
AgentRunResultEvent(
|
|
673
676
|
result=AgentRunResult(output='The capital of France is Paris. ')
|
|
674
677
|
),
|