Conversation
PR #105660 — avoid quadratic work in tool history pairingBoth optimizations check out. The slice→index change preserves the break-on-first-non-tool semantics while removing the O(n) copy per row, and the union reuse is provably equivalent (an orphan result is by definition disjoint from every declared call's variants, so it can never rescue a "missing" call — the comment states exactly this). The scaling tests assert bounds structurally (items read / intersections) rather than with wall-clock timing, so they won't flake on slow CI.
|
_classify_tool_call_orphans rebuilt the surviving-result variant list and scanned all of it for every declared tool call (O(calls x results)). Orphan result variants are disjoint from every declared call, so matching against the already-computed union of all result variants is equivalent and O(1) per call. 1000 pairs: 50.9 ms -> 2.4 ms per classification. Partial salvage of #105660: kept the _classify_tool_call_orphans union reuse; dropped the messages[i+1:] slice -> range() hunk (sub-ms) and tests/agent/test_tool_pairing_scaling.py (read-count assertions are a change-detector). Hunk re-derived by hand: PR base was ~7.9k commits behind. (cherry picked from commit 6879a69)
|
Thanks for this. It is included in #120406 as part of the perf sweep. The orphan-classification union reuse in Closing this in favour of #120406, which is armed to rebase-merge. If that PR doesn't land, I'll reopen this one. |
What does this PR do?
Long tool-heavy histories incur two avoidable quadratic costs during message pairing: positional repair copies the entire remaining history for each assistant call, and compression orphan classification scans result-ID sets again for each declared call. This removes both costs while preserving the existing pairing decisions.
Related Issue
Found by profiling current main (
fef0e16fe19b79ded929209f87c7434270b03825); no linked issue. This preserves the positional pairing policy from #97203. I searched open and closed PRs by the helper names and quadratic/tool-pairing terms; the nearby #104454 changes persistence invalidation, not traversal cost.Type of Change
Changes Made
agent/agent_runtime_helpers.py: walk the immediately following tool-result run by index, avoiding eager suffix copies. Stop at the same first non-tool message.tests/agent/test_tool_pairing_scaling.py: two parametrized operation-count contracts (64 and 256 pairs), covering adjacency, composite aliases, missing calls, orphan results, and retained object identity. No wall-clock assertions.How to Test
218 passed. Copying just the new test file onto the base produces 4 failures; this branch passes all 4. At 256 pairs, the base performs 66,821 history reference reads/copies and 33,152 call/result intersections, exceeding the linear budgets.
A separate 1,000-case seeded differential check through the real repair and compression-cleanup callers also preserved repair counts, persistence cursors, serialized transcripts, and retained message identities. Ruff,
git diff --check, andscripts/check_compat_pointers.pypass.Performance
macOS 26.6.2 arm64, Python 3.11.15; synthetic histories, 5-run medians after warming existing argument-validation cursors. The small reproduction below was run on the base and this branch with the same venv.
These measure local phases, excluding model inference, network latency, and summary generation. Short conversations see small absolute savings. Large compression-cleanup inputs are reachable when current-task retention keeps a long tool chain in the tail: with the default lean-tail settings and a 1M-token window, the real boundary/tail helpers retained 12,002 tail messages in a synthetic input estimated at 519,989 tokens. This PR does not change compression thresholds or retention.
Benchmark reproduction (run from each checkout root with its dev Python)
Run the following with
python -or save it in the checkout root. The outer agent is a minimal stub; the iteration-preparation and compressor methods are real imports. No model request is made. JSON equality checks are outside the timed region.Checklist