Skip to content

Commit 1a22d68

Browse files
mattbrandmanKludexDouweM
authored
Add builtin_tools to Agent (#2102)
Co-authored-by: Marcelo Trylesinski <[email protected]> Co-authored-by: Douwe Maan <[email protected]>
1 parent 37a5ddd commit 1a22d68

File tree

60 files changed

+5964
-253
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+5964
-253
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ node_modules/
1919
**.idea/
2020
.coverage*
2121
/test_tmp/
22+
.mcp.json

docs/api/builtin_tools.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# `pydantic_ai.builtin_tools`
2+
3+
::: pydantic_ai.builtin_tools

docs/builtin-tools.md

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# Builtin Tools
2+
3+
Builtin tools are native tools provided by LLM providers that can be used to enhance your agent's capabilities. Unlike [common tools](common-tools.md), which are custom implementations that PydanticAI executes, builtin tools are executed directly by the model provider.
4+
5+
## Overview
6+
7+
PydanticAI supports two types of builtin tools:
8+
9+
- **[`WebSearchTool`][pydantic_ai.builtin_tools.WebSearchTool]**: Allows agents to search the web
10+
- **[`CodeExecutionTool`][pydantic_ai.builtin_tools.CodeExecutionTool]**: Enables agents to execute code in a secure environment
11+
12+
These tools are passed to the agent via the `builtin_tools` parameter and are executed by the model provider's infrastructure.
13+
14+
!!! warning "Provider Support"
15+
Not all model providers support builtin tools. If you use a builtin tool with an unsupported provider, PydanticAI will raise a [`UserError`][pydantic_ai.exceptions.UserError] when you try to run the agent.
16+
17+
## Web Search Tool
18+
19+
The [`WebSearchTool`][pydantic_ai.builtin_tools.WebSearchTool] allows your agent to search the web,
20+
making it ideal for queries that require up-to-date data.
21+
22+
### Provider Support
23+
24+
| Provider | Supported | Notes |
25+
|----------|-----------|-------|
26+
| OpenAI || Full feature support |
27+
| Anthropic || Full feature support |
28+
| Groq || Limited parameter support |
29+
| Google || Not supported |
30+
| Bedrock || Not supported |
31+
| Mistral || Not supported |
32+
| Cohere || Not supported |
33+
| HuggingFace || Not supported |
34+
35+
!!! note "Groq Support"
36+
To use web search capabilities with Groq, you need to use the [compound models](https://console.groq.com/docs/compound).
37+
38+
### Usage
39+
40+
```py title="web_search_basic.py"
41+
from pydantic_ai import Agent, WebSearchTool
42+
43+
agent = Agent('anthropic:claude-sonnet-4-0', builtin_tools=[WebSearchTool()])
44+
45+
result = agent.run_sync('Give me a sentence with the biggest news in AI this week.')
46+
# > Scientists have developed a universal AI detector that can identify deepfake videos.
47+
48+
```
49+
50+
### Configuration Options
51+
52+
The `WebSearchTool` supports several configuration parameters:
53+
54+
```py title="web_search_configured.py"
55+
from pydantic_ai import Agent, WebSearchTool, WebSearchUserLocation
56+
57+
agent = Agent(
58+
'anthropic:claude-sonnet-4-0',
59+
builtin_tools=[
60+
WebSearchTool(
61+
search_context_size='high',
62+
user_location=WebSearchUserLocation(
63+
city='San Francisco',
64+
country='US',
65+
region='CA',
66+
timezone='America/Los_Angeles',
67+
),
68+
blocked_domains=['example.com', 'spam-site.net'],
69+
allowed_domains=None, # Cannot use both blocked_domains and allowed_domains with Anthropic
70+
max_uses=5, # Anthropic only: limit tool usage
71+
)
72+
],
73+
)
74+
75+
result = agent.run_sync('Use the web to get the current time.')
76+
# > In San Francisco, it's 8:21:41 pm PDT on Wednesday, August 6, 2025.
77+
```
78+
79+
### Parameter Support by Provider
80+
81+
| Parameter | OpenAI | Anthropic | Groq |
82+
|-----------|--------|-----------|------|
83+
| `search_context_size` ||||
84+
| `user_location` ||||
85+
| `blocked_domains` ||||
86+
| `allowed_domains` ||||
87+
| `max_uses` ||||
88+
89+
!!! note "Anthropic Domain Filtering"
90+
With Anthropic, you can only use either `blocked_domains` or `allowed_domains`, not both.
91+
92+
## Code Execution Tool
93+
94+
The [`CodeExecutionTool`][pydantic_ai.builtin_tools.CodeExecutionTool] enables your agent to execute code
95+
in a secure environment, making it perfect for computational tasks, data analysis, and mathematical operations.
96+
97+
### Provider Support
98+
99+
| Provider | Supported |
100+
|----------|-----------|
101+
| OpenAI ||
102+
| Anthropic ||
103+
| Google ||
104+
| Groq ||
105+
| Bedrock ||
106+
| Mistral ||
107+
| Cohere ||
108+
| HuggingFace ||
109+
110+
### Usage
111+
112+
```py title="code_execution_basic.py"
113+
from pydantic_ai import Agent, CodeExecutionTool
114+
115+
agent = Agent('anthropic:claude-sonnet-4-0', builtin_tools=[CodeExecutionTool()])
116+
117+
result = agent.run_sync('Calculate the factorial of 15 and show your work')
118+
# > The factorial of 15 is **1,307,674,368,000**.
119+
```
120+
121+
## API Reference
122+
123+
For complete API documentation, see the [API Reference](api/builtin_tools.md).

mkdocs.yml

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ nav:
4141
- input.md
4242
- thinking.md
4343
- direct.md
44+
- builtin-tools.md
4445
- common-tools.md
4546
- retries.md
4647
- MCP:
@@ -72,6 +73,7 @@ nav:
7273
- api/agent.md
7374
- api/tools.md
7475
- api/toolsets.md
76+
- api/builtin_tools.md
7577
- api/common_tools.md
7678
- api/output.md
7779
- api/result.md
@@ -118,29 +120,29 @@ extra:
118120
generator: false
119121

120122
theme:
121-
name: 'material'
123+
name: "material"
122124
custom_dir: docs/.overrides
123125
palette:
124-
- media: '(prefers-color-scheme)'
126+
- media: "(prefers-color-scheme)"
125127
primary: pink
126128
accent: pink
127129
toggle:
128130
icon: material/brightness-auto
129-
name: 'Switch to light mode'
130-
- media: '(prefers-color-scheme: light)'
131+
name: "Switch to light mode"
132+
- media: "(prefers-color-scheme: light)"
131133
scheme: default
132134
primary: pink
133135
accent: pink
134136
toggle:
135137
icon: material/brightness-7
136-
name: 'Switch to dark mode'
137-
- media: '(prefers-color-scheme: dark)'
138+
name: "Switch to dark mode"
139+
- media: "(prefers-color-scheme: dark)"
138140
scheme: slate
139141
primary: pink
140142
accent: pink
141143
toggle:
142144
icon: material/brightness-4
143-
name: 'Switch to system preference'
145+
name: "Switch to system preference"
144146
features:
145147
- search.suggest
146148
- search.highlight
@@ -153,8 +155,8 @@ theme:
153155
- navigation.sections
154156
- navigation.tracking
155157
- toc.follow
156-
logo: 'img/logo-white.svg'
157-
favicon: 'favicon.ico'
158+
logo: "img/logo-white.svg"
159+
favicon: "favicon.ico"
158160

159161
# https://www.mkdocs.org/user-guide/configuration/#validation
160162
validation:
@@ -164,13 +166,13 @@ validation:
164166
anchors: warn
165167

166168
extra_css:
167-
- 'extra/tweaks.css'
169+
- "extra/tweaks.css"
168170
# used for analytics
169171
extra_javascript:
170-
- '/flarelytics/client.js'
171-
- 'https://cdn.jsdelivr.net/npm/[email protected]/dist/lite/builds/browser.umd.js'
172-
- 'https://cdn.jsdelivr.net/npm/[email protected]/dist/instantsearch.production.min.js'
173-
- '/javascripts/algolia-search.js'
172+
- "/flarelytics/client.js"
173+
- "https://cdn.jsdelivr.net/npm/[email protected]/dist/lite/builds/browser.umd.js"
174+
- "https://cdn.jsdelivr.net/npm/[email protected]/dist/instantsearch.production.min.js"
175+
- "/javascripts/algolia-search.js"
174176

175177
markdown_extensions:
176178
- tables
@@ -272,5 +274,5 @@ plugins:
272274
- examples/*.md
273275

274276
hooks:
275-
- 'docs/.hooks/main.py'
276-
- 'docs/.hooks/algolia.py'
277+
- "docs/.hooks/main.py"
278+
- "docs/.hooks/algolia.py"

pydantic_ai_slim/pydantic_ai/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from importlib.metadata import version as _metadata_version
22

33
from .agent import Agent, CallToolsNode, EndStrategy, ModelRequestNode, UserPromptNode, capture_run_messages
4+
from .builtin_tools import CodeExecutionTool, WebSearchTool, WebSearchUserLocation
45
from .exceptions import (
56
AgentRunError,
67
FallbackExceptionGroup,
@@ -41,6 +42,10 @@
4142
# tools
4243
'Tool',
4344
'RunContext',
45+
# builtin_tools
46+
'WebSearchTool',
47+
'WebSearchUserLocation',
48+
'CodeExecutionTool',
4449
# output
4550
'ToolOutput',
4651
'NativeOutput',

pydantic_ai_slim/pydantic_ai/_agent_graph.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pydantic_ai._function_schema import _takes_ctx as is_takes_ctx # type: ignore
1717
from pydantic_ai._tool_manager import ToolManager
1818
from pydantic_ai._utils import is_async_callable, run_in_executor
19+
from pydantic_ai.builtin_tools import AbstractBuiltinTool
1920
from pydantic_graph import BaseNode, Graph, GraphRunContext
2021
from pydantic_graph.nodes import End, NodeRunEndT
2122

@@ -112,6 +113,7 @@ class GraphAgentDeps(Generic[DepsT, OutputDataT]):
112113

113114
history_processors: Sequence[HistoryProcessor[DepsT]]
114115

116+
builtin_tools: list[AbstractBuiltinTool] = dataclasses.field(repr=False)
115117
tool_manager: ToolManager[DepsT]
116118

117119
tracer: Tracer
@@ -269,6 +271,7 @@ async def _prepare_request_parameters(
269271

270272
return models.ModelRequestParameters(
271273
function_tools=function_tools,
274+
builtin_tools=ctx.deps.builtin_tools,
272275
output_mode=output_schema.mode,
273276
output_tools=output_tools,
274277
output_object=output_object,
@@ -443,6 +446,10 @@ async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]:
443446
texts.append(part.content)
444447
elif isinstance(part, _messages.ToolCallPart):
445448
tool_calls.append(part)
449+
elif isinstance(part, _messages.BuiltinToolCallPart):
450+
yield _messages.BuiltinToolCallEvent(part)
451+
elif isinstance(part, _messages.BuiltinToolReturnPart):
452+
yield _messages.BuiltinToolResultEvent(part)
446453
elif isinstance(part, _messages.ThinkingPart):
447454
# We don't need to do anything with thinking parts in this tool-calling node.
448455
# We need to handle text parts in case there are no tool calls and/or the desired output comes

pydantic_ai_slim/pydantic_ai/_utils.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,13 @@ def now_utc() -> datetime:
227227
return datetime.now(tz=timezone.utc)
228228

229229

230-
def guard_tool_call_id(t: _messages.ToolCallPart | _messages.ToolReturnPart | _messages.RetryPromptPart) -> str:
230+
def guard_tool_call_id(
231+
t: _messages.ToolCallPart
232+
| _messages.ToolReturnPart
233+
| _messages.RetryPromptPart
234+
| _messages.BuiltinToolCallPart
235+
| _messages.BuiltinToolReturnPart,
236+
) -> str:
231237
"""Type guard that either returns the tool call id or generates a new one if it's None."""
232238
return t.tool_call_id or generate_tool_call_id()
233239

pydantic_ai_slim/pydantic_ai/agent.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from pydantic.json_schema import GenerateJsonSchema
1717
from typing_extensions import Literal, Never, Self, TypeIs, TypeVar, deprecated
1818

19+
from pydantic_ai.builtin_tools import AbstractBuiltinTool
1920
from pydantic_graph import End, Graph, GraphRun, GraphRunContext
2021
from pydantic_graph._utils import get_event_loop
2122

@@ -188,6 +189,7 @@ def __init__(
188189
retries: int = 1,
189190
output_retries: int | None = None,
190191
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
192+
builtin_tools: Sequence[AbstractBuiltinTool] = (),
191193
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
192194
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
193195
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
@@ -215,6 +217,7 @@ def __init__(
215217
retries: int = 1,
216218
output_retries: int | None = None,
217219
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
220+
builtin_tools: Sequence[AbstractBuiltinTool] = (),
218221
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
219222
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
220223
mcp_servers: Sequence[MCPServer] = (),
@@ -240,6 +243,7 @@ def __init__(
240243
retries: int = 1,
241244
output_retries: int | None = None,
242245
tools: Sequence[Tool[AgentDepsT] | ToolFuncEither[AgentDepsT, ...]] = (),
246+
builtin_tools: Sequence[AbstractBuiltinTool] = (),
243247
prepare_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
244248
prepare_output_tools: ToolsPrepareFunc[AgentDepsT] | None = None,
245249
toolsets: Sequence[AbstractToolset[AgentDepsT]] | None = None,
@@ -271,6 +275,8 @@ def __init__(
271275
output_retries: The maximum number of retries to allow for output validation, defaults to `retries`.
272276
tools: Tools to register with the agent, you can also register tools via the decorators
273277
[`@agent.tool`][pydantic_ai.Agent.tool] and [`@agent.tool_plain`][pydantic_ai.Agent.tool_plain].
278+
builtin_tools: The builtin tools that the agent will use. This depends on the model, as some models may not
279+
support certain tools. If the model doesn't support the builtin tools, an error will be raised.
274280
prepare_tools: Custom function to prepare the tool definition of all tools for each step, except output tools.
275281
This is useful if you want to customize the definition of multiple tools or you want to register
276282
a subset of tools for a given step. See [`ToolsPrepareFunc`][pydantic_ai.tools.ToolsPrepareFunc]
@@ -340,6 +346,8 @@ def __init__(
340346
self._system_prompt_dynamic_functions = {}
341347

342348
self._max_result_retries = output_retries if output_retries is not None else retries
349+
self._builtin_tools = builtin_tools
350+
343351
self._prepare_tools = prepare_tools
344352
self._prepare_output_tools = prepare_output_tools
345353

@@ -700,6 +708,7 @@ async def get_instructions(run_context: RunContext[AgentDepsT]) -> str | None:
700708
output_schema=output_schema,
701709
output_validators=output_validators,
702710
history_processors=self.history_processors,
711+
builtin_tools=list(self._builtin_tools),
703712
tool_manager=run_toolset,
704713
tracer=tracer,
705714
get_instructions=get_instructions,

0 commit comments

Comments
 (0)