Skip to content

Live mode: tool_call can be silently dropped when co-framed with turn_complete in gemini_llm_connection.py #6615

Description

@imazizongit

Description

In google/adk/models/gemini_llm_connection.py, receive()'s handling of message.server_content (the if message.server_content: block starting around line 314) ends its turn_complete handling with an unconditional break around line 538. The sibling if message.tool_call: handler sits at ~line 562 — a separate top-level branch only reached if the loop hasn't already break-ed out.

If a single Live API server message carries both serverContent.turnComplete=true and a populated toolCall in the same frame, the tool_call branch is never reached and the function call is silently dropped — no exception, no log line, nothing observable except a missing function call downstream. The wire protocol appears to allow this framing (both fields are independent keys on one server message per the converter mapping in google-genai's live converters).

How we found it

We were chasing an intermittent bug where a Gemini Live model would compose a function call in its own thinking output (via include_thoughts=True) but the corresponding tool call never arrived client-side. Static reading of the installed google-adk==2.5.0 source turned up this real ordering issue.

We want to flag two things clearly:

  1. This is a genuine defect we can point to in source, independent of our specific bug.
  2. We do not believe it explains our bug. We captured every raw pre-ADK-processing Live API server message for several reproductions (logging at the point gemini_llm_connection.py receives each message, before any ADK-side handling) and in every fabricating turn, the raw message stream simply never contained a tool_call at all — so there was nothing for this code path to drop. Our issue is upstream (model/serving-side), not this ordering bug. But we think this ordering issue is real, reproducible from source, and could bite a different model/config where a turn_complete and a tool_call really do co-occur in one frame.

Suggested fix

Check message.tool_call independently of the turn_complete branch's break (e.g. before the break, or restructure so the two are not mutually exclusive within one message's processing), so a tool_call co-framed with turn_complete is never skipped.

Environment

  • google-adk 2.5.0
  • google-genai 2.10.0
  • Live API (bidi streaming / WebSocket transport), various models including gemini-3.1-flash-live-preview

Metadata

Metadata

Assignees

Labels

live[Component] This issue is related to live, voice and video chat

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions