fix(gemini): stop splitting one tool call into two when signature arrives late - #28438
fix(gemini): stop splitting one tool call into two when signature arrives late#28438Ricardo-M-L wants to merge 1 commit into
Conversation
|
Duplicate of #15739 (same fix: dedup gemini tool-call slots on (part_index, name) instead of including thought_signature in the key). Both PRs fix the same root cause in gemini_native_adapter.py. |
|
@alt-glitch yes — #15739 is my earlier version of the exact same fix; it was 2k+ commits behind |
…ives late
translate_stream_event() in agent/gemini_native_adapter.py keys
tool_call slots on (part_index, name, thought_signature). Because the
thought_signature is part of the dedup key, a single tool call whose
chunks carry the signature inconsistently (e.g., empty on early chunks,
present on a later one — which Gemini 3 thinking models do) is split
into two separate slots:
- slot 0: built from the early chunks → no signature, partial args
- slot 1: built from the later chunk → has signature, fuller args
Both slots are emitted as deltas, so the agent records *two* tool calls
for what was logically one. On the next turn the slot without a
signature is replayed back to Gemini, which 400s with:
Function call is missing a thought_signature in functionCall parts.
Fix: dedup on (part_index, name) only. The signature is still surfaced
through the per-chunk extra_content field, and the downstream
streaming accumulator (run_agent.py) already does latest-non-None-wins
on extra_content per slot — so whichever chunk carried the signature
gets it merged into the single slot.
Adds a regression test that fails on main and passes here:
test_stream_event_translation_does_not_split_slot_when_signature_arrives_late
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9112aee to
09ce3a2
Compare
|
Thanks for the focused regression fix. I verified the premise against current main and this still reproduces. Suggested changes
Evidence checked:
Automated hermes-sweeper review. |
|
Hi maintainers! This PR fixes a Gemini tool call splitting bug where a single tool call gets incorrectly split into two when the signature arrives late, causing downstream parsing failures. Please let me know if you'd like me to add tests, adjust the approach, or rebase onto the latest main. Thanks! |
Correcting a stale |
|
Hi! Just a friendly ping on this PR. It's been open for a while — would appreciate a review when you get a chance. Thanks for your time! |
Not a duplicate: #15739 is CLOSED (never merged) and is the author's earlier un-rebased branch — this is the active rebased resubmission. Verified on |
Related (not a duplicate): this is @Ricardo-M-L's rebased resubmission of the closed #15739 (same author, earlier PR closed 2026-05-19 because it couldn't rebase cleanly). Verified the fix is not yet on |
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the focused late-signature regression. The reported split remains real on current main: agent/gemini_native_adapter.py:687-703 includes thought_signature in the cross-event slot key, and the streaming generator reuses that key map at agent/gemini_native_adapter.py:977-979.
Problems
- Blocking: removing the signature from the sole key makes every separate
parts[0]call with the same function name share one slot. The downstream accumulator appends arguments for that slot atagent/chat_completion_helpers.py:2436-2437, so parallel calls can become concatenated invalid JSON. This is the active collision scenario documented in #24676; this patch additionally collides calls whose signatures differ.
Suggested changes
- Rework the slot matcher to support both late-signature continuation and distinct parallel same-name calls.
- Retain this regression and add separate-event, same-name parallel calls with distinct arguments/signatures; assert separate ids/indexes and isolated valid argument payloads.
Automated hermes-sweeper review.
| @@ -657,12 +657,19 @@ def translate_stream_event(event: Dict[str, Any], model: str, tool_call_indices: | |||
| args_str = json.dumps(fc.get("args") or {}, ensure_ascii=False, sort_keys=True) | |||
There was a problem hiding this comment.
Dropping the signature from the only cross-event key merges distinct same-name parts[0] calls into one slot. _stream_completion() reuses tool_call_indices for the entire response, and the downstream accumulator appends arguments for a shared slot (agent/chat_completion_helpers.py:2436-2437). Please preserve a reliable distinct-call discriminator and add a parallel-call regression before removing this dimension.
|
Friendly ping - this PR fixes Gemini splitting one tool call into two. Would appreciate a review. Thanks! |
|
Closing duplicate: superseded by #88149 (gemini signature dedup) |
What does this PR do?
translate_stream_event()inagent/gemini_native_adapter.pykeys tool-call slots on(part_index, name, thought_signature). Because thethought_signatureis part of the dedup key, a single tool call whose chunks carry the signature inconsistently — which Gemini 3 thinking models do (e.g., signature is empty on early chunks and present on a later one) — gets split into two separate slots:Both slots are emitted as deltas, so the agent records two tool calls for what was logically one. On the next turn the slot without a signature is replayed back to Gemini, which 400s with:
Fix
Dedup on
(part_index, name)only. The signature is still surfaced through the per-chunkextra_contentfield, and the downstream streaming accumulator inrun_agent.pyalready does latest-non-None-wins onextra_contentper slot — so whichever chunk carried the signature gets it merged into the single slot.The change is one-line in the dedup key plus a comment explaining the constraint.
Test
Adds
test_stream_event_translation_does_not_split_slot_when_signature_arrives_lateintests/agent/test_gemini_native_adapter.py. The test feeds two chunks of the same tool call where only the second carries athoughtSignatureand asserts the events flow into a single slot. Fails onmain(two slots emitted, both reported as distinct tool calls) and passes here.Before submitting