Skip to content

Commit 9a92f05

Browse files
committed
Add UrlContextTool support for Anthropic models
1 parent d10df9a commit 9a92f05

File tree

5 files changed

+355
-5
lines changed

5 files changed

+355
-5
lines changed

docs/builtin-tools.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,9 +315,9 @@ allowing it to pull up-to-date information from the web.
315315

316316
| Provider | Supported | Notes |
317317
|----------|-----------|-------|
318+
| Anthropic || Full feature support. Uses Anthropic's [Web Fetch Tool](https://docs.claude.com/en/docs/agents-and-tools/tool-use/web-fetch-tool) internally to retrieve URL contents. |
318319
| Google || No [`BuiltinToolCallPart`][pydantic_ai.messages.BuiltinToolCallPart] or [`BuiltinToolReturnPart`][pydantic_ai.messages.BuiltinToolReturnPart] is currently generated; please submit an issue if you need this. Using built-in tools and function tools (including [output tools](output.md#tool-output)) at the same time is not supported; to use structured output, use [`PromptedOutput`](output.md#prompted-output) instead. |
319320
| OpenAI || |
320-
| Anthropic || |
321321
| Groq || |
322322
| Bedrock || |
323323
| Mistral || |
@@ -327,7 +327,21 @@ allowing it to pull up-to-date information from the web.
327327

328328
### Usage
329329

330-
```py {title="url_context_basic.py"}
330+
```py {title="url_context_anthropic.py"}
331+
from pydantic_ai import Agent, UrlContextTool
332+
333+
agent = Agent('anthropic:claude-sonnet-4-0', builtin_tools=[UrlContextTool()])
334+
335+
result = agent.run_sync('What is the first sentence on https://ai.pydantic.dev?')
336+
print(result.output)
337+
#> Pydantic AI is a Python agent framework designed to make it less painful to build production grade applications with Generative AI.
338+
```
339+
340+
_(This example is complete, it can be run "as is")_
341+
342+
With Google, you can also use `UrlContextTool`:
343+
344+
```py {title="url_context_google.py"}
331345
from pydantic_ai import Agent, UrlContextTool
332346

333347
agent = Agent('google-gla:gemini-2.5-flash', builtin_tools=[UrlContextTool()])

pydantic_ai_slim/pydantic_ai/builtin_tools.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ class UrlContextTool(AbstractBuiltinTool):
171171
172172
Supported by:
173173
174+
* Anthropic
174175
* Google
175176
"""
176177

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .. import ModelHTTPError, UnexpectedModelBehavior, _utils, usage
1414
from .._run_context import RunContext
1515
from .._utils import guard_tool_call_id as _guard_tool_call_id
16-
from ..builtin_tools import CodeExecutionTool, MCPServerTool, MemoryTool, WebSearchTool
16+
from ..builtin_tools import CodeExecutionTool, MCPServerTool, MemoryTool, UrlContextTool, WebSearchTool
1717
from ..exceptions import UserError
1818
from ..messages import (
1919
BinaryContent,
@@ -106,6 +106,9 @@
106106
BetaToolUnionParam,
107107
BetaToolUseBlock,
108108
BetaToolUseBlockParam,
109+
BetaWebFetchTool20250910Param,
110+
BetaWebFetchToolResultBlock,
111+
BetaWebFetchToolResultBlockParam,
109112
BetaWebSearchTool20250305Param,
110113
BetaWebSearchToolResultBlock,
111114
BetaWebSearchToolResultBlockContent,
@@ -371,6 +374,8 @@ def _process_response(self, response: BetaMessage) -> ModelResponse:
371374
items.append(_map_web_search_tool_result_block(item, self.system))
372375
elif isinstance(item, BetaCodeExecutionToolResultBlock):
373376
items.append(_map_code_execution_tool_result_block(item, self.system))
377+
elif isinstance(item, BetaWebFetchToolResultBlock):
378+
items.append(_map_web_fetch_tool_result_block(item, self.system))
374379
elif isinstance(item, BetaRedactedThinkingBlock):
375380
items.append(
376381
ThinkingPart(id='redacted_thinking', content='', signature=item.data, provider_name=self.system)
@@ -464,6 +469,9 @@ def _add_builtin_tools(
464469
elif isinstance(tool, CodeExecutionTool): # pragma: no branch
465470
tools.append(BetaCodeExecutionTool20250522Param(name='code_execution', type='code_execution_20250522'))
466471
beta_features.append('code-execution-2025-05-22')
472+
elif isinstance(tool, UrlContextTool): # pragma: no branch
473+
tools.append(BetaWebFetchTool20250910Param(name='web_fetch', type='web_fetch_20250910'))
474+
beta_features.append('web-fetch-2025-09-10')
467475
elif isinstance(tool, MemoryTool): # pragma: no branch
468476
if 'memory' not in model_request_parameters.tool_defs:
469477
raise UserError("Built-in `MemoryTool` requires a 'memory' tool to be defined.")
@@ -542,6 +550,7 @@ async def _map_message( # noqa: C901
542550
| BetaServerToolUseBlockParam
543551
| BetaWebSearchToolResultBlockParam
544552
| BetaCodeExecutionToolResultBlockParam
553+
| BetaWebFetchToolResultBlockParam
545554
| BetaThinkingBlockParam
546555
| BetaRedactedThinkingBlockParam
547556
| BetaMCPToolUseBlockParam
@@ -604,6 +613,14 @@ async def _map_message( # noqa: C901
604613
input=response_part.args_as_dict(),
605614
)
606615
assistant_content_params.append(server_tool_use_block_param)
616+
elif response_part.tool_name == UrlContextTool.kind:
617+
server_tool_use_block_param = BetaServerToolUseBlockParam(
618+
id=tool_use_id,
619+
type='server_tool_use',
620+
name='web_fetch',
621+
input=response_part.args_as_dict(),
622+
)
623+
assistant_content_params.append(server_tool_use_block_param)
607624
elif (
608625
response_part.tool_name.startswith(MCPServerTool.kind)
609626
and (server_id := response_part.tool_name.split(':', 1)[1])
@@ -650,6 +667,19 @@ async def _map_message( # noqa: C901
650667
),
651668
)
652669
)
670+
elif response_part.tool_name == UrlContextTool.kind and isinstance(
671+
response_part.content, dict
672+
): # pragma: no branch
673+
assistant_content_params.append(
674+
cast(
675+
BetaWebFetchToolResultBlockParam,
676+
{
677+
'tool_use_id': tool_use_id,
678+
'type': 'web_fetch_tool_result',
679+
'content': response_part.content, # pyright: ignore[reportUnknownMemberType]
680+
},
681+
)
682+
)
653683
elif response_part.tool_name.startswith(MCPServerTool.kind) and isinstance(
654684
response_part.content, dict
655685
): # pragma: no branch
@@ -866,6 +896,11 @@ async def _get_event_iterator(self) -> AsyncIterator[ModelResponseStreamEvent]:
866896
vendor_part_id=event.index,
867897
part=_map_code_execution_tool_result_block(current_block, self.provider_name),
868898
)
899+
elif isinstance(current_block, BetaWebFetchToolResultBlock):
900+
yield self._parts_manager.handle_part(
901+
vendor_part_id=event.index,
902+
part=_map_web_fetch_tool_result_block(current_block, self.provider_name),
903+
)
869904
elif isinstance(current_block, BetaMCPToolUseBlock):
870905
call_part = _map_mcp_server_use_block(current_block, self.provider_name)
871906
builtin_tool_calls[call_part.tool_call_id] = call_part
@@ -972,7 +1007,14 @@ def _map_server_tool_use_block(item: BetaServerToolUseBlock, provider_name: str)
9721007
args=cast(dict[str, Any], item.input) or None,
9731008
tool_call_id=item.id,
9741009
)
975-
elif item.name in ('web_fetch', 'bash_code_execution', 'text_editor_code_execution'): # pragma: no cover
1010+
elif item.name == 'web_fetch':
1011+
return BuiltinToolCallPart(
1012+
provider_name=provider_name,
1013+
tool_name=UrlContextTool.kind,
1014+
args=cast(dict[str, Any], item.input) or None,
1015+
tool_call_id=item.id,
1016+
)
1017+
elif item.name in ('bash_code_execution', 'text_editor_code_execution'): # pragma: no cover
9761018
raise NotImplementedError(f'Anthropic built-in tool {item.name!r} is not currently supported.')
9771019
else:
9781020
assert_never(item.name)
@@ -1008,6 +1050,15 @@ def _map_code_execution_tool_result_block(
10081050
)
10091051

10101052

1053+
def _map_web_fetch_tool_result_block(item: BetaWebFetchToolResultBlock, provider_name: str) -> BuiltinToolReturnPart:
1054+
return BuiltinToolReturnPart(
1055+
provider_name=provider_name,
1056+
tool_name=UrlContextTool.kind,
1057+
content=item.model_dump(mode='json', include={'content'}),
1058+
tool_call_id=item.tool_use_id,
1059+
)
1060+
1061+
10111062
def _map_mcp_server_use_block(item: BetaMCPToolUseBlock, provider_name: str) -> BuiltinToolCallPart:
10121063
return BuiltinToolCallPart(
10131064
provider_name=provider_name,

0 commit comments

Comments
 (0)