Skip to content

Inference: strip Gemma 4 <|channel>thought…<channel|> markers (strip_thinking_tokens only knows <think>) #6375

Description

@cursor

Summary

LiveKit Inference's google/gemma-4-31b-it emits Gemma 4 channel-thought markup that leaks into customer-facing agent output (transcription / TTS / chat history). strip_thinking_tokens only strips <think>/</think>, so Gemma's markers pass through unchanged.

Markers

Per Gemma 4 model card:

  • Open: <|channel>thought (often followed by \n)
  • Close: <channel|>
  • Body: internal reasoning (or empty when thinking is "off" — larger models still emit the empty wrapper)

Example stream content that reaches the agent:

<|channel>thought
Customer wants a $100 refund; check policy.
<channel|>I can help start a return instead.

Or empty:

<|channel>thought
<channel|>Happy to help with that.

Where it leaks

In the Agents SDK Inference LLM path, deltas go through strip_thinking_tokens before becoming ChatChunks:

https://github.com/livekit/agents/blob/main/livekit-agents/livekit/agents/inference/llm.py

THINK_TAG_START = "<think>"
THINK_TAG_END = "</think>"

def strip_thinking_tokens(content: str | None, thinking: asyncio.Event) -> str | None:
    ...

Those tags don't match Gemma's channel markers, so the markup reaches llm_node → TTS / transcription_node / session history.

Expected

Inference (or strip_thinking_tokens) should treat Gemma channel-thought blocks like <think> blocks: drop the markers and any body between them so only the final answer is customer-visible. Prior-turn thoughts also must not re-enter chat history (Gemma model card: "No Thinking Content in History").

Actual

Raw <|channel>thought / <channel|> (and sometimes the thought body) appear in the LiveKit transcription stream used by text chat UIs, and would be spoken on voice.

Repro (Agents SDK + Inference)

  1. AgentSession with inference.LLM(model="google/gemma-4-31b-it")
  2. Multimodal/text chat so agent replies ride lk.transcription
  3. Ask something that triggers a tool-using support turn (refund / damage claim)
  4. Inspect the transcription / chat history for <|channel>thought / <channel|>

Workaround

App-side llm_node filter that strips the Gemma markers before chunks reach TTS/transcription/history (same state-machine pattern as the chain-of-thought recipe, with Gemma's tags).

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions