Gemini-3.1-flash-live-preview stalls at low temperature (0.0, 0.1)

TL;DR: On the Live API with gemini-3.1-flash-live-preview, at low temperature (especially temperature=0.0) the model often finishes speaking its actual response, then keeps emitting 10–40 seconds of near-silent audio as modelTurn parts before turnComplete fires. To the user this is a long dead pause (“the model went dead”). It is stochastic and it disappears at temperature ≥ 0.5. Reproducible in a minimal setup.

Environment

  • Model: gemini-3.1-flash-live-preview
  • response_modalities: ["AUDIO"], thinkingLevel: medium, native audio out (voice Sulafat)
  • google-genai Python SDK; raw-WebSocket behaviour is identical

Symptom / wire behaviour

On an affected turn:

  1. The model streams its speech (e.g. ~2s: “That’s ‘eye.’ How do you say ‘hand’?”), bursted faster than real time.
  2. generationComplete fires early.
  3. It keeps sending serverContent.modelTurn audio parts that are near-silent comfort noise (~−55 dBFS), totalling 10–40s.
  4. turnComplete only fires after the full virtual playback of that audio (turnComplete_time ≈ total_generated_audio_seconds), leaving a long silent gap after the real speech ends.
  5. On a minority of affected turns the speech itself truncates mid-sentence (e.g. “Correct! How do you say” → stops), then the tail + delayed turnComplete.

The tail is billed as output audio tokens (responseTokensDetails: AUDIO), so a ~2s answer can cost the tokens of a ~40s response.

The key finding — temperature dependence

Two-part “confirm + ask next” turn, everything else fixed, interleaved runs:

temperature   turns with the tail
-----------   -------------------
0.0           ~6–7 of 8   (+ occasional mid-sentence truncation)
0.3           ~3 of 7     (transitional)
0.5             0 of 7    (clean, turnComplete ~2–3s)
0.7             0 of 7    (clean)
1.0             0 of 7    (clean)

Small n per cell and a single bench, but the separation is stark and monotonic. Knee ≈ 0.4–0.5.

Determinism note

At temperature=0.0, identical input produces the byte-identical transcript, yet the tail length varies from ~1s to ~40s across runs. So token generation is deterministic while the tail / turn-completion is not — the non-determinism appears to live in the audio/turn-completion path, not token sampling.

Ruled out (no effect on the tail)

  • System-prompt wording — an explicit “don’t pad with silence, end your turn immediately” instruction did nothing.
  • Streaming vs not streaming input audio.
  • clientContent vs realtimeInput for the input.
  • Automatic VAD on vs off.
  • thinkingLevel minimal vs medium (medium helped somewhat but did not fix it).

Only turn structure (single-act turns tail far less than two-part “statement + question” turns) and temperature move it.

Minimal repro (needs only an API key)

import asyncio, time
from google import genai
from google.genai import types

MODEL = "gemini-3.1-flash-live-preview"
SYS = ("You are a Ukrainian tutor doing vocab recall. The student says a word; "
       "confirm it briefly, then ask them the next word. Keep turns short.")

async def one(temp):
    client = genai.Client()
    cfg = types.LiveConnectConfig(
        response_modalities=["AUDIO"], temperature=temp, system_instruction=SYS,
        output_audio_transcription=types.AudioTranscriptionConfig(),
        thinking_config=types.ThinkingConfig(thinking_level="medium"))
    async with client.aio.live.connect(model=MODEL, config=cfg) as s:
        await s.send_client_content(
            turns=[types.Content(role="user", parts=[types.Part(text="кіт")])],
            turn_complete=True)
        t0, audio = time.monotonic(), 0
        async for m in s.receive():
            sc = m.server_content
            if sc and sc.model_turn:
                for p in (sc.model_turn.parts or []):
                    if p.inline_data and p.inline_data.data:
                        audio += len(p.inline_data.data)
            if sc and sc.turn_complete:
                print(f"temp={temp}: turnComplete after {time.monotonic()-t0:4.1f}s, "
                      f"{audio/2/24000:4.1f}s audio generated")
                return

for t in (0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0):
    asyncio.run(one(t))

Expected: several temp=0.0 runs print turnComplete after ~15–40s, ~15–40s audio generated for a ~2s answer; temp=1.0 runs print ~2–3s.

Questions for the team

  1. Is this a known issue? Is the post-generationComplete “comfort noise until virtual playback finishes” behaviour intended?
  2. Why is it so strongly temperature-dependent? We’d like temperature=0 for deterministic tool-calling, but it triggers this reliably.
  3. Is there a supported way to suppress the tail at low temperature, or to have generationComplete be treated as end-of-turn rather than waiting out the virtual playback?