Skip to content

Commit 32324fa

Browse files
committed
add more built-in-tools
1 parent fa7fd11 commit 32324fa

File tree

8 files changed

+786
-214
lines changed

8 files changed

+786
-214
lines changed

pydantic_ai_slim/pydantic_ai/_agent_graph.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -419,7 +419,7 @@ async def stream(
419419
async for _event in stream:
420420
pass
421421

422-
async def _run_stream(
422+
async def _run_stream( # noqa C901
423423
self, ctx: GraphRunContext[GraphAgentState, GraphAgentDeps[DepsT, NodeRunEndT]]
424424
) -> AsyncIterator[_messages.HandleResponseEvent]:
425425
if self._events_iterator is None:
@@ -435,6 +435,10 @@ async def _run_stream() -> AsyncIterator[_messages.HandleResponseEvent]:
435435
texts.append(part.content)
436436
elif isinstance(part, _messages.ToolCallPart):
437437
tool_calls.append(part)
438+
elif isinstance(part, _messages.ServerToolCallPart):
439+
yield _messages.ServerToolCallEvent(part)
440+
elif isinstance(part, _messages.ServerToolReturnPart):
441+
yield _messages.ServerToolResultEvent(part)
438442
else:
439443
assert_never(part)
440444

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,29 @@ class FunctionToolResultEvent:
914914
__repr__ = _utils.dataclasses_no_defaults_repr
915915

916916

917+
@dataclass(repr=False)
918+
class ServerToolCallEvent:
919+
"""An event indicating the start to a call to a server tool."""
920+
921+
part: ServerToolCallPart
922+
"""The server tool call to make."""
923+
924+
event_kind: Literal['server_tool_call'] = 'server_tool_call'
925+
"""Event type identifier, used as a discriminator."""
926+
927+
928+
@dataclass(repr=False)
929+
class ServerToolResultEvent:
930+
"""An event indicating the result of a server tool call."""
931+
932+
result: ServerToolReturnPart
933+
"""The result of the call to the server tool."""
934+
935+
event_kind: Literal['server_tool_result'] = 'server_tool_result'
936+
"""Event type identifier, used as a discriminator."""
937+
938+
917939
HandleResponseEvent = Annotated[
918-
Union[FunctionToolCallEvent, FunctionToolResultEvent], pydantic.Discriminator('event_kind')
940+
Union[FunctionToolCallEvent, FunctionToolResultEvent, ServerToolCallEvent, ServerToolResultEvent],
941+
pydantic.Discriminator('event_kind'),
919942
]

pydantic_ai_slim/pydantic_ai/models/anthropic.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from typing import Any, Literal, Union, cast, overload
99

1010
from anthropic.types.beta import (
11+
BetaCodeExecutionToolResultBlock,
1112
BetaCodeExecutionToolResultBlockParam,
1213
BetaServerToolUseBlockParam,
1314
BetaWebSearchToolResultBlockParam,
@@ -238,6 +239,7 @@ async def _messages_create(
238239
try:
239240
extra_headers = model_settings.get('extra_headers', {})
240241
extra_headers.setdefault('User-Agent', get_user_agent())
242+
extra_headers.setdefault('anthropic-beta', 'code-execution-2025-05-22')
241243
return await self.client.beta.messages.create(
242244
max_tokens=model_settings.get('max_tokens', 1024),
243245
system=system_prompt or NOT_GIVEN,
@@ -268,7 +270,7 @@ def _process_response(self, response: BetaMessage) -> ModelResponse:
268270
elif isinstance(item, BetaWebSearchToolResultBlock):
269271
items.append(
270272
ServerToolReturnPart(
271-
tool_name='web_search',
273+
tool_name=item.type,
272274
content=item.content,
273275
tool_call_id=item.tool_use_id,
274276
)
@@ -282,6 +284,14 @@ def _process_response(self, response: BetaMessage) -> ModelResponse:
282284
tool_call_id=item.id,
283285
)
284286
)
287+
elif isinstance(item, BetaCodeExecutionToolResultBlock):
288+
items.append(
289+
ServerToolReturnPart(
290+
tool_name=item.type,
291+
content=item.content,
292+
tool_call_id=item.tool_use_id,
293+
)
294+
)
285295
else:
286296
assert isinstance(item, BetaToolUseBlock), f'unexpected item type {type(item)}'
287297
items.append(
@@ -326,7 +336,7 @@ def _get_builtin_tools(self, model_request_parameters: ModelRequestParameters) -
326336
user_location=user_location,
327337
)
328338
)
329-
if isinstance(tool, CodeExecutionTool):
339+
elif isinstance(tool, CodeExecutionTool):
330340
tools.append(BetaCodeExecutionTool20250522Param(name='code_execution', type='code_execution_20250522'))
331341
return tools
332342

pydantic_ai_slim/pydantic_ai/models/openai.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
ModelResponsePart,
2828
ModelResponseStreamEvent,
2929
RetryPromptPart,
30+
ServerToolCallPart,
31+
ServerToolReturnPart,
3032
SystemPromptPart,
3133
TextPart,
3234
ToolCallPart,
@@ -398,6 +400,9 @@ async def _map_messages(self, messages: list[ModelMessage]) -> list[chat.ChatCom
398400
texts.append(item.content)
399401
elif isinstance(item, ToolCallPart):
400402
tool_calls.append(self._map_tool_call(item))
403+
# OpenAI doesn't return server tools calls.
404+
elif isinstance(item, (ServerToolCallPart, ServerToolReturnPart)):
405+
continue
401406
else:
402407
assert_never(item)
403408
message_param = chat.ChatCompletionAssistantMessageParam(role='assistant')
@@ -779,6 +784,9 @@ async def _map_messages(
779784
openai_messages.append(responses.EasyInputMessageParam(role='assistant', content=item.content))
780785
elif isinstance(item, ToolCallPart):
781786
openai_messages.append(self._map_tool_call(item))
787+
# OpenAI doesn't return server tools calls.
788+
elif isinstance(item, (ServerToolCallPart, ServerToolReturnPart)):
789+
continue
782790
else:
783791
assert_never(item)
784792
else:

tests/models/cassettes/test_anthropic/test_anthropic_code_execution_tool.yaml

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ interactions:
88
connection:
99
- keep-alive
1010
content-length:
11-
- '143'
11+
- '250'
1212
content-type:
1313
- application/json
1414
host:
@@ -21,38 +21,64 @@ interactions:
2121
- text: How much is 3 * 12390?
2222
type: text
2323
role: user
24-
model: claude-3-5-sonnet-latest
24+
model: claude-sonnet-4-0
25+
stream: false
26+
tool_choice:
27+
type: auto
28+
tools:
29+
- name: code_execution
30+
type: code_execution_20250522
2531
uri: https://api.anthropic.com/v1/messages?beta=true
2632
response:
2733
headers:
2834
connection:
2935
- keep-alive
3036
content-length:
31-
- '370'
37+
- '927'
3238
content-type:
3339
- application/json
3440
strict-transport-security:
3541
- max-age=31536000; includeSubDomains; preload
3642
transfer-encoding:
3743
- chunked
3844
parsed_body:
45+
container:
46+
expires_at: '2025-05-26T13:30:45.703429+00:00'
47+
id: container_011CPW9LpfbF8dmXMvVNCiQJ
3948
content:
40-
- text: |-
41-
Let me calculate that:
42-
43-
3 * 12390 = 37170
49+
- text: I'll calculate 3 * 12390 for you.
4450
type: text
45-
id: msg_01D3uRsKuEcst7jtMdwoqYUi
46-
model: claude-3-5-sonnet-20241022
51+
- id: srvtoolu_01CPfaeVC7ju4VsdzxjSLDrY
52+
input:
53+
code: |-
54+
result = 3 * 12390
55+
print(f"3 * 12390 = {result}")
56+
name: code_execution
57+
type: server_tool_use
58+
- content:
59+
content: []
60+
return_code: 0
61+
stderr: ''
62+
stdout: |
63+
3 * 12390 = 37170
64+
type: code_execution_result
65+
tool_use_id: srvtoolu_01CPfaeVC7ju4VsdzxjSLDrY
66+
type: code_execution_tool_result
67+
- text: The answer is **37,170**.
68+
type: text
69+
id: msg_015H6Emn2T8vZhE52mU2jF1U
70+
model: claude-sonnet-4-20250514
4771
role: assistant
4872
stop_reason: end_turn
4973
stop_sequence: null
5074
type: message
5175
usage:
5276
cache_creation_input_tokens: 0
5377
cache_read_input_tokens: 0
54-
input_tokens: 18
55-
output_tokens: 20
78+
input_tokens: 1630
79+
output_tokens: 105
80+
server_tool_use:
81+
web_search_requests: 0
5682
service_tier: standard
5783
status:
5884
code: 200

0 commit comments

Comments
 (0)