diff --git a/docs/builtin-tools.md b/docs/builtin-tools.md index c61fa8b51d..86f41b5c60 100644 --- a/docs/builtin-tools.md +++ b/docs/builtin-tools.md @@ -20,6 +20,47 @@ These tools are passed to the agent via the `builtin_tools` parameter and are ex If a provider supports a built-in tool that is not currently supported by Pydantic AI, please file an issue. +## Dynamic Configuration + +Sometimes you need to configure a built-in tool dynamically based on the [run context][pydantic_ai.tools.RunContext] (e.g., user dependencies), or conditionally omit it. You can achieve this by passing a function to `builtin_tools` that takes [`RunContext`][pydantic_ai.tools.RunContext] as an argument and returns an [`AbstractBuiltinTool`][pydantic_ai.builtin_tools.AbstractBuiltinTool] or `None`. + +This is particularly useful for tools like [`WebSearchTool`][pydantic_ai.builtin_tools.WebSearchTool] where you might want to set the user's location based on the current request, or disable the tool if the user provides no location. + +```python {title="dynamic_builtin_tool.py"} +from pydantic_ai import Agent, RunContext, WebSearchTool + + +async def prepared_web_search(ctx: RunContext[dict]) -> WebSearchTool | None: + if not ctx.deps.get('location'): + return None + + return WebSearchTool( + user_location={'city': ctx.deps['location']}, + ) + +agent = Agent( + 'openai-responses:gpt-5', + builtin_tools=[prepared_web_search], + deps_type=dict, +) + +# Run with location +result = agent.run_sync( + 'What is the weather like?', + deps={'location': 'London'}, +) +print(result.output) +#> It's currently raining in London. + +# Run without location (tool will be omitted) +result = agent.run_sync( + 'What is the capital of France?', + deps={'location': None}, +) +print(result.output) +#> The capital of France is Paris. +``` + ## Web Search Tool The [`WebSearchTool`][pydantic_ai.builtin_tools.WebSearchTool] allows your agent to search the web, diff --git a/pydantic_ai_slim/pydantic_ai/_agent_graph.py b/pydantic_ai_slim/pydantic_ai/_agent_graph.py index 6a14f8b350..043c27c4f5 100644 --- a/pydantic_ai_slim/pydantic_ai/_agent_graph.py +++ b/pydantic_ai_slim/pydantic_ai/_agent_graph.py @@ -30,6 +30,7 @@ from .output import OutputDataT, OutputSpec from .settings import ModelSettings from .tools import ( + BuiltinToolFunc, DeferredToolCallResult, DeferredToolResult, DeferredToolResults, @@ -148,7 +149,7 @@ class GraphAgentDeps(Generic[DepsT, OutputDataT]): history_processors: Sequence[HistoryProcessor[DepsT]] - builtin_tools: list[AbstractBuiltinTool] = dataclasses.field(repr=False) + builtin_tools: list[AbstractBuiltinTool | BuiltinToolFunc[DepsT]] = dataclasses.field(repr=False) tool_manager: ToolManager[DepsT] tracer: Tracer @@ -395,9 +396,23 @@ async def _prepare_request_parameters( else: function_tools.append(tool_def) + # resolve dynamic builtin tools + builtin_tools: list[AbstractBuiltinTool] = [] + if ctx.deps.builtin_tools: + run_context = build_run_context(ctx) + for tool in ctx.deps.builtin_tools: + if isinstance(tool, AbstractBuiltinTool): + builtin_tools.append(tool) + else: + t = tool(run_context) + if inspect.isawaitable(t): + t = await t + if t is not None: + builtin_tools.append(t) + return models.ModelRequestParameters( function_tools=function_tools, - builtin_tools=ctx.deps.builtin_tools, + builtin_tools=builtin_tools, output_mode=output_schema.mode, output_tools=output_tools, output_object=output_schema.object_def, diff --git a/pydantic_ai_slim/pydantic_ai/agent/__init__.py b/pydantic_ai_slim/pydantic_ai/agent/__init__.py index c8208ac9e6..19edb4a619 100644 --- a/pydantic_ai_slim/pydantic_ai/agent/__init__.py +++ b/pydantic_ai_slim/pydantic_ai/agent/__init__.py @@ -43,6 +43,7 @@ from ..settings import ModelSettings, merge_model_settings from ..tools import ( AgentDepsT, + BuiltinToolFunc, DeferredToolResults, DocstringFormat, GenerateToolJsonSchema, @@ -170,7 +171,7 @@ def __init__( validation_context: Any | Callable[[RunContext[AgentDepsT]], Any] = None, output_retries: int | None = None, tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), - builtin_tools: Sequence[AbstractBuiltinTool] = (), + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] = (), prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None, toolsets: Sequence[AbstractToolset[AgentDepsT] | ToolsetFunc[AgentDepsT]] | None = None, @@ -197,7 +198,7 @@ def __init__( validation_context: Any | Callable[[RunContext[AgentDepsT]], Any] = None, output_retries: int | None = None, tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), - builtin_tools: Sequence[AbstractBuiltinTool] = (), + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] = (), prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None, mcp_servers: Sequence[MCPServer] = (), @@ -222,7 +223,7 @@ def __init__( validation_context: Any | Callable[[RunContext[AgentDepsT]], Any] = None, output_retries: int | None = None, tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (), - builtin_tools: Sequence[AbstractBuiltinTool] = (), + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] = (), prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None, prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None, toolsets: Sequence[AbstractToolset[AgentDepsT] | ToolsetFunc[AgentDepsT]] | None = None, @@ -427,7 +428,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -446,7 +447,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -465,7 +466,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. diff --git a/pydantic_ai_slim/pydantic_ai/agent/abstract.py b/pydantic_ai_slim/pydantic_ai/agent/abstract.py index 567b61dff6..96d4d23766 100644 --- a/pydantic_ai_slim/pydantic_ai/agent/abstract.py +++ b/pydantic_ai_slim/pydantic_ai/agent/abstract.py @@ -31,6 +31,7 @@ from ..settings import ModelSettings from ..tools import ( AgentDepsT, + BuiltinToolFunc, DeferredToolResults, RunContext, Tool, @@ -138,7 +139,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -158,7 +159,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -177,7 +178,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[Any]: """Run the agent with a user prompt in async mode. @@ -262,7 +263,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -282,7 +283,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -301,7 +302,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[Any]: """Synchronously run the agent with a user prompt. @@ -378,7 +379,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -398,7 +399,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -418,7 +419,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | 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. @@ -610,7 +611,7 @@ def run_stream_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> result.StreamedRunResultSync[AgentDepsT, OutputDataT]: ... @@ -647,7 +648,7 @@ def run_stream_sync( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> result.StreamedRunResultSync[AgentDepsT, Any]: """Run the agent with a user prompt in sync streaming mode. @@ -738,7 +739,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[OutputDataT]]: ... @overload @@ -757,7 +758,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[RunOutputDataT]]: ... def run_stream_events( @@ -775,7 +776,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]: """Run the agent with a user prompt in async mode and stream events from the run. @@ -866,7 +867,7 @@ async def _run_stream_events( usage_limits: _usage.UsageLimits | None = None, usage: _usage.RunUsage | None = None, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]: send_stream, receive_stream = anyio.create_memory_object_stream[ _messages.AgentStreamEvent | AgentRunResultEvent[Any] @@ -922,7 +923,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -941,7 +942,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -961,7 +962,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. diff --git a/pydantic_ai_slim/pydantic_ai/agent/wrapper.py b/pydantic_ai_slim/pydantic_ai/agent/wrapper.py index 38e832fa2b..6e275b3f50 100644 --- a/pydantic_ai_slim/pydantic_ai/agent/wrapper.py +++ b/pydantic_ai_slim/pydantic_ai/agent/wrapper.py @@ -16,6 +16,7 @@ from ..settings import ModelSettings from ..tools import ( AgentDepsT, + BuiltinToolFunc, DeferredToolResults, Tool, ToolFuncEither, @@ -83,7 +84,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -102,7 +103,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -121,7 +122,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. 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 ff6730f220..c5adf5221d 100644 --- a/pydantic_ai_slim/pydantic_ai/durable_exec/dbos/_agent.py +++ b/pydantic_ai_slim/pydantic_ai/durable_exec/dbos/_agent.py @@ -25,6 +25,7 @@ from pydantic_ai.settings import ModelSettings from pydantic_ai.tools import ( AgentDepsT, + BuiltinToolFunc, DeferredToolResults, RunContext, Tool, @@ -138,7 +139,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -179,7 +180,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -271,7 +272,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -291,7 +292,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -310,7 +311,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -389,7 +390,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -409,7 +410,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -428,7 +429,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -506,7 +507,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -526,7 +527,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -546,7 +547,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[StreamedRunResult[AgentDepsT, Any]]: @@ -625,7 +626,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[OutputDataT]]: ... @overload @@ -644,7 +645,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[RunOutputDataT]]: ... def run_stream_events( @@ -662,7 +663,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]: """Run the agent with a user prompt in async mode and stream events from the run. @@ -739,7 +740,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @@ -759,7 +760,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @@ -779,7 +780,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | 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. diff --git a/pydantic_ai_slim/pydantic_ai/durable_exec/prefect/_agent.py b/pydantic_ai_slim/pydantic_ai/durable_exec/prefect/_agent.py index 8b1b6af44a..60c8122686 100644 --- a/pydantic_ai_slim/pydantic_ai/durable_exec/prefect/_agent.py +++ b/pydantic_ai_slim/pydantic_ai/durable_exec/prefect/_agent.py @@ -28,6 +28,7 @@ from pydantic_ai.settings import ModelSettings from pydantic_ai.tools import ( AgentDepsT, + BuiltinToolFunc, DeferredToolResults, RunContext, Tool, @@ -187,7 +188,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -207,7 +208,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -226,7 +227,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -311,7 +312,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -331,7 +332,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -350,7 +351,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -437,7 +438,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -457,7 +458,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -477,7 +478,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[StreamedRunResult[AgentDepsT, Any]]: @@ -556,7 +557,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[OutputDataT]]: ... @overload @@ -575,7 +576,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[RunOutputDataT]]: ... def run_stream_events( @@ -593,7 +594,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]: """Run the agent with a user prompt in async mode and stream events from the run. @@ -687,7 +688,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @overload @@ -706,7 +707,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @asynccontextmanager @@ -725,7 +726,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[AgentRun[AgentDepsT, Any]]: """A contextmanager which can be used to iterate over the agent graph's nodes as they are executed. 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 6e964c8d08..42fc2a872e 100644 --- a/pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_agent.py +++ b/pydantic_ai_slim/pydantic_ai/durable_exec/temporal/_agent.py @@ -33,6 +33,7 @@ from pydantic_ai.settings import ModelSettings from pydantic_ai.tools import ( AgentDepsT, + BuiltinToolFunc, DeferredToolResults, RunContext, Tool, @@ -270,7 +271,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -290,7 +291,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -309,7 +310,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -390,7 +391,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[OutputDataT]: ... @@ -410,7 +411,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AgentRunResult[RunOutputDataT]: ... @@ -429,7 +430,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AgentRunResult[Any]: @@ -508,7 +509,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, OutputDataT]]: ... @@ -528,7 +529,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, ) -> AbstractAsyncContextManager[StreamedRunResult[AgentDepsT, RunOutputDataT]]: ... @@ -548,7 +549,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, event_stream_handler: EventStreamHandler[AgentDepsT] | None = None, **_deprecated_kwargs: Never, ) -> AsyncIterator[StreamedRunResult[AgentDepsT, Any]]: @@ -627,7 +628,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[OutputDataT]]: ... @overload @@ -646,7 +647,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[RunOutputDataT]]: ... def run_stream_events( @@ -664,7 +665,7 @@ def run_stream_events( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, ) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]: """Run the agent with a user prompt in async mode and stream events from the run. @@ -757,7 +758,7 @@ def iter( usage_limits: _usage.UsageLimits | None = None, usage: _usage.RunUsage | None = None, infer_name: bool = True, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ... @@ -778,7 +779,7 @@ def iter( usage: _usage.RunUsage | None = None, infer_name: bool = True, toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None, - builtin_tools: Sequence[AbstractBuiltinTool] | None = None, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None, **_deprecated_kwargs: Never, ) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ... @@ -798,7 +799,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, + builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | 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. diff --git a/pydantic_ai_slim/pydantic_ai/tools.py b/pydantic_ai_slim/pydantic_ai/tools.py index e54b829bfb..dcd860b019 100644 --- a/pydantic_ai_slim/pydantic_ai/tools.py +++ b/pydantic_ai_slim/pydantic_ai/tools.py @@ -11,6 +11,7 @@ from . import _function_schema, _utils from ._run_context import AgentDepsT, RunContext +from .builtin_tools import AbstractBuiltinTool from .exceptions import ModelRetry from .messages import RetryPromptPart, ToolCallPart, ToolReturn @@ -25,6 +26,7 @@ 'ToolParams', 'ToolPrepareFunc', 'ToolsPrepareFunc', + 'BuiltinToolFunc', 'Tool', 'ObjectJsonSchema', 'ToolDefinition', @@ -122,6 +124,15 @@ async def turn_on_strict_if_openai( Usage `ToolsPrepareFunc[AgentDepsT]`. """ +BuiltinToolFunc: TypeAlias = Callable[ + [RunContext[AgentDepsT]], Awaitable[AbstractBuiltinTool | None] | AbstractBuiltinTool | None +] +"""Definition of a function that can prepare a builtin tool at call time. + +This is useful if you want to customize the builtin tool based on the run context (e.g. user dependencies), +or omit it completely from a step. +""" + DocstringFormat: TypeAlias = Literal['google', 'numpy', 'sphinx', 'auto'] """Supported docstring formats. diff --git a/tests/test_agent.py b/tests/test_agent.py index c912334434..9c44f4adfb 100644 --- a/tests/test_agent.py +++ b/tests/test_agent.py @@ -57,7 +57,12 @@ TextOutput, ) from pydantic_ai.agent import AgentRunResult, WrapperAgent -from pydantic_ai.builtin_tools import CodeExecutionTool, MCPServerTool, WebSearchTool +from pydantic_ai.builtin_tools import ( + CodeExecutionTool, + MCPServerTool, + WebSearchTool, + WebSearchUserLocation, +) from pydantic_ai.models.function import AgentInfo, DeltaToolCall, DeltaToolCalls, FunctionModel from pydantic_ai.models.test import TestModel from pydantic_ai.output import OutputObjectDefinition, StructuredDict, ToolOutput @@ -6313,3 +6318,115 @@ def llm(messages: list[ModelMessage], _info: AgentInfo) -> ModelResponse: ] ) assert run.all_messages_json().startswith(b'[{"parts":[{"content":"Hello",') + + +@dataclass +class UserContext: + location: str | None + + +async def prepared_web_search(ctx: RunContext[UserContext]) -> WebSearchTool | None: + if not ctx.deps.location: + return None + + return WebSearchTool( + search_context_size='medium', + user_location=WebSearchUserLocation(city=ctx.deps.location), + ) + + +async def test_dynamic_builtin_tool_configured(): + model = TestModel() + agent = Agent(model, builtin_tools=[prepared_web_search], deps_type=UserContext) + + user_context = UserContext(location='London') + + with pytest.raises(UserError, match='TestModel does not support built-in tools'): + await agent.run('Hello', deps=user_context) + + assert model.last_model_request_parameters is not None + tools = model.last_model_request_parameters.builtin_tools + assert len(tools) == 1 + tool = tools[0] + assert isinstance(tool, WebSearchTool) + assert tool.user_location is not None + assert tool.user_location.get('city') == 'London' + assert tool.search_context_size == 'medium' + + +async def test_dynamic_builtin_tool_omitted(): + model = TestModel() + agent = Agent(model, builtin_tools=[prepared_web_search], deps_type=UserContext) + + user_context = UserContext(location=None) + + await agent.run('Hello', deps=user_context) + + assert model.last_model_request_parameters is not None + tools = model.last_model_request_parameters.builtin_tools + assert len(tools) == 0 + + +async def test_mixed_static_and_dynamic_builtin_tools(): + model = TestModel() + + static_tool = CodeExecutionTool() + agent = Agent(model, builtin_tools=[static_tool, prepared_web_search], deps_type=UserContext) + + # Case 1: Dynamic tool returns None + with pytest.raises(UserError, match='TestModel does not support built-in tools'): + await agent.run('Hello', deps=UserContext(location=None)) + + assert model.last_model_request_parameters is not None + tools = model.last_model_request_parameters.builtin_tools + assert len(tools) == 1 + assert tools[0] == static_tool + + # Case 2: Dynamic tool returns a tool + with pytest.raises(UserError, match='TestModel does not support built-in tools'): + await agent.run('Hello', deps=UserContext(location='Paris')) + + assert model.last_model_request_parameters is not None + tools = model.last_model_request_parameters.builtin_tools + assert len(tools) == 2 + assert tools[0] == static_tool + dynamic_tool = tools[1] + assert isinstance(dynamic_tool, WebSearchTool) + assert dynamic_tool.user_location is not None + assert dynamic_tool.user_location.get('city') == 'Paris' + + +def sync_dynamic_tool(ctx: RunContext[UserContext]) -> WebSearchTool: + """Verify that synchronous functions work.""" + return WebSearchTool(search_context_size='low') + + +async def test_sync_dynamic_tool(): + model = TestModel() + agent = Agent(model, builtin_tools=[sync_dynamic_tool], deps_type=UserContext) + + with pytest.raises(UserError, match='TestModel does not support built-in tools'): + await agent.run('Hello', deps=UserContext(location='London')) + + assert model.last_model_request_parameters is not None + tools = model.last_model_request_parameters.builtin_tools + assert len(tools) == 1 + assert isinstance(tools[0], WebSearchTool) + assert tools[0].search_context_size == 'low' + + +async def test_dynamic_tool_in_run_call(): + """Verify dynamic tools can be passed to agent.run().""" + model = TestModel() + agent = Agent(model, deps_type=UserContext) + + with pytest.raises(UserError, match='TestModel does not support built-in tools'): + await agent.run('Hello', deps=UserContext(location='Berlin'), builtin_tools=[prepared_web_search]) + + assert model.last_model_request_parameters is not None + tools = model.last_model_request_parameters.builtin_tools + assert len(tools) == 1 + tool = tools[0] + assert isinstance(tool, WebSearchTool) + assert tool.user_location is not None + assert tool.user_location.get('city') == 'Berlin' diff --git a/tests/test_examples.py b/tests/test_examples.py index 3490f0dd3e..f5cf196b7c 100644 --- a/tests/test_examples.py +++ b/tests/test_examples.py @@ -328,6 +328,7 @@ async def call_tool( 'Give me a sentence with the biggest news in AI this week.': 'Scientists have developed a universal AI detector that can identify deepfake videos.', 'How many days between 2000-01-01 and 2025-03-18?': 'There are 9,208 days between January 1, 2000, and March 18, 2025.', 'What is 7 plus 5?': 'The answer is 12.', + 'What is the weather like?': "It's currently raining in London.", 'What is the weather like in West London and in Wiltshire?': ( 'The weather in West London is raining, while in Wiltshire it is sunny.' ),