diff --git a/pydantic_ai_slim/pydantic_ai/agent/__init__.py b/pydantic_ai_slim/pydantic_ai/agent/__init__.py index 72c256e9c4..a3ec7024c6 100644 --- a/pydantic_ai_slim/pydantic_ai/agent/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/agent/__init__.py @@ -424,6 +424,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -441,6 +442,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -458,6 +460,7 @@ async def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. @@ -530,6 +533,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -601,7 +605,16 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None: else: instrumentation_settings = None tracer = NoOpTracer() - + if builtin_tools: + # Deduplicate builtin tools passed to the agent and the run based on type + builtin_tools = list( + { + **({type(tool): tool for tool in self._builtin_tools or []}), + **({type(tool): tool for tool in builtin_tools}), + }.values() + ) + else: + builtin_tools = list(self._builtin_tools) graph_deps = _agent_graph.GraphAgentDeps[AgentDepsT, RunOutputDataT]( user_deps=deps, prompt=user_prompt, @@ -614,7 +627,7 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None: output_schema=output_schema, output_validators=output_validators, history_processors=self.history_processors, - builtin_tools=list(self._builtin_tools), + builtin_tools=builtin_tools, tool_manager=tool_manager, tracer=tracer, get_instructions=get_instructions, diff --git a/pydantic_ai_slim/pydantic_ai/agent/abstract.py b/pydantic_ai_slim/pydantic_ai/agent/abstract.py index fdd21b8065..aff5ff1693 100644 --- a/pydantic_ai_slim/pydantic_ai/agent/abstract.py +++ b/pydantic_ai_slim/pydantic_ai/agent/abstract.py @@ -23,6 +23,7 @@ usage as _usage, ) from .._tool_manager import ToolManager +from ..builtin_tools import AbstractBuiltinTool from ..output import OutputDataT, OutputSpec from ..result import AgentStream, FinalResult, StreamedRunResult from ..run import AgentRun, AgentRunResult @@ -135,6 +136,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -153,6 +155,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -170,6 +173,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[Any]: """Run the agent with a user prompt in async mode. @@ -203,6 +207,7 @@ async def main(): infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. 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. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -223,6 +228,7 @@ async def main(): usage_limits=usage_limits, usage=usage, toolsets=toolsets, + builtin_tools=builtin_tools, ) as agent_run: async for node in agent_run: if event_stream_handler is not None and ( @@ -249,6 +255,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -267,6 +274,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -284,6 +292,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[Any]: """Synchronously run the agent with a user prompt. @@ -316,6 +325,7 @@ def run_sync( infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. 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. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -336,6 +346,7 @@ def run_sync( usage=usage, infer_name=False, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, ) ) @@ -355,6 +366,7 @@ def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -373,6 +385,7 @@ def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -391,6 +404,7 @@ async def run_stream( # noqa C901 usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AsyncIterator[result.StreamedRunResult[AgentDepsT, Any]]: """Run the agent with a user prompt in async streaming mode. @@ -430,6 +444,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. 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. 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. Note that it does _not_ receive any events after the final result is found. @@ -457,6 +472,7 @@ async def main(): usage=usage, infer_name=False, toolsets=toolsets, + builtin_tools=builtin_tools, ) as agent_run: first_node = agent_run.next_node # start with the first node assert isinstance(first_node, _agent_graph.UserPromptNode) # the first node should be a user prompt node @@ -567,6 +583,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -584,6 +601,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -602,6 +620,7 @@ async def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. @@ -674,6 +693,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. diff --git a/pydantic_ai_slim/pydantic_ai/agent/wrapper.py b/pydantic_ai_slim/pydantic_ai/agent/wrapper.py index ba735f0907..93183a935b 100644 --- a/pydantic_ai_slim/pydantic_ai/agent/wrapper.py +++ b/pydantic_ai_slim/pydantic_ai/agent/wrapper.py @@ -10,6 +10,7 @@ models, usage as _usage, ) +from ..builtin_tools import AbstractBuiltinTool from ..output import OutputDataT, OutputSpec from ..run import AgentRun from ..settings import ModelSettings @@ -81,6 +82,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -98,6 +100,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -115,6 +118,7 @@ async def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. @@ -187,6 +191,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -203,6 +208,7 @@ async def main(): usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, ) as run: yield run diff --git a/pydantic_ai_slim/pydantic_ai/builtin_tools.py b/pydantic_ai_slim/pydantic_ai/builtin_tools.py index f313bf7e83..d15c587a6c 100644 --- a/pydantic_ai_slim/pydantic_ai/builtin_tools.py +++ b/pydantic_ai_slim/pydantic_ai/builtin_tools.py @@ -2,10 +2,13 @@ from abc import ABC from dataclasses import dataclass -from typing import Literal +from typing import TYPE_CHECKING, Literal from typing_extensions import TypedDict +if TYPE_CHECKING: + from .builtin_tools import AbstractBuiltinTool + __all__ = ( 'AbstractBuiltinTool', 'WebSearchTool', diff --git a/pydantic_ai_slim/pydantic_ai/durable_exec/dbos/_agent.py b/pydantic_ai_slim/pydantic_ai/durable_exec/dbos/_agent.py index d7d4987d8f..cdb19ea6fc 100644 --- a/pydantic_ai_slim/pydantic_ai/durable_exec/dbos/_agent.py +++ b/pydantic_ai_slim/pydantic_ai/durable_exec/dbos/_agent.py @@ -14,8 +14,9 @@ models, usage as _usage, ) -from pydantic_ai.agent import AbstractAgent, AgentRun, AgentRunResult, EventStreamHandler, RunOutputDataT, WrapperAgent -from pydantic_ai.agent.abstract import Instructions +from pydantic_ai.agent import AbstractAgent, AgentRun, AgentRunResult, EventStreamHandler, WrapperAgent +from pydantic_ai.agent.abstract import Instructions, RunOutputDataT +from pydantic_ai.builtin_tools import AbstractBuiltinTool from pydantic_ai.exceptions import UserError from pydantic_ai.models import Model from pydantic_ai.output import OutputDataT, OutputSpec @@ -120,6 +121,7 @@ async def wrapped_run_workflow( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -136,6 +138,7 @@ async def wrapped_run_workflow( usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, **_deprecated_kwargs, ) @@ -157,6 +160,7 @@ def wrapped_run_sync_workflow( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -173,6 +177,7 @@ def wrapped_run_sync_workflow( usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, **_deprecated_kwargs, ) @@ -245,6 +250,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -263,6 +269,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -280,6 +287,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -313,6 +321,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. event_stream_handler: Optional event stream handler to use for this run. Returns: @@ -330,6 +339,7 @@ async def main(): usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, **_deprecated_kwargs, ) @@ -349,6 +359,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -367,6 +378,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -384,6 +396,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -416,6 +429,7 @@ def run_sync( usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. event_stream_handler: Optional event stream handler to use for this run. Returns: @@ -433,6 +447,7 @@ def run_sync( usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, **_deprecated_kwargs, ) @@ -452,6 +467,7 @@ def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -470,6 +486,7 @@ def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -488,6 +505,7 @@ async def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[StreamedRunResult[AgentDepsT, Any]]: @@ -518,6 +536,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. event_stream_handler: Optional event stream handler to use for this run. 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. Returns: @@ -542,6 +561,7 @@ async def main(): usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, **_deprecated_kwargs, ) as result: @@ -562,6 +582,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @@ -580,6 +601,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @@ -598,6 +620,7 @@ async def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. @@ -671,6 +694,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -693,6 +717,7 @@ async def main(): usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, **_deprecated_kwargs, ) as run: yield run diff --git a/pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_agent.py b/pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_agent.py index a87b5195a9..111736a8b4 100644 --- a/pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_agent.py +++ b/pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_agent.py @@ -24,6 +24,7 @@ ) from pydantic_ai.agent import AbstractAgent, AgentRun, AgentRunResult, EventStreamHandler, WrapperAgent from pydantic_ai.agent.abstract import Instructions, RunOutputDataT +from pydantic_ai.builtin_tools import AbstractBuiltinTool from pydantic_ai.exceptions import UserError from pydantic_ai.models import Model from pydantic_ai.output import OutputDataT, OutputSpec @@ -267,6 +268,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -285,6 +287,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -302,6 +305,7 @@ async def run( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -336,6 +340,7 @@ async def main(): infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. event_stream_handler: Optional event stream handler to use for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -358,6 +363,7 @@ async def main(): usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler or self.event_stream_handler, **_deprecated_kwargs, ) @@ -377,6 +383,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -395,6 +402,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -412,6 +420,7 @@ def run_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -445,6 +454,7 @@ def run_sync( infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. event_stream_handler: Optional event stream handler to use for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -466,6 +476,7 @@ def run_sync( usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, event_stream_handler=event_stream_handler, **_deprecated_kwargs, ) @@ -485,6 +496,7 @@ def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -503,6 +515,7 @@ def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -521,6 +534,7 @@ async def run_stream( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[StreamedRunResult[AgentDepsT, Any]]: @@ -551,6 +565,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. event_stream_handler: Optional event stream handler to use for this run. 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. Returns: @@ -576,6 +591,7 @@ async def main(): infer_name=infer_name, toolsets=toolsets, event_stream_handler=event_stream_handler, + builtin_tools=builtin_tools, **_deprecated_kwargs, ) as result: yield result @@ -594,6 +610,7 @@ def iter( usage_limits: _usage.UsageLimits | None = None, usage: _usage.RunUsage | None = None, infer_name: bool = True, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @@ -613,6 +630,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @@ -631,6 +649,7 @@ async def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. @@ -704,6 +723,7 @@ async def main(): usage: Optional usage to start with, useful for resuming a conversation or agents used in tools. infer_name: Whether to try to infer the agent name from the call frame if it's not set. toolsets: Optional additional toolsets for this run. + builtin_tools: Optional additional builtin tools for this run. Returns: The result of the run. @@ -737,6 +757,7 @@ async def main(): usage=usage, infer_name=infer_name, toolsets=toolsets, + builtin_tools=builtin_tools, **_deprecated_kwargs, ) as run: yield run diff --git a/tests/test_agent.py b/tests/test_agent.py index cf3a416a1a..30d4c733ae 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -57,6 +57,7 @@ ToolOutputSchema, ) from pydantic_ai.agent import AgentRunResult, WrapperAgent +from pydantic_ai.builtin_tools import WebSearchTool from pydantic_ai.models.function import AgentInfo, FunctionModel from pydantic_ai.models.test import TestModel from pydantic_ai.output import StructuredDict, ToolOutput @@ -5511,3 +5512,87 @@ def roll_dice() -> int: ) assert not any(isinstance(p, ToolReturnPart) and p.tool_name == 'final_result' for p in new_messages[0].parts) + + +def test_agent_builtin_tools_runtime_parameter(): + """Test that Agent.run_sync accepts builtin_tools parameter.""" + model = TestModel() + agent = Agent(model=model, builtin_tools=[]) + + # Should work with empty builtin_tools + result = agent.run_sync('Hello', builtin_tools=[]) + assert result.output == 'success (no tool calls)' + + assert model.last_model_request_parameters is not None + assert model.last_model_request_parameters.builtin_tools == [] + + +async def test_agent_builtin_tools_runtime_parameter_async(): + """Test that Agent.run and Agent.run_stream accept builtin_tools parameter.""" + model = TestModel() + agent = Agent(model=model, builtin_tools=[]) + + # Test async run + result = await agent.run('Hello', builtin_tools=[]) + assert result.output == 'success (no tool calls)' + + assert model.last_model_request_parameters is not None + assert model.last_model_request_parameters.builtin_tools == [] + + # Test run_stream + async with agent.run_stream('Hello', builtin_tools=[]) as stream: + output = await stream.get_output() + assert output == 'success (no tool calls)' + + assert model.last_model_request_parameters is not None + assert model.last_model_request_parameters.builtin_tools == [] + + +def test_agent_builtin_tools_testmodel_rejection(): + """Test that TestModel rejects builtin tools as expected.""" + model = TestModel() + agent = Agent(model=model, builtin_tools=[]) + + # Should raise error when builtin_tools contains actual tools + web_search_tool = WebSearchTool() + with pytest.raises(Exception, match='TestModel does not support built-in tools'): + agent.run_sync('Hello', builtin_tools=[web_search_tool]) + + assert model.last_model_request_parameters is not None + assert len(model.last_model_request_parameters.builtin_tools) == 1 + assert model.last_model_request_parameters.builtin_tools[0] == web_search_tool + + +def test_agent_builtin_tools_runtime_vs_agent_level(): + """Test that runtime builtin_tools parameter is merged with agent-level builtin_tools.""" + model = TestModel() + web_search_tool = WebSearchTool() + + # Agent has builtin tools, and we provide same type at runtime + agent = Agent(model=model, builtin_tools=[web_search_tool]) + + # Runtime tool of same type should override agent-level tool + different_web_search = WebSearchTool(search_context_size='high') + with pytest.raises(Exception, match='TestModel does not support built-in tools'): + agent.run_sync('Hello', builtin_tools=[different_web_search]) + + assert model.last_model_request_parameters is not None + assert len(model.last_model_request_parameters.builtin_tools) == 1 + runtime_tool = model.last_model_request_parameters.builtin_tools[0] + assert isinstance(runtime_tool, WebSearchTool) + assert runtime_tool.search_context_size == 'high' + + +def test_agent_builtin_tools_runtime_additional(): + """Test that runtime builtin_tools can add to agent-level builtin_tools when different types.""" + model = TestModel() + web_search_tool = WebSearchTool() + + agent = Agent(model=model, builtin_tools=[]) + + with pytest.raises(Exception, match='TestModel does not support built-in tools'): + agent.run_sync('Hello', builtin_tools=[web_search_tool]) + + assert model.last_model_request_parameters is not None + assert len(model.last_model_request_parameters.builtin_tools) == 1 + assert model.last_model_request_parameters.builtin_tools[0] == web_search_tool