Skip to content

Gemini provider: per-turn 'gem_<index>' tool-call ids collide across turns, corrupting multi-turn tool-result pairing #45

@TYRMars

Description

@TYRMars

Summary

GoogleProvider synthesises tool-call ids as gem_<index> where the index resets every turn, so ids collide across turns of a multi-turn conversation. This corrupts functionResponse name resolution and breaks tool-call/tool-result pairing on the 2nd+ tool-using turn.

Details

Tool-call ids are minted per-response, scoped to a single turn:

Every Gemini turn that emits tool calls therefore produces ids starting again from gem_0, gem_1, …

When building the next request, GoogleRequest::from_request constructs an id_to_name map keyed by id over the whole message history with last-writer-wins:

So turn 1's gem_0 (e.g. fs.read) gets overwritten by turn 3's gem_0 (e.g. code.grep). When turn 1's Message::Tool { tool_call_id: "gem_0" } is converted to a functionResponse, the name resolves to the wrong tool:

Because Gemini matches functionResponsefunctionCall by name (no ids on the wire), this sends a mismatched/duplicated function name.

Impact

Cryptic Gemini 400s or wrong-tool attribution on any conversation that uses tools across more than one turn. Single-turn tool use is unaffected (ids are unique within a turn). Severity: high.

Suggested fix

Make the synthesised id unique across the whole conversation — e.g. include a turn/message counter (gem_<turn>_<index>) when building the assistant message, or carry the original wire-name alongside the id so result conversion never has to round-trip through a colliding map.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions