Skip to content

fix(gemini-live): gemini-3.1-flash-live — setup dies with affectiveDialog, session wedges on BLOCKING tool calls - #210

Merged
nicolotognoni merged 3 commits into
mainfrom
fix/gemini-31-flash-live
Jul 5, 2026
Merged

nicolotognoni merged 3 commits into
mainfrom
fix/gemini-31-flash-live

Conversation

@FrancescoRosciano

Copy link
Copy Markdown
Collaborator

What

Two field-debugged fixes that make gemini-3.1-flash-live-preview actually usable, found while moving the Pillar demo line to 3.1:

  1. Setup: enableAffectiveDialog/proactivity are dated-2.5-native-audio-only knobs. On the flash-live family the server never sends setupComplete if the setup includes them → Gemini Live connect: session ready timeout. Now dropped with a warning on flash-live models. (Empirically isolated with a config-ablation matrix: minimal/voice/it-IT/VAD/instructions all connect in ~300ms; adding affectiveDialog → timeout, 100% reproducible. 2.5 with the same config connects fine.)

  2. Tool calls: in the default BLOCKING function-call mode, 3.1 halts audio generation when it emits the toolCall and never resumes speech after receiving our toolResponse — no error event, no socket close, just silence until the caller gives up. Hit 2/2 on real calls (first call after long idle, where the model emits the tool call mid-conversation; warm sessions batch it at end-of-call, which is why local repros never triggered it). Fix: opt flash-live into the documented async mode — behavior: 'NON_BLOCKING' on the declarations + scheduling: 'INTERRUPT' on the response. Also logs the tool round-trip at info (it was invisible at default log level, which made this stall undiagnosable in prod).

Both changes are gated on model.includes('flash-live') — 2.5 native-audio and every other model are byte-identical on the wire.

Testing

  • TS suite: 158 files, 2493 passing.
  • Live-verified on the Pillar demo line (Telnyx, it-IT, Cloud Run): 3.1 connects in ~300-450ms, ~1.35s median first-audio per turn, and a NON_BLOCKING qualifica_lead round-trip observed completing with the conversation continuing (toolCall received → toolResponse sent → native lead write → clean goodbye).

Notes

  • Python parity pending: gemini_live.py sends the same BLOCKING-shape FunctionResponse; the same two changes apply.
  • Note for reviewers: the mocked gemini-live test only asserts function_call event emission — the post-toolResponse resume path (where 3.1 wedges) has no coverage yet.

🤖 Generated with Claude Code

https://claude.ai/code/session_019DYHvQ7uFxXVLoEEmduKso

FrancescoRosciano and others added 3 commits July 3, 2026 04:06
…ually work

Two field-debugged fixes for the *-flash-live-preview family, ported onto
the 0.7.1 adapter (both proven on the Pillar demo line):

1. Drop enableAffectiveDialog/proactivity on flash-live models (with a
   warning): they are dated-2.5-native-audio-only setup knobs — including
   them makes the server never send setupComplete, so connect dies with
   'session ready timeout'.

2. Opt flash-live into the Live API's async function-calling mode:
   behavior NON_BLOCKING on functionDeclarations + scheduling INTERRUPT
   on the FunctionResponse. In the default BLOCKING mode 3.1 halts audio
   generation at the toolCall and never resumes speech after receiving
   the response — no error, no socket close, just silence until the
   caller hangs up (reproduced 2/2 on the first call after long idle,
   where the model emits the tool call mid-conversation). Also log the
   tool round-trip at info: it was invisible in prod at default log
   level, which made the stall undiagnosable.

Gated on the model family — 2.5 native-audio and every other model are
byte-identical on the wire. TS suite: 2493 passing. Python parity pending
(gemini_live.py has the same BLOCKING shape).
…s + parity

The gemini-3.1-flash-live async-mode tool-call fix shipped in TypeScript only.
This closes the SDK-parity gap and adds the missing test coverage:

- Python: opt flash-live into NON_BLOCKING declarations + INTERRUPT scheduling
  on the tool response (default BLOCKING wedges 3.1 into a silent stall after
  the toolCall), log the tool round-trip at info, and add
  GeminiLiveModel.LIVE_3_1_FLASH_PREVIEW to match the TS constant.
- TypeScript: fold the duplicated flash-live predicate into a single
  isFlashLive getter, and warn symmetrically when proactiveAudio is dropped on
  flash-live (previously silent, unlike affectiveDialog).
- Tests: new authentic mocked coverage in both SDKs for the async-mode
  declaration + tool-response shaping — the post-toolResponse resume path that
  had no coverage. Python gemini-live had no test file at all before this.

The affectiveDialog/proactivity setup fix is TS-only by nature: the Python
adapter never exposed those knobs, so it never sent them.
@nicolotognoni

Copy link
Copy Markdown
Collaborator

Follow-up commit f94d05f closes the two caveats noted above (Python parity + missing resume-path coverage):

Python parity — gemini_live.py now opts flash-live into the same async mode: behavior: NON_BLOCKING on the function declaration + scheduling: INTERRUPT on the tool response, the tool round-trip is logged at info (with a try/except that logs+re-raises on send failure), and GeminiLiveModel.LIVE_3_1_FLASH_PREVIEW is added to the enum to match the exported TS constant. The affectiveDialog/proactivity setup fix stays TS-only by nature — the Python adapter never exposed those knobs, so it never sent them (called out in the CHANGELOG).

Test coverage — the post-toolResponse resume path now has coverage in both SDKs:

  • libraries/python/tests/test_gemini_live.py (new — there was no Python gemini-live test file before): 6 mocked tests asserting INTERRUPT scheduling on flash-live tool responses, no scheduling on 2.5, NON_BLOCKING on the declaration (and its absence on 2.5), the info log, and log+re-raise on send failure.
  • libraries/typescript/tests/gemini-live-tools.mocked.test.ts (new): 4 tests covering the same declaration + tool-response shaping.

TS cleanup — the duplicated model.includes('flash-live') predicate is folded into a single isFlashLive getter, and proactiveAudio now warns symmetrically with affectiveDialog when dropped on flash-live (previously silent).

All checks green: Python 3.11/3.12/3.13, TS 20/22, security, secret scan, pre-commit, claude-review. Local: Python 6/6, TS 15/15 (4 new + 11 existing, no regressions), tsc --noEmit clean.

@nicolotognoni
nicolotognoni merged commit a95c166 into main Jul 5, 2026
10 checks passed
@github-actions
github-actions Bot deleted the fix/gemini-31-flash-live branch July 6, 2026 08:21
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.

2 participants