Skip to content

fix(gemini): stop parallel tool-call args gluing when thought signatures attach (#102787) - #102809

Open
lEWFkRAD wants to merge 1 commit into
NousResearch:mainfrom
lEWFkRAD:fix/gemini-native-toolcall-key-102787
Open

lEWFkRAD wants to merge 1 commit into
NousResearch:mainfrom
lEWFkRAD:fix/gemini-native-toolcall-key-102787

Conversation

@lEWFkRAD

@lEWFkRAD lEWFkRAD commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Problem

On the native Gemini streaming path, parallel tool calls arrive glued together: one call's arguments is {...}{...} (two JSON objects concatenated), which fails to parse and breaks the turn. Reproduced deterministically against origin/mainjson.decoder.JSONDecodeError: Extra data: line 1 column 19.

Root cause

translate_stream_event keys its per-call accumulator slots with json.dumps({"part_index", "name", "thought_signature"}). Gemini delivers a functionCall part first without a thoughtSignature, then re-delivers the same part once the turn's thought signatures are attached. When that happens the key changes mid-stream ("thought_signature": "" → the real signature), so the second delivery does not find the existing slot: it opens a phantom second slot at a new index and re-emits the full arguments. The consumer (chat_completion_helpers, which appends streamed arguments fragments per call index) therefore appends the complete args a second time onto the first call's slot — producing {"a":1}{"b":2} on one call and a spurious duplicate on the other.

Fix

Make the slot key stable for the life of one call: (part_index, name) only. The signature is excluded from the key and still rides to the consumer via extra_content (unchanged), so round-tripping signatures back to Gemini on the next request is unaffected.

TDD

  • New regression test test_stream_signature_attach_keeps_parallel_call_slots streams the exact issue shape (two read_file calls, signature attached on the second delivery) and asserts every slot parses as single JSON.
  • RED on origin/main (6327930): 1 failed — json.decoder.JSONDecodeError: Extra data: line 1 column 19 (char 18)
  • GREEN on this branch: 1 passed; full file 29 passed, 1 deselected (the deselected test_async_native_client_... fails identically on clean origin/main — pre-existing event-loop flake, unrelated).
  • Adjacent lanes: tests/run_agent/test_repair_tool_call_arguments.py, test_streaming_tool_call_repair.py, tests/agent/test_gemini_schema.py14 passed.

Linked issue

Closes #102787

…res attach

Signed-off-by: lEWFkRAD <SJWATTS89@outlook.com>
Co-Authored-By: Hermes Agent <agent@nousresearch.com>
@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/gemini Google Gemini (AI Studio, Cloud Code) area/streaming Streaming responses: gateway delivery, provider wire duplicate This issue or pull request already exists labels Sep 4, 2026
@alt-glitch

Copy link
Copy Markdown
Contributor

This was generated by AI during triage.

Duplicate of #88149 — same one-line change (drop thought_signature from the translate_stream_event slot key so identity is (part_index, name)), which already superseded #28438 / #15739. The #102787-shaped regression test here is the useful delta; consider contributing it to #88149. Related same-code-site but different-mechanism fixes: #24676, #75528, #99273 (same-name parallel-call collision).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/streaming Streaming responses: gateway delivery, provider wire comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint duplicate This issue or pull request already exists P2 Medium — degraded but workaround exists provider/gemini Google Gemini (AI Studio, Cloud Code) type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parallel tool calls: arguments of two calls concatenated into one string, sanitizer falls back to {}

2 participants