Skip to content

Commit 36929de

Browse files
committed
Fix nested agent tool run lookup for call_id collisions
1 parent 5681255 commit 36929de

File tree

1 file changed

+52
-33
lines changed

1 file changed

+52
-33
lines changed

src/agents/agent_tool_state.py

Lines changed: 52 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,52 @@
88
from .result import RunResult, RunResultStreaming
99

1010
# Ephemeral maps linking tool call objects to nested agent results within the same run.
11-
# Store by object identity to avoid collisions when call IDs repeat across tool calls.
11+
# Store by object identity, and index by a stable signature to avoid call ID collisions.
1212
_agent_tool_run_results_by_obj: dict[int, RunResult | RunResultStreaming] = {}
13-
_agent_tool_run_results_by_call_id: dict[str, set[int]] = {}
13+
_agent_tool_run_results_by_signature: dict[
14+
tuple[str, str, str, str, str | None, str | None],
15+
set[int],
16+
] = {}
17+
_agent_tool_run_result_signature_by_obj: dict[
18+
int,
19+
tuple[str, str, str, str, str | None, str | None],
20+
] = {}
1421

1522

16-
def _index_agent_tool_run_result(tool_call_id: str, tool_call_obj_id: int) -> None:
17-
"""Track tool call objects by call ID for fallback lookup."""
18-
_agent_tool_run_results_by_call_id.setdefault(tool_call_id, set()).add(tool_call_obj_id)
23+
def _tool_call_signature(
24+
tool_call: ResponseFunctionToolCall,
25+
) -> tuple[str, str, str, str, str | None, str | None]:
26+
"""Build a stable signature for fallback lookup across tool call instances."""
27+
return (
28+
tool_call.call_id,
29+
tool_call.name,
30+
tool_call.arguments,
31+
tool_call.type,
32+
tool_call.id,
33+
tool_call.status,
34+
)
35+
36+
37+
def _index_agent_tool_run_result(
38+
tool_call: ResponseFunctionToolCall, tool_call_obj_id: int
39+
) -> None:
40+
"""Track tool call objects by signature for fallback lookup."""
41+
signature = _tool_call_signature(tool_call)
42+
_agent_tool_run_result_signature_by_obj[tool_call_obj_id] = signature
43+
_agent_tool_run_results_by_signature.setdefault(signature, set()).add(tool_call_obj_id)
1944

2045

21-
def _drop_agent_tool_run_result(tool_call_id: str | None, tool_call_obj_id: int) -> None:
46+
def _drop_agent_tool_run_result(tool_call_obj_id: int) -> None:
2247
"""Remove a tool call object from the fallback index."""
23-
if tool_call_id is None:
48+
signature = _agent_tool_run_result_signature_by_obj.pop(tool_call_obj_id, None)
49+
if signature is None:
2450
return
25-
call_ids = _agent_tool_run_results_by_call_id.get(tool_call_id)
26-
if not call_ids:
51+
candidate_ids = _agent_tool_run_results_by_signature.get(signature)
52+
if not candidate_ids:
2753
return
28-
call_ids.discard(tool_call_obj_id)
29-
if not call_ids:
30-
_agent_tool_run_results_by_call_id.pop(tool_call_id, None)
54+
candidate_ids.discard(tool_call_obj_id)
55+
if not candidate_ids:
56+
_agent_tool_run_results_by_signature.pop(signature, None)
3157

3258

3359
def record_agent_tool_run_result(
@@ -36,32 +62,29 @@ def record_agent_tool_run_result(
3662
"""Store the nested agent run result by tool call identity."""
3763
tool_call_obj_id = id(tool_call)
3864
_agent_tool_run_results_by_obj[tool_call_obj_id] = run_result
39-
if isinstance(tool_call.call_id, str):
40-
_index_agent_tool_run_result(tool_call.call_id, tool_call_obj_id)
65+
_index_agent_tool_run_result(tool_call, tool_call_obj_id)
4166

4267

4368
def consume_agent_tool_run_result(
4469
tool_call: ResponseFunctionToolCall,
4570
) -> RunResult | RunResultStreaming | None:
46-
"""Return and drop the stored nested agent run result for the given tool call ID."""
71+
"""Return and drop the stored nested agent run result for the given tool call."""
4772
obj_id = id(tool_call)
4873
run_result = _agent_tool_run_results_by_obj.pop(obj_id, None)
4974
if run_result is not None:
50-
_drop_agent_tool_run_result(tool_call.call_id, obj_id)
75+
_drop_agent_tool_run_result(obj_id)
5176
return run_result
5277

53-
call_id = tool_call.call_id
54-
if not call_id:
55-
return None
56-
57-
candidate_ids = _agent_tool_run_results_by_call_id.get(call_id)
78+
signature = _tool_call_signature(tool_call)
79+
candidate_ids = _agent_tool_run_results_by_signature.get(signature)
5880
if not candidate_ids:
5981
return None
6082
if len(candidate_ids) != 1:
6183
return None
6284

6385
candidate_id = next(iter(candidate_ids))
64-
_agent_tool_run_results_by_call_id.pop(call_id, None)
86+
_agent_tool_run_results_by_signature.pop(signature, None)
87+
_agent_tool_run_result_signature_by_obj.pop(candidate_id, None)
6588
return _agent_tool_run_results_by_obj.pop(candidate_id, None)
6689

6790

@@ -74,11 +97,8 @@ def peek_agent_tool_run_result(
7497
if run_result is not None:
7598
return run_result
7699

77-
call_id = tool_call.call_id
78-
if not call_id:
79-
return None
80-
81-
candidate_ids = _agent_tool_run_results_by_call_id.get(call_id)
100+
signature = _tool_call_signature(tool_call)
101+
candidate_ids = _agent_tool_run_results_by_signature.get(signature)
82102
if not candidate_ids:
83103
return None
84104
if len(candidate_ids) != 1:
@@ -93,18 +113,17 @@ def drop_agent_tool_run_result(tool_call: ResponseFunctionToolCall) -> None:
93113
obj_id = id(tool_call)
94114
run_result = _agent_tool_run_results_by_obj.pop(obj_id, None)
95115
if run_result is not None:
96-
_drop_agent_tool_run_result(tool_call.call_id, obj_id)
116+
_drop_agent_tool_run_result(obj_id)
97117
return
98118

99-
call_id = tool_call.call_id
100-
if not call_id:
101-
return
102-
candidate_ids = _agent_tool_run_results_by_call_id.get(call_id)
119+
signature = _tool_call_signature(tool_call)
120+
candidate_ids = _agent_tool_run_results_by_signature.get(signature)
103121
if not candidate_ids:
104122
return
105123
if len(candidate_ids) != 1:
106124
return
107125

108126
candidate_id = next(iter(candidate_ids))
109-
_agent_tool_run_results_by_call_id.pop(call_id, None)
127+
_agent_tool_run_results_by_signature.pop(signature, None)
128+
_agent_tool_run_result_signature_by_obj.pop(candidate_id, None)
110129
_agent_tool_run_results_by_obj.pop(candidate_id, None)

0 commit comments

Comments
 (0)