Skip to content

fix(google): fail generate_reply when the server ends the turn without a reply - #6713

Open
biztex wants to merge 2 commits into
livekit:mainfrom
biztex:fix/gemini-aborted-turn
Open

fix(google): fail generate_reply when the server ends the turn without a reply#6713
biztex wants to merge 2 commits into
livekit:mainfrom
biztex:fix/gemini-aborted-turn

Conversation

@biztex

@biztex biztex commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #6708.

Problem

generate_reply() parks a future that only generation_created ever resolves, backed by a 5s timeout. When the server ends a turn without generating anything — turn_complete_reason=MALFORMED_FUNCTION_CALL, or a rejected response — no generation is created, so _handle_server_content takes its if not current_gen: early return and nothing settles the future.

The caller then waits out the full five seconds for an outcome the server reported in ~250ms. @luce98 measured it: 12 aborts, 12 timeouts, 1:1, with the failure arriving instantly each time. That's five seconds of dead air stacked on top of a failure.

turn_complete_reason is never read anywhere in the plugin today.

Fix

When a turn completes with no generation in flight, the pending generate_reply future is failed immediately, carrying the reason the server gave:

the server ended the turn without generating a reply: MALFORMED_FUNCTION_CALL

The caller gets a RealtimeError as soon as the abort lands rather than at the timeout, and the reason is in the message and the log rather than being dropped.

Deliberately narrow: only turn_complete settles it, and only when there is no current generation. A turn that produced a generation is untouched, and a stray frame mid-request can't fail a live call.

Verification

tests/test_plugin_google_realtime.py:

  • a turn_complete carrying MALFORMED_FUNCTION_CALL fails the pending future at once, with the reason in the error — fails on main, where the future is still pending
  • a turn_complete with no reason still fails the caller rather than stranding it
  • content without turn_complete leaves a live request alone

ruff format --check, ruff check and mypy -p livekit.plugins.google pass locally.

…t a reply

a turn the server rejects outright - a malformed function call, a rejected
response - never creates a generation, and the pending generate_reply future
is only ever resolved by generation_created. The caller sat through the full
5s timeout for an outcome the server had already reported in a fraction of a
second, which lands as dead air on top of a failure.

The turn ending with no generation now fails that future straight away,
carrying the reason the server gave.
@biztex
biztex requested a review from a team as a code owner August 5, 2026 16:17
devin-ai-integration[bot]

This comment was marked as resolved.

…ng generation

_current_generation is never cleared, so keying the failure off the "no
generation" branch made it unreachable from the second reply of a call
onwards - the reported case, an abort mid-conversation, was untouched.

It now settles on turn_complete itself: the pending future is cleared as soon
as a generation is created for it, so one still parked there means the server
ended the turn without producing anything. A tool-rejection drain also ends
with turn_complete but its reply is still coming, so that path is excluded.
@biztex
biztex force-pushed the fix/gemini-aborted-turn branch from 29be1e9 to 20234c2 Compare August 5, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

generate_reply() waits out its full 5s timeout even when the server aborts the turn in ~250ms with turn_complete_reason=MALFORMED_FUNCTION_CALL

1 participant