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:
- This is a genuine defect we can point to in source, independent of our specific bug.
- 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
Description
In
google/adk/models/gemini_llm_connection.py,receive()'s handling ofmessage.server_content(theif message.server_content:block starting around line 314) ends itsturn_completehandling with an unconditionalbreakaround line 538. The siblingif message.tool_call:handler sits at ~line 562 — a separate top-level branch only reached if the loop hasn't alreadybreak-ed out.If a single Live API server message carries both
serverContent.turnComplete=trueand a populatedtoolCallin the same frame, thetool_callbranch 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 ingoogle-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
thinkingoutput (viainclude_thoughts=True) but the corresponding tool call never arrived client-side. Static reading of the installedgoogle-adk==2.5.0source turned up this real ordering issue.We want to flag two things clearly:
gemini_llm_connection.pyreceives each message, before any ADK-side handling) and in every fabricating turn, the raw message stream simply never contained atool_callat 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_callindependently of theturn_completebranch'sbreak(e.g. before the break, or restructure so the two are not mutually exclusive within one message's processing), so atool_callco-framed withturn_completeis never skipped.Environment