Skip to content

Commit 212f935

Browse files
feat: Allow dynamic configuration of built-in tools via RunContext (#3600)
1 parent 7dda937 commit 212f935

File tree

11 files changed

+268
-77
lines changed

11 files changed

+268
-77
lines changed

docs/builtin-tools.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,47 @@ These tools are passed to the agent via the `builtin_tools` parameter and are ex
2020

2121
If a provider supports a built-in tool that is not currently supported by Pydantic AI, please file an issue.
2222

23+
## Dynamic Configuration
24+
25+
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`.
26+
27+
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.
28+
29+
```python {title="dynamic_builtin_tool.py"}
30+
from pydantic_ai import Agent, RunContext, WebSearchTool
31+
32+
33+
async def prepared_web_search(ctx: RunContext[dict]) -> WebSearchTool | None:
34+
if not ctx.deps.get('location'):
35+
return None
36+
37+
return WebSearchTool(
38+
user_location={'city': ctx.deps['location']},
39+
)
40+
41+
agent = Agent(
42+
'openai-responses:gpt-5',
43+
builtin_tools=[prepared_web_search],
44+
deps_type=dict,
45+
)
46+
47+
# Run with location
48+
result = agent.run_sync(
49+
'What is the weather like?',
50+
deps={'location': 'London'},
51+
)
52+
print(result.output)
53+
#> It's currently raining in London.
54+
55+
# Run without location (tool will be omitted)
56+
result = agent.run_sync(
57+
'What is the capital of France?',
58+
deps={'location': None},
59+
)
60+
print(result.output)
61+
#> The capital of France is Paris.
62+
```
63+
2364
## Web Search Tool
2465

2566
The [`WebSearchTool`][pydantic_ai.builtin_tools.WebSearchTool] allows your agent to search the web,

pydantic_ai_slim/pydantic_ai/_agent_graph.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
from .output import OutputDataT, OutputSpec
3131
from .settings import ModelSettings
3232
from .tools import (
33+
BuiltinToolFunc,
3334
DeferredToolCallResult,
3435
DeferredToolResult,
3536
DeferredToolResults,
@@ -148,7 +149,7 @@ class GraphAgentDeps(Generic[DepsT, OutputDataT]):
148149

149150
history_processors: Sequence[HistoryProcessor[DepsT]]
150151

151-
builtin_tools: list[AbstractBuiltinTool] = dataclasses.field(repr=False)
152+
builtin_tools: list[AbstractBuiltinTool | BuiltinToolFunc[DepsT]] = dataclasses.field(repr=False)
152153
tool_manager: ToolManager[DepsT]
153154

154155
tracer: Tracer
@@ -395,9 +396,23 @@ async def _prepare_request_parameters(
395396
else:
396397
function_tools.append(tool_def)
397398

399+
# resolve dynamic builtin tools
400+
builtin_tools: list[AbstractBuiltinTool] = []
401+
if ctx.deps.builtin_tools:
402+
run_context = build_run_context(ctx)
403+
for tool in ctx.deps.builtin_tools:
404+
if isinstance(tool, AbstractBuiltinTool):
405+
builtin_tools.append(tool)
406+
else:
407+
t = tool(run_context)
408+
if inspect.isawaitable(t):
409+
t = await t
410+
if t is not None:
411+
builtin_tools.append(t)
412+
398413
return models.ModelRequestParameters(
399414
function_tools=function_tools,
400-
builtin_tools=ctx.deps.builtin_tools,
415+
builtin_tools=builtin_tools,
401416
output_mode=output_schema.mode,
402417
output_tools=output_tools,
403418
output_object=output_schema.object_def,

pydantic_ai_slim/pydantic_ai/agent/__init__.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
from ..settings import ModelSettings, merge_model_settings
4444
from ..tools import (
4545
AgentDepsT,
46+
BuiltinToolFunc,
4647
DeferredToolResults,
4748
DocstringFormat,
4849
GenerateToolJsonSchema,
@@ -170,7 +171,7 @@ def __init__(
170171
validation_context: Any | Callable[[RunContext[AgentDepsT]], Any] = None,
171172
output_retries: int | None = None,
172173
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
173-
builtin_tools: Sequence[AbstractBuiltinTool] = (),
174+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] = (),
174175
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
175176
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
176177
toolsets: Sequence[AbstractToolset[AgentDepsT] | ToolsetFunc[AgentDepsT]] | None = None,
@@ -197,7 +198,7 @@ def __init__(
197198
validation_context: Any | Callable[[RunContext[AgentDepsT]], Any] = None,
198199
output_retries: int | None = None,
199200
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
200-
builtin_tools: Sequence[AbstractBuiltinTool] = (),
201+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] = (),
201202
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
202203
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
203204
mcp_servers: Sequence[MCPServer] = (),
@@ -222,7 +223,7 @@ def __init__(
222223
validation_context: Any | Callable[[RunContext[AgentDepsT]], Any] = None,
223224
output_retries: int | None = None,
224225
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
225-
builtin_tools: Sequence[AbstractBuiltinTool] = (),
226+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] = (),
226227
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
227228
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
228229
toolsets: Sequence[AbstractToolset[AgentDepsT] | ToolsetFunc[AgentDepsT]] | None = None,
@@ -427,7 +428,7 @@ def iter(
427428
usage: _usage.RunUsage | None = None,
428429
infer_name: bool = True,
429430
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
430-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
431+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
431432
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ...
432433

433434
@overload
@@ -446,7 +447,7 @@ def iter(
446447
usage: _usage.RunUsage | None = None,
447448
infer_name: bool = True,
448449
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
449-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
450+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
450451
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ...
451452

452453
@asynccontextmanager
@@ -465,7 +466,7 @@ async def iter(
465466
usage: _usage.RunUsage | None = None,
466467
infer_name: bool = True,
467468
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
468-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
469+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
469470
) -> AsyncIterator[AgentRun[AgentDepsT, Any]]:
470471
"""A contextmanager which can be used to iterate over the agent graph's nodes as they are executed.
471472

pydantic_ai_slim/pydantic_ai/agent/abstract.py

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from ..settings import ModelSettings
3232
from ..tools import (
3333
AgentDepsT,
34+
BuiltinToolFunc,
3435
DeferredToolResults,
3536
RunContext,
3637
Tool,
@@ -138,7 +139,7 @@ async def run(
138139
usage: _usage.RunUsage | None = None,
139140
infer_name: bool = True,
140141
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
141-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
142+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
142143
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
143144
) -> AgentRunResult[OutputDataT]: ...
144145

@@ -158,7 +159,7 @@ async def run(
158159
usage: _usage.RunUsage | None = None,
159160
infer_name: bool = True,
160161
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
161-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
162+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
162163
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
163164
) -> AgentRunResult[RunOutputDataT]: ...
164165

@@ -177,7 +178,7 @@ async def run(
177178
usage: _usage.RunUsage | None = None,
178179
infer_name: bool = True,
179180
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
180-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
181+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
181182
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
182183
) -> AgentRunResult[Any]:
183184
"""Run the agent with a user prompt in async mode.
@@ -262,7 +263,7 @@ def run_sync(
262263
usage: _usage.RunUsage | None = None,
263264
infer_name: bool = True,
264265
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
265-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
266+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
266267
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
267268
) -> AgentRunResult[OutputDataT]: ...
268269

@@ -282,7 +283,7 @@ def run_sync(
282283
usage: _usage.RunUsage | None = None,
283284
infer_name: bool = True,
284285
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
285-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
286+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
286287
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
287288
) -> AgentRunResult[RunOutputDataT]: ...
288289

@@ -301,7 +302,7 @@ def run_sync(
301302
usage: _usage.RunUsage | None = None,
302303
infer_name: bool = True,
303304
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
304-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
305+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
305306
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
306307
) -> AgentRunResult[Any]:
307308
"""Synchronously run the agent with a user prompt.
@@ -378,7 +379,7 @@ def run_stream(
378379
usage: _usage.RunUsage | None = None,
379380
infer_name: bool = True,
380381
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
381-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
382+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
382383
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
383384
) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, OutputDataT]]: ...
384385

@@ -398,7 +399,7 @@ def run_stream(
398399
usage: _usage.RunUsage | None = None,
399400
infer_name: bool = True,
400401
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
401-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
402+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
402403
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
403404
) -> AbstractAsyncContextManager[result.StreamedRunResult[AgentDepsT, RunOutputDataT]]: ...
404405

@@ -418,7 +419,7 @@ async def run_stream( # noqa: C901
418419
usage: _usage.RunUsage | None = None,
419420
infer_name: bool = True,
420421
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
421-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
422+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
422423
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
423424
) -> AsyncIterator[result.StreamedRunResult[AgentDepsT, Any]]:
424425
"""Run the agent with a user prompt in async streaming mode.
@@ -610,7 +611,7 @@ def run_stream_sync(
610611
usage: _usage.RunUsage | None = None,
611612
infer_name: bool = True,
612613
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
613-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
614+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
614615
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
615616
) -> result.StreamedRunResultSync[AgentDepsT, OutputDataT]: ...
616617

@@ -647,7 +648,7 @@ def run_stream_sync(
647648
usage: _usage.RunUsage | None = None,
648649
infer_name: bool = True,
649650
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
650-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
651+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
651652
event_stream_handler: EventStreamHandler[AgentDepsT] | None = None,
652653
) -> result.StreamedRunResultSync[AgentDepsT, Any]:
653654
"""Run the agent with a user prompt in sync streaming mode.
@@ -738,7 +739,7 @@ def run_stream_events(
738739
usage: _usage.RunUsage | None = None,
739740
infer_name: bool = True,
740741
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
741-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
742+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
742743
) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[OutputDataT]]: ...
743744

744745
@overload
@@ -757,7 +758,7 @@ def run_stream_events(
757758
usage: _usage.RunUsage | None = None,
758759
infer_name: bool = True,
759760
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
760-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
761+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
761762
) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[RunOutputDataT]]: ...
762763

763764
def run_stream_events(
@@ -775,7 +776,7 @@ def run_stream_events(
775776
usage: _usage.RunUsage | None = None,
776777
infer_name: bool = True,
777778
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
778-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
779+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
779780
) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]:
780781
"""Run the agent with a user prompt in async mode and stream events from the run.
781782
@@ -866,7 +867,7 @@ async def _run_stream_events(
866867
usage_limits: _usage.UsageLimits | None = None,
867868
usage: _usage.RunUsage | None = None,
868869
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
869-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
870+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
870871
) -> AsyncIterator[_messages.AgentStreamEvent | AgentRunResultEvent[Any]]:
871872
send_stream, receive_stream = anyio.create_memory_object_stream[
872873
_messages.AgentStreamEvent | AgentRunResultEvent[Any]
@@ -922,7 +923,7 @@ def iter(
922923
usage: _usage.RunUsage | None = None,
923924
infer_name: bool = True,
924925
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
925-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
926+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
926927
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ...
927928

928929
@overload
@@ -941,7 +942,7 @@ def iter(
941942
usage: _usage.RunUsage | None = None,
942943
infer_name: bool = True,
943944
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
944-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
945+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
945946
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ...
946947

947948
@asynccontextmanager
@@ -961,7 +962,7 @@ async def iter(
961962
usage: _usage.RunUsage | None = None,
962963
infer_name: bool = True,
963964
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
964-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
965+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
965966
) -> AsyncIterator[AgentRun[AgentDepsT, Any]]:
966967
"""A contextmanager which can be used to iterate over the agent graph's nodes as they are executed.
967968

pydantic_ai_slim/pydantic_ai/agent/wrapper.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from ..settings import ModelSettings
1717
from ..tools import (
1818
AgentDepsT,
19+
BuiltinToolFunc,
1920
DeferredToolResults,
2021
Tool,
2122
ToolFuncEither,
@@ -83,7 +84,7 @@ def iter(
8384
usage: _usage.RunUsage | None = None,
8485
infer_name: bool = True,
8586
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
86-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
87+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
8788
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, OutputDataT]]: ...
8889

8990
@overload
@@ -102,7 +103,7 @@ def iter(
102103
usage: _usage.RunUsage | None = None,
103104
infer_name: bool = True,
104105
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
105-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
106+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
106107
) -> AbstractAsyncContextManager[AgentRun[AgentDepsT, RunOutputDataT]]: ...
107108

108109
@asynccontextmanager
@@ -121,7 +122,7 @@ async def iter(
121122
usage: _usage.RunUsage | None = None,
122123
infer_name: bool = True,
123124
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
124-
builtin_tools: Sequence[AbstractBuiltinTool] | None = None,
125+
builtin_tools: Sequence[AbstractBuiltinTool | BuiltinToolFunc[AgentDepsT]] | None = None,
125126
) -> AsyncIterator[AgentRun[AgentDepsT, Any]]:
126127
"""A contextmanager which can be used to iterate over the agent graph's nodes as they are executed.
127128

0 commit comments

Comments
 (0)