Gemini Live
Real-time speech-to-speech with GeminiLiveLLMService over the Gemini Live API, with server or local VAD, tools, thinking and the Gemini File API.
Overview
GeminiLiveLLMService connects to the Gemini Live API over a WebSocket. It takes user audio
(and text and video), and returns spoken audio, transcripts and tool calls, so one service
replaces the STT, LLM and TTS stages of a pipeline.
GeminiLiveLLMService: the Live session, with server-side or local VAD, function calling, thinking, context window compression, session resumption and Google Search grounding.GeminiVADParamsandContextWindowCompressionParams: settings objects for VAD and compression.GeminiFileAPI: a client for the Gemini File API (upload, get, list, delete), available asllm.fileApi.
For Vertex AI, see Gemini Live on Vertex AI.
Upstream page
Pipecat's documentation for the same service.
Source
packages/google/src/gemini_live/llm.ts and file_api.ts.
Gemini Live API docs
Models, voices, VAD and session management.
Installation
bun add @pipedog/googleimport {
ContextWindowCompressionParams,
GeminiLiveLLMService,
GeminiVADParams,
} from "@pipedog/google/gemini_live/llm";
import { GeminiFileAPI } from "@pipedog/google/gemini_live/file_api";Prerequisites
- A Gemini API key from Google AI Studio.
- Pass the key as
apiKey. The examples read it fromGOOGLE_API_KEY.
GeminiLiveLLMService
Configuration
Prop
Type
Settings
Passed as settings: new GeminiLiveLLMService.Settings({...}). They are sent when the session
connects. Changing them later stores the new values but doesn't apply them to the open session
(a warning names the settings that didn't take effect).
| Setting | Type | Default | Description |
|---|---|---|---|
model | string | "models/gemini-3.8-live" | Gemini Live model. |
voice | string | "Charon" | Voice name. |
language | Language | string | "en-US" | Language. A Language value is sent as Gemini's locale code (Language.ES as "es-ES"). |
modalities | GeminiModalities | GeminiModalities.AUDIO | Response modality. TEXT logs a warning: recent models may not support it. |
mediaResolution | GeminiMediaResolution | GeminiMediaResolution.UNSPECIFIED | Video input resolution. |
vad | GeminiVADParams | null | null (server VAD on) | Voice activity detection. |
turnCoverage | TurnCoverage | null | null (model default) | Which realtime input a user turn covers. |
contextWindowCompression | ContextWindowCompressionParams | {} (off) | Context window compression. |
thinking | ThinkingConfig | {} | Thinking configuration. Thinking models get thinkingLevel: "LOW" when none is set. |
enableAffectiveDialog | boolean | false | Affective dialog. |
proactivity | ProactivityConfig | {} | Proactivity configuration. |
maxTokens | number | null | 4096 | Maximum output tokens. |
temperature, topK, topP, seed | number | null | null | Sampling settings. Gemini treats seed as best effort. |
GeminiVADParams:
Prop
Type
ContextWindowCompressionParams:
Prop
Type
Usage
import { LLMContext } from "@pipedog/core/processors/aggregators/llm_context";
import { Language } from "@pipedog/core/transcriptions/language";
import {
ContextWindowCompressionParams,
GeminiLiveLLMService,
GeminiVADParams,
} from "@pipedog/google/gemini_live/llm";
const llm = new GeminiLiveLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
systemInstruction: "You are a friendly voice assistant. Keep answers short.",
settings: new GeminiLiveLLMService.Settings({
model: "models/gemini-3.8-live",
voice: "Puck",
language: Language.EN_US,
}),
});Compression and thinking:
const tuned = new GeminiLiveLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
settings: new GeminiLiveLLMService.Settings({
temperature: 0.6,
contextWindowCompression: new ContextWindowCompressionParams({
enabled: true,
triggerTokens: 8000,
}),
thinking: { includeThoughts: true },
}),
});Local VAD. Disable server-side VAD and configure a VAD analyzer on the user aggregator; the
service then sends activity start and end signals to Gemini and replays a short pre-roll of
audio (userAudioPrerollSecs) so the start of speech isn't clipped:
// Turn off server-side VAD. A local VAD in the user aggregator then drives
// turns, and the service sends activity start/end signals to Gemini.
const locallyDriven = new GeminiLiveLLMService({
apiKey: process.env.GOOGLE_API_KEY ?? "",
settings: new GeminiLiveLLMService.Settings({
vad: new GeminiVADParams({ disabled: true }),
}),
});Methods
| Method | Description |
|---|---|
setAudioInputPaused(paused) | Stop (true) or resume (false) sending input audio. |
setVideoInputPaused(paused) | Stop or resume sending video frames. |
setModelModalities(modalities) | Set the response modality. |
setLanguage(language) | Set the language. |
fileApi | The GeminiFileAPI client, created on first access. |
Gemini File API
Upload a file, then reference its uri in a file_data content part of a context message:
// A path is read with node:fs/promises (Bun, Node, Deno). On any runtime,
// including Workers, pass the contents instead (bytes, a Blob or a stream):
// llm.fileApi.uploadFile(new TextEncoder().encode(menu), "menu.txt").
const uploaded = await llm.fileApi.uploadFile("menu.txt", "Menu");
const file = uploaded.file as { uri: string };
const context = new LLMContext({
messages: [
{
role: "user",
content: [
{ type: "text", text: "What's on the menu today?" },
{ type: "file_data", file_data: { mime_type: "text/plain", file_uri: file.uri } },
],
},
],
});GeminiFileAPI has uploadFile(file, displayName?, { mimeType? }), getFile(name),
listFiles(pageSize?, pageToken?) and deleteFile(name). Each returns the parsed JSON response
(or true for deleteFile) and throws when the API returns an error. To build one yourself:
Prop
Type
Event handlers
Inherited from LLMService.
| Event | Description |
|---|---|
on_function_calls_started | Function calls were received and are about to run. |
on_function_calls_cancelled | In-progress function calls were cancelled. |
Notes
- Bot audio comes out as
TTSStartedFrame,TTSAudioRawFrames andTTSStoppedFrame, with the bot's transcript asLLMTextFrames andTTSTextFrames. User transcripts come out asTranscriptionFrames, aggregated into sentences with a 0.5 second fallback timeout. - With server-side VAD, the service emits no
UserStartedSpeakingFrameorUserStoppedSpeakingFrame. Processors that depend on them (RTVI user speaking events, user idle and mute handling, turn recording) do nothing unless the user aggregator has a VAD analyzer.LLMContextAggregatorPairdetects this service, so context writes are correct either way. - Tools in the context take precedence over
toolsgiven at construction. Tools can't change mid-session:LLMSetToolsFrameis dropped, with a warning. - Functions registered with
cancelOnInterruption: falseuse Gemini'sNON_BLOCKINGtool behavior on models that support it (3.8 and later, and pre-3.0). On Gemini 3.x models before 3.8, the service logs an error and pushes an error the first time one is used. - Grounding metadata (for example from a Google Search tool) produces an
LLMSearchResponseFrame, as withGoogleLLMService. - Video frames are throttled to one per second and sent as JPEG.
- The service reconnects after a connection error, resuming the session with the last resumption handle. After three consecutive failures it reports an error and stops.
- An
EndFramewaits for the bot's turn to finish, for at most 30 seconds. - On thinking models, a turn that Gemini reports as still in progress is held open, so a reply
spanning several
turn_completemessages is one assistant turn. - A new system instruction or tools from the context make the service reconnect the session.
Runtime notes
| Runtime | Works | What it needs |
|---|---|---|
| Bun | ✅ yes | Nothing. |
| Node.js | ✅ yes | Nothing. |
| Deno | ✅ yes | Nothing. |
| Cloudflare Workers | ✅ yes | Nothing. |
Generated from pipecat.runtimes and pipecat.compat in packages/google/package.json: ✅ means the compat smoke test passes on that runtime.
The client is GoogleGenAI from @google/genai, which opens the Live API over the global
WebSocket. Server messages arrive through the SDK's callbacks and are fed to the service's
receive task, so socket errors go through the same reconnect path as upstream.
GeminiFileAPI uses the global fetch (injectable with fetch). uploadFile() takes a path,
as upstream does, or the file's contents (a Uint8Array, an ArrayBuffer, a Blob or a
ReadableStream). Contents work on every runtime, Cloudflare Workers included. A path is read
with the injected fs, or with node:fs/promises imported on first use (Bun, Node, Deno); on a
runtime without it, a path throws an error that says to pass the contents. The MIME type comes
from mimeType, else the path's extension, else a Blob's type, else displayName's
extension, else application/octet-stream.
historyConfig is set on the connect config as upstream does, but @google/genai 2.27 does
not forward it in the setup message.
The deprecated upstream InputParams class and the model, voiceId and params
constructor options are not ported; use settings.
pipedog is an unofficial community TypeScript port of Pipecat. It is not affiliated with or endorsed by Daily or the Pipecat project.
Portions adapted from the Pipecat documentation, Copyright (c) 2024–2025 Daily, BSD-2-Clause (see apps/docs/LICENSE-pipecat-docs).
OpenAI Live
Full-duplex speech-to-speech with OpenAILiveLLMService, which delegates work to a backend model through ResponsesDelegation or ClientDelegation.
Gemini Live on Vertex AI
Real-time speech-to-speech with GeminiLiveVertexLLMService, the Gemini Live API through Vertex AI with service account or ADC authentication.