The SDK supports the Gemini Live API for real-time bidirectional voice and video interactions over WebSocket.
Overview
The Live API enables:
Real-time audio conversations with voice activity detection (VAD)
Video frame streaming for live visual understanding
Tool calling during live sessions
Session resumption for reconnection without losing context
Auto-reconnect via ResilientLiveSession for production use
Model requirements
The Live API requires a Live-compatible model (e.g., models/gemini-3.1-flash-live-preview) and Audio response modality.
Quick Start
1 2 3 4 5 6 7 8 910111213141516171819202122
usingGoogle.Gemini;usingvarclient=newGeminiClient(apiKey);awaitusingvarsession=awaitclient.ConnectLiveAsync(newLiveSetupConfig{Model="models/gemini-3.1-flash-live-preview",GenerationConfig=newGenerationConfig{ResponseModalities=[GenerationConfigResponseModalitie.Audio],},});// Send text, receive audioawaitsession.SendTextAsync("Hello, how are you?");awaitforeach(varmessageinsession.ReadEventsAsync()){// Audio data in message.ServerContent.ModelTurn.Parts[].InlineDataif(message.ServerContent?.TurnComplete==true)break;}
Session Types
GeminiLiveSession
The basic session class for direct WebSocket communication:
A wrapper that automatically reconnects when the server sends a GoAway message (e.g., for maintenance). Recommended for production use:
1 2 3 4 5 6 7 8 910111213
awaitusingvarsession=awaitclient.ConnectResilientLiveAsync(config);session.GoAwayReceived+=(sender,goAway)=>Console.WriteLine($"Server closing in {goAway.TimeLeft}, reconnecting...");session.Reconnected+=(sender,_)=>Console.WriteLine("Reconnected!");// Events flow transparently across reconnectionsawaitforeach(varmessageinsession.ReadEventsAsync()){if(message.ServerContent?.TurnComplete==true)break;}
Sending Input
Text
12
// Sends text as a complete user turn (triggers model response)awaitsession.SendTextAsync("What's the weather?");
// Send a JPEG frameawaitsession.SendVideoAsync(jpegBytes,"image/jpeg");// Stream video at ~10 fpsforeach(varframeinvideoFrames){awaitsession.SendVideoAsync(frame,"image/jpeg");awaitTask.Delay(100);}
Multi-turn Conversation
Use sequential SendTextAsync calls (wait for TurnComplete between turns):
123456789
// First turnawaitsession.SendTextAsync("My name is Alice");awaitforeach(varmsginsession.ReadEventsAsync()){if(msg.ServerContent?.TurnComplete==true)break;}// Second turn — the model remembers contextawaitsession.SendTextAsync("What's my name?");
awaitforeach(varmessageinsession.ReadEventsAsync()){// Audio/text model responseif(message.ServerContent?.ModelTurn?.Partsis{}parts){foreach(varpartinparts){if(part.InlineData?.Datais{}audioData)PlayAudio(audioData);// 24kHz PCMif(part.Textis{}text)Console.Write(text);}}// Output transcription (text version of audio response)if(message.ServerContent?.OutputTranscription?.Textis{}transcript)Console.Write(transcript);// Input transcription (text version of audio you sent)if(message.ServerContent?.InputTranscription?.Textis{}inputText)Console.Write($"[You said: {inputText}]");// Model was interrupted by new user inputif(message.ServerContent?.Interrupted==true)Console.Write("[interrupted]");// Tool call requestif(message.ToolCallis{}toolCall)HandleToolCall(toolCall);// Tool call was cancelled (user interrupted)if(message.ToolCallCancellationis{}cancellation)Console.Write($"Cancelled: {string.Join(",", cancellation.Ids!)}");// Token usage (3.1+ uses ResponseTokenCount instead of CandidatesTokenCount)if(message.UsageMetadatais{}usage)Console.Write($"[Tokens: {usage.TotalTokenCount ?? usage.ResponseTokenCount}]");// Server requesting disconnect (handled automatically by ResilientLiveSession)if(message.GoAwayis{}goAway)Console.Write($"[Server closing in {goAway.TimeLeft}]");if(message.ServerContent?.TurnComplete==true)break;}
varconfig=newLiveSetupConfig{// ...SystemInstruction=newContent{Parts=[newPart{Text="You are a friendly pirate. Always respond in pirate speak."}],},};
Transcription
12345678
varconfig=newLiveSetupConfig{// ...// Get text alongside audio responsesOutputAudioTranscription=newLiveOutputAudioTranscription(),// Get text for audio you sendInputAudioTranscription=newLiveInputAudioTranscription(),};
Context Window Compression
For longer sessions that might exceed the context window:
1 2 3 4 5 6 7 8 91011
varconfig=newLiveSetupConfig{// ...ContextWindowCompression=newLiveContextWindowCompression{SlidingWindow=newLiveSlidingWindow{TargetTokens=1024,// tokens to retain after compression},},};
Session Resumption
Reconnect without losing conversation context:
1 2 3 4 5 6 7 8 91011121314151617
varconfig=newLiveSetupConfig{// ...SessionResumption=newLiveSessionResumptionConfig(),};awaitusingvarsession1=awaitclient.ConnectLiveAsync(config);// ... interact ...varhandle=session1.LastSessionResumptionHandle;// Later, reconnect with the handlevarconfig2=newLiveSetupConfig{// ... same config ...SessionResumption=newLiveSessionResumptionConfig{Handle=handle},};awaitusingvarsession2=awaitclient.ConnectLiveAsync(config2);
Tip
ResilientLiveSession handles this automatically when GoAway is received.
varconfig=newLiveSetupConfig{Model="models/gemini-3.1-flash-live-preview",GenerationConfig=newGenerationConfig{ResponseModalities=[GenerationConfigResponseModalitie.Audio],},Tools=[newTool{FunctionDeclarations=[myFunction]}],};awaitusingvarsession=awaitclient.ConnectLiveAsync(config);awaitsession.SendTextAsync("What's the weather in London?");awaitforeach(varmessageinsession.ReadEventsAsync()){if(message.ToolCallis{}toolCall){awaitsession.SendToolResponseAsync([newFunctionResponse{Name=toolCall.FunctionCalls![0].Name,Id=toolCall.FunctionCalls[0].Id,Response=new{temperature="15C"},}]);}if(message.ToolCallCancellationis{}cancellation){Console.WriteLine($"Tool calls cancelled: {string.Join(",", cancellation.Ids!)}");}if(message.ServerContent?.TurnComplete==true)break;}
Choosing Parameters vs ParametersJsonSchema
Use FunctionDeclaration.Parameters when your tool schema is simple and fits naturally into the SDK's typed Schema model.
Use FunctionDeclaration.ParametersJsonSchema when you want to pass raw JSON Schema directly, especially for cases like:
additionalProperties: false
exact propertyOrdering
nested schema metadata or an existing JSON Schema document you want to preserve as-is
Parameters and ParametersJsonSchema are mutually exclusive. The SDK's IChatClient bridge already uses ParametersJsonSchema for this reason.
usingSystem.Text.Json;varweatherSchema=JsonDocument.Parse("""{"type":"object","additionalProperties":false,"propertyOrdering":["location","units","preferences"],"properties":{"location":{"type":"string","description":"The city name"},"units":{"type":"string","enum":["celsius","fahrenheit"]},"preferences":{"type":"object","additionalProperties":false,"propertyOrdering":["includeHumidity"],"properties":{"includeHumidity":{"type":"boolean"}}}},"required":["location"]}""").RootElement.Clone();varmyFunction=newFunctionDeclaration{Name="get_weather",Description="Get the current weather for a location",ParametersJsonSchema=weatherSchema,};
Session Limits
Scenario
Duration
Audio only
~15 minutes
Audio + video
~2 minutes (without compression)
Connection lifetime
~10 minutes (use session resumption or ResilientLiveSession)