Skip to content

google: 400 'Requests ending with a model turn are not supported' after barge-in on gemini-3.6-flash — retried in a loop, agent goes silent #2108

Description

@arsenieciprian

Summary

When the user interrupts the agent (barge-in) in a cascaded AgentSession using the Google plugin (Vertex AI), the truncated assistant message is committed to the chat context after the user's transcript. The next LLM request is then sent with a history that ends with a model turn, which newer Gemini models (observed with gemini-3.6-flash) reject deterministically:

400 INVALID_ARGUMENT: "Requests ending with a model turn are not supported."

The plugin marks the error as retryable: true, so LLMStream.run retries the identical request in a loop (3 attempts in our runs) — every retry fails with the same 400, and the agent goes silent for ~25 seconds until the user happens to speak again, which appends a user turn and unblocks the conversation.

Older models (gemini-3.1-flash-lite, gemini-3.5-flash-lite) tolerate trailing model turns, so the bug only surfaces on newer models. Edit 2026-07-27: no longer accurate — gemini-3.5-flash-lite reproduces this in production too (84 errors / 21 lost turns / 7 calls in ~24h). See the update comment below. Google has documented this as intended API behavior for newer Gemini models (prefilled model turns are no longer supported) — see references below.

Environment

  • @livekit/agents: 1.5.5
  • @livekit/agents-plugin-google: 1.5.5 (LLM via Vertex AI, vertexai: true, location global)
  • Model: gemini-3.6-flash (also expected on other current-gen models enforcing the constraint)
  • Node.js v24.18.0, Linux (Rocky 10)
  • Cascaded pipeline: Google Cloud STT → google.LLM → Google Cloud TTS, local VAD (inference.VAD), allowInterruptions: true
  • Telephony (LiveKit SIP), Romanian language — irrelevant to the bug, included for completeness

Timeline from production logs (redacted)

The agent proposes an address; the user barges in with a confirmation while the agent is speaking; the agent's reply is truncated by the interruption:

14:07:22 [METRIC] llm ttft=849ms dur=1161ms
14:07:29 [USER]  Da, da, da, da, acolo.          <- user turn committed (barge-in)
14:07:29 [AGENT] Am găsit Liceul Teoretic Dumitru Tăuțan, de pe strada   <- truncated by interruption,
                                                    committed AFTER the user turn
14:07:29 {"llm":"google.LLM","attempt":1,"error":{"type":"APIConnectionError","message":"Google LLM: API error -
  {\"error\":{\"code\":400,\"message\":\"Requests ending with a model turn are not supported.\",
  \"status\":\"INVALID_ARGUMENT\"}}","retryable":true},"msg":"failed to generate LLM completion, retrying in 0.1ms"}
14:07:29 ... attempt=2, same 400 ...
14:07:32 ... attempt=3, same 400 ...
14:07:34 [METRIC] llm ttft=-1ms dur=5381ms        <- generation gave up
        (~25s of dead air on the phone call)
14:07:49 [USER]  Alo                              <- user speaks again -> a user turn is appended
14:07:50 [METRIC] llm ttft=961ms dur=1250ms       <- next request succeeds

Root cause

LLMStream.run in agents-plugin-google/src/llm.ts sends the chat history exactly as produced by chatCtx.toProviderFormat('google'), with no normalization of the final turn:

const [turns, extraData] = await this.chatCtx.toProviderFormat("google");
const contents = turns.map((turn) => ({ role: turn.role, parts: turn.parts }));

After a barge-in, the committed order in the chat context is [..., user: "Da, da...", model: "<truncated reply>"], so contents ends with a model turn. Newer Gemini models reject that request with a deterministic 400.

Two compounding problems:

  1. No sanitization of the trailing turn for the Google provider, even though the Gemini API now requires the last content to be a user turn.
  2. The 400 is treated as retryable — the identical request is retried and fails identically, turning a deterministic client-side formatting issue into a multi-second outage of the voice session.

Expected behavior

The plugin (or toProviderFormat('google')) should guarantee the request never ends with a model turn — e.g. by reordering a trailing assistant message before the preceding user message (chronologically faithful for the interruption case: the agent spoke the partial reply, then the user reacted), or by any equivalent normalization. Deterministic INVALID_ARGUMENT responses should also not be retried with an unchanged payload.

Repro steps

  1. Cascaded AgentSession with google.LLM on gemini-3.6-flash (Vertex, any location), any STT/TTS, allowInterruptions: true.
  2. Let the agent produce a reply of a few seconds.
  3. Interrupt it mid-sentence with a short user utterance (so the truncated assistant message is committed after the user transcript).
  4. The next generation fails with 400 INVALID_ARGUMENT: "Requests ending with a model turn are not supported." and is retried in a loop; the session produces no speech until a new user turn arrives.

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions