fix(agent): harden canonical tool call deduplication

This commit is contained in:
fangliquanflq
2026-08-16 11:10:57 +08:00
committed by Teknium
parent a55d29d9fb
commit 8fc4189edd
2 changed files with 43 additions and 3 deletions

View File

@@ -4879,19 +4879,28 @@ class AIAgent:
Valid JSON arguments are canonicalized so equivalent objects do not
evade deduplication merely because their keys or whitespace differ.
Malformed arguments retain their raw representation rather than being
repaired here. Only the first occurrence of each unique pair is kept.
Duplicate object keys use the parser's last-value-wins semantics,
matching downstream argument parsing. Malformed or excessively nested
arguments retain their raw representation rather than being repaired
here. Only the first occurrence of each unique pair is kept.
Returns the original list if no duplicates were found.
"""
seen_raw: set = set()
seen: set = set()
unique: list = []
for tc in tool_calls:
raw_key = (tc.function.name, tc.function.arguments)
if raw_key in seen_raw:
logger.warning("Removed duplicate tool call: %s", tc.function.name)
continue
seen_raw.add(raw_key)
arguments = tc.function.arguments
try:
arguments = json.dumps(
json.loads(arguments), separators=(",", ":"), sort_keys=True
)
except (TypeError, ValueError):
except (RecursionError, TypeError, ValueError):
pass
key = (tc.function.name, arguments)
if key not in seen:

View File

@@ -7,6 +7,7 @@ Covers three static methods on AIAgent (inspired by PR #1321 — @alireza78a):
- _uniquify_tool_call_ids() — Phase 2c: duplicate-id repair (lossless pairing)
"""
import json
import types
import pytest
@@ -212,6 +213,36 @@ class TestDeduplicateToolCalls:
assert out == [first, distinct]
def test_recursion_error_uses_raw_string_for_deduplication(self, monkeypatch):
def raise_recursion_error(_arguments):
raise RecursionError("maximum recursion depth exceeded")
monkeypatch.setattr("run_agent.json.loads", raise_recursion_error)
first = make_tc("terminal", '{"command":"one"}')
duplicate = make_tc("terminal", '{"command":"one"}')
out = AIAgent._deduplicate_tool_calls([first, duplicate])
assert out == [first]
def test_byte_identical_duplicate_is_parsed_only_once(self, monkeypatch):
original_loads = json.loads
calls = 0
def track_loads(arguments):
nonlocal calls
calls += 1
return original_loads(arguments)
monkeypatch.setattr("run_agent.json.loads", track_loads)
first = make_tc("terminal", '{"command":"one"}')
duplicate = make_tc("terminal", '{"command":"one"}')
out = AIAgent._deduplicate_tool_calls([first, duplicate])
assert out == [first]
assert calls == 1