Skip to content

Commit 9b48623

Browse files
proeverDouweM
andauthored
Deprecate {FunctionToolCallEvent,FunctionToolResultEvent}.call_id in favor of tool_call_id (#2028)
Co-authored-by: Douwe Maan <douwe@pydantic.dev>
1 parent 81bd32b commit 9b48623

File tree

5 files changed

+40
-23
lines changed

5 files changed

+40
-23
lines changed

pydantic_ai_slim/pydantic_ai/_agent_graph.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,6 @@ async def process_function_tools( # noqa C901
641641
run_context = build_run_context(ctx)
642642

643643
calls_to_run: list[tuple[Tool[DepsT], _messages.ToolCallPart]] = []
644-
call_index_to_event_id: dict[int, str] = {}
645644
for call in tool_calls:
646645
if (
647646
call.tool_name == output_tool_name
@@ -668,7 +667,6 @@ async def process_function_tools( # noqa C901
668667
else:
669668
event = _messages.FunctionToolCallEvent(call)
670669
yield event
671-
call_index_to_event_id[len(calls_to_run)] = event.call_id
672670
calls_to_run.append((tool, call))
673671
elif mcp_tool := await _tool_from_mcp_server(call.tool_name, ctx):
674672
if stub_function_tools:
@@ -683,7 +681,6 @@ async def process_function_tools( # noqa C901
683681
else:
684682
event = _messages.FunctionToolCallEvent(call)
685683
yield event
686-
call_index_to_event_id[len(calls_to_run)] = event.call_id
687684
calls_to_run.append((mcp_tool, call))
688685
elif call.tool_name in output_schema.tools:
689686
# if tool_name is in output_schema, it means we found a output tool but an error occurred in
@@ -700,13 +697,13 @@ async def process_function_tools( # noqa C901
700697
content=content,
701698
tool_call_id=call.tool_call_id,
702699
)
703-
yield _messages.FunctionToolResultEvent(part, tool_call_id=call.tool_call_id)
700+
yield _messages.FunctionToolResultEvent(part)
704701
output_parts.append(part)
705702
else:
706703
yield _messages.FunctionToolCallEvent(call)
707704

708705
part = _unknown_tool(call.tool_name, call.tool_call_id, ctx)
709-
yield _messages.FunctionToolResultEvent(part, tool_call_id=call.tool_call_id)
706+
yield _messages.FunctionToolResultEvent(part)
710707
output_parts.append(part)
711708

712709
if not calls_to_run:
@@ -738,7 +735,7 @@ async def process_function_tools( # noqa C901
738735
for task in done:
739736
index = tasks.index(task)
740737
result = task.result()
741-
yield _messages.FunctionToolResultEvent(result, tool_call_id=call_index_to_event_id[index])
738+
yield _messages.FunctionToolResultEvent(result)
742739

743740
if isinstance(result, _messages.RetryPromptPart):
744741
results_by_index[index] = result

pydantic_ai_slim/pydantic_ai/messages.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import pydantic
1212
import pydantic_core
1313
from opentelemetry._events import Event # pyright: ignore[reportPrivateImportUsage]
14-
from typing_extensions import TypeAlias
14+
from typing_extensions import TypeAlias, deprecated
1515

1616
from . import _utils
1717
from ._utils import (
@@ -1009,10 +1009,16 @@ class FunctionToolCallEvent:
10091009
"""Event type identifier, used as a discriminator."""
10101010

10111011
@property
1012-
def call_id(self) -> str:
1013-
"""An ID used for matching details about the call to its result. If present, defaults to the part's tool_call_id."""
1012+
def tool_call_id(self) -> str:
1013+
"""An ID used for matching details about the call to its result."""
10141014
return self.part.tool_call_id
10151015

1016+
@property
1017+
@deprecated('`call_id` is deprecated, use `tool_call_id` instead.')
1018+
def call_id(self) -> str:
1019+
"""An ID used for matching details about the call to its result."""
1020+
return self.part.tool_call_id # pragma: no cover
1021+
10161022
__repr__ = _utils.dataclasses_no_defaults_repr
10171023

10181024

@@ -1022,11 +1028,14 @@ class FunctionToolResultEvent:
10221028

10231029
result: ToolReturnPart | RetryPromptPart
10241030
"""The result of the call to the function tool."""
1025-
tool_call_id: str
1026-
"""An ID used to match the result to its original call."""
10271031
event_kind: Literal['function_tool_result'] = 'function_tool_result'
10281032
"""Event type identifier, used as a discriminator."""
10291033

1034+
@property
1035+
def tool_call_id(self) -> str:
1036+
"""An ID used to match the result to its original call."""
1037+
return self.result.tool_call_id
1038+
10301039
__repr__ = _utils.dataclasses_no_defaults_repr
10311040

10321041

tests/models/test_bedrock.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -413,8 +413,7 @@ async def get_temperature(city: str) -> str:
413413
content='30°C',
414414
tool_call_id='tooluse_lAG_zP8QRHmSYOwZzzaCqA',
415415
timestamp=IsDatetime(),
416-
),
417-
tool_call_id='tooluse_lAG_zP8QRHmSYOwZzzaCqA',
416+
)
418417
),
419418
PartStartEvent(index=0, part=TextPart(content='The')),
420419
FinalResultEvent(tool_name=None, tool_call_id=None),

tests/models/test_google.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -375,8 +375,7 @@ async def get_temperature(city: str) -> str:
375375
FunctionToolResultEvent(
376376
result=ToolReturnPart(
377377
tool_name='get_capital', content='Paris', tool_call_id=IsStr(), timestamp=IsDatetime()
378-
),
379-
tool_call_id=IsStr(),
378+
)
380379
),
381380
PartStartEvent(
382381
index=0,
@@ -386,8 +385,7 @@ async def get_temperature(city: str) -> str:
386385
FunctionToolResultEvent(
387386
result=ToolReturnPart(
388387
tool_name='get_temperature', content='30°C', tool_call_id=IsStr(), timestamp=IsDatetime()
389-
),
390-
tool_call_id=IsStr(),
388+
)
391389
),
392390
PartStartEvent(index=0, part=TextPart(content='The temperature in Paris')),
393391
FinalResultEvent(tool_name=None, tool_call_id=None),

tests/test_streaming.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -988,8 +988,7 @@ def known_tool(x: int) -> int:
988988
tool_name='unknown_tool',
989989
tool_call_id=IsStr(),
990990
timestamp=IsNow(tz=timezone.utc),
991-
),
992-
tool_call_id=IsStr(),
991+
)
993992
),
994993
FunctionToolCallEvent(
995994
part=ToolCallPart(tool_name='known_tool', args={'x': 5}, tool_call_id=IsStr()),
@@ -1000,8 +999,7 @@ def known_tool(x: int) -> int:
1000999
content=10,
10011000
tool_call_id=IsStr(),
10021001
timestamp=IsNow(tz=timezone.utc),
1003-
),
1004-
tool_call_id=IsStr(),
1002+
)
10051003
),
10061004
FunctionToolCallEvent(
10071005
part=ToolCallPart(
@@ -1052,8 +1050,7 @@ def call_final_result_with_bad_data(messages: list[ModelMessage], info: AgentInf
10521050
content='Output tool not used - result failed validation.',
10531051
tool_call_id=IsStr(),
10541052
timestamp=IsNow(tz=timezone.utc),
1055-
),
1056-
tool_call_id=IsStr(),
1053+
)
10571054
),
10581055
]
10591056
)
@@ -1080,3 +1077,20 @@ class CityLocation(BaseModel):
10801077
]
10811078
)
10821079
assert result.is_complete
1080+
1081+
1082+
def test_function_tool_event_tool_call_id_properties():
1083+
"""Ensure that the `tool_call_id` property on function tool events mirrors the underlying part's ID."""
1084+
# Prepare a ToolCallPart with a fixed ID
1085+
call_part = ToolCallPart(tool_name='sample_tool', args={'a': 1}, tool_call_id='call_id_123')
1086+
call_event = FunctionToolCallEvent(part=call_part)
1087+
1088+
# The event should expose the same `tool_call_id` as the part
1089+
assert call_event.tool_call_id == call_part.tool_call_id == 'call_id_123'
1090+
1091+
# Prepare a ToolReturnPart with a fixed ID
1092+
return_part = ToolReturnPart(tool_name='sample_tool', content='ok', tool_call_id='return_id_456')
1093+
result_event = FunctionToolResultEvent(result=return_part)
1094+
1095+
# The event should expose the same `tool_call_id` as the result part
1096+
assert result_event.tool_call_id == return_part.tool_call_id == 'return_id_456'

0 commit comments

Comments
 (0)