From 5b31602c1586e5f5e7ece07c89d18a17b428a46f Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Fri, 28 Aug 2026 06:51:59 -0700 Subject: [PATCH] =?UTF-8?q?docs:=20reconcile=20positional=20pairing=20with?= =?UTF-8?q?=20shared=20=5Fclassify=5Ftool=5Fcall=5Forphans=20(#97167)=20?= =?UTF-8?q?=E2=80=94=20classifier=20docstring=20reflects=20its=20remaining?= =?UTF-8?q?=20consumer;=20empty-id=20filter=20note=20updated?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- agent/agent_runtime_helpers.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/agent/agent_runtime_helpers.py b/agent/agent_runtime_helpers.py index 8055c5016f..4ae7a18315 100644 --- a/agent/agent_runtime_helpers.py +++ b/agent/agent_runtime_helpers.py @@ -3852,11 +3852,20 @@ def _classify_tool_call_orphans(messages: List[Dict[str, Any]]): - ``missing_tool_calls``: the actual tool_call entries with no matching result on ANY alias (after orphaned results are excluded). - This is the single source of truth for orphan *detection*. Callers keep - their divergent remediation strategies — ``sanitize_api_messages`` - inserts stubs, the context compressor strips orphans — but the - id-resolution and variant-expansion rules can never drift between the - two sites again (#58357). + This is the single source of truth for GLOBAL orphan *detection* (does + a matching id exist anywhere in the transcript). Its remaining consumer + is the context compressor's ``_sanitize_tool_pairs`` (strip orphans from + the durable history); the id-resolution and variant-expansion rules live + here so they can never drift between call sites again (#58357). + + ``sanitize_api_messages`` no longer uses the global check: strict + positional providers (DeepSeek v4, Kimi) reject a call whose result is + not in the IMMEDIATELY-following tool run even when a matching id exists + elsewhere, so its pairing pass walks the transcript positionally instead + (#94704) — a strictly stronger invariant that subsumes the global one at + that site. Both share the same ``tool_call_id_variants`` / + ``tool_result_id_variants`` alias policy, which is the part that must + not fork. """ assistant_call_variants: List[tuple[Any, frozenset[str]]] = [] surviving_call_ids: set[str] = set() @@ -4010,13 +4019,13 @@ def sanitize_api_messages(messages: List[Dict[str, Any]]) -> List[Dict[str, Any] tc["function"] = {"name": _EMPTY_NAME_SENTINEL, "arguments": "{}"} # --- Drop tool results with a missing/empty tool_call_id --- - # The orphan-sweep below only ever adds a TRUTHY ``tool_call_id`` to - # ``result_call_ids``, so a message with a missing/empty id is never - # added to that set and can therefore never land in ``orphaned_results`` - # (a set-difference against ``surviving_call_ids``) either — it silently - # passes through this chokepoint untouched and can reach the provider - # with no ``tool_call_id`` at all, which strict OpenAI-compatible - # providers reject as a schema violation. ``repair_message_sequence``'s + # The positional pairing walk below also catches this shape (an id-less + # result expands to zero variants, matches no declared call, and is + # dropped as a positional orphan), but keep the explicit early filter so + # the distinct failure mode keeps its own log line and the guarantee + # doesn't silently depend on the walk's internals: a result with no + # ``tool_call_id`` at all is a schema violation strict OpenAI-compatible + # providers reject outright. ``repair_message_sequence``'s # Pass 1 already drops this shape (`if tc_id and tc_id in # known_tool_ids`) when it runs first on the same list, but any caller # that reaches this function without going through