The Gemini Live API processes continuous streams of audio or text called
***sessions***. You can manage the session lifecycle, from the initial handshake
to graceful termination.

## Limits for sessions

For the Live API, a *session* refers to a persistent connection where input
and output are streamed continuously over a connection.

If the session exceeds ***any*** of the following limits, the connection is
terminated. Note, though, the Live API provides some options (see below) to
handle these session-related limits.

- **Session context window** is limited to 128k tokens.

  Due to this context window limit, here are the approximate maximum session
  lengths based on input modalities:
  - Audio-only input sessions are limited to 15 minutes.
  - Video + audio input are limited to 2 minutes.
- **Connection length** is limited to about 10 minutes.

  You'll receive a
  [*going away* notification](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#going-away-notification)
  about 60 seconds before the connection ends.

Here are some options for handling session-related limits:

- [Compress the session context window](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#compress-context)
  so that the server automatically maintains the context size within the limit.

- [Resume a session](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#resume-session)
  to prevent losing conversation context during brief network disconnects or
  after receiving a *going away* notification.

## Start a session

Visit the
[getting started guide for the Live API](https://firebase.google.com/docs/ai-logic/live-api.md.txt#audio-in-audio-out)
for a full snippet showing how to start a session.

## Update mid-session

The Live API models support the following advanced capabilities for
***mid-session updates***:

- [Add incremental content updates](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#add-incremental-content-updates)

- [Update system instructions](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#update-system-instructions-mid-session)
  *(for Agent Platform Gemini API (formerly Vertex AI) only)*

> [!NOTE]
> **Note:** Firebase AI Logic does *not yet* support affective dialogue or proactive audio when using the Live API. Check back soon!

### Add incremental content updates

You can add incremental updates during an active session. Use this to
send text input, establish session context, or restore session context.

- For longer contexts, we recommend providing a single message summary to free
  up the context window for subsequent interactions.

- For short contexts, you can send turn-by-turn interactions to represent the
  exact sequence of events, like the snippet below.

### Swift

```swift
// Define initial turns (history/context).
let turns: [ModelContent] = [
  ModelContent(role: "user", parts: [TextPart("What is the capital of France?")]),
  ModelContent(role: "model", parts: [TextPart("Paris")]),
]

// Send history, keeping the conversational turn OPEN (false).
await session.sendContent(turns, turnComplete: false)

// Define the new user query.
let newTurn: [ModelContent] = [
  ModelContent(role: "user", parts: [TextPart("What is the capital of Germany?")]),
]

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.sendContent(newTurn, turnComplete: true)
```

### Kotlin

```kotlin
// Define initial turns (history/context).
val turns = listOf(
  content("user") {
    text("What is the capital of France?")
  },
  content("model") {
    text("Paris")
  }
)

// Send history, keeping the conversational turn OPEN (false).
turns.forEach {
  session.send(
    content = it,
    turnComplete = false
  )
}

// Define the new user query.
val newTurn = content("user") {
  text("What is the capital of Germany?")
}

// Send the final query, CLOSING the turn (true) to trigger the model response.
session.send(
  content = newTurn,
  turnComplete = true
)
```

### Java

```java
// Define initial turns (history/context).
List turns =
    Arrays.asList(
        new Content.Builder().setRole("user").addText("What is the capital of France?").build(),
        new Content.Builder().setRole("model").addText("Paris").build());

for (Content turn : turns) {
  session.send(
    turn,
    false // turnComplete: false
  );
}

// Define the new user query.
Content newTurn = new Content.Builder().addText("What is the capital of Germany?").build();

// Send the final query, CLOSING the turn (true) to trigger the model response.
session.send(
  newTurn,
  true // isTurnComplete: true
);
```

### Web

```javascript
const turns = [{ type: "text", text: "Hello from the user!" }];

await session.send(
  turns,
  false // turnComplete: false
);

console.log("Sent history. Waiting for next input...");

// Define the new user query.
const newTurn = [{ type: "text", text: "And what is the capital of Germany?" }];

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.send(
    newTurn,
    true // turnComplete: true
);
console.log("Sent final query. Model response expected now.");
```

### Dart

```dart
// Define initial turns (history/context).
final List turns = [
  Content(
    "user",
    [TextPart("What is the capital of France?")],
  ),
  Content(
    "model",
    [TextPart("Paris")],
  ),
];

// Send history, keeping the conversational turn OPEN (false).
await session.send(
  input: turns,
  turnComplete: false,
);

// Define the new user query.
final List newTurn = [
  Content(
    "user",
    [TextPart("What is the capital of Germany?")],
  ),
];

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.send(
  input: newTurn,
  turnComplete: true,
);
```

### Unity

```c#
// Define initial turns (history/context).
List turns = new List {
    new ModelContent("user", new ModelContent.TextPart("What is the capital of France?") ),
    new ModelContent("model", new ModelContent.TextPart("Paris") ),
};

// Send history, keeping the conversational turn OPEN (false).
foreach (ModelContent turn in turns)
{
    await session.SendAsync(
        content: turn,
        turnComplete: false
    );
}

// Define the new user query.
ModelContent newTurn = ModelContent.Text("What is the capital of Germany?");

// Send the final query, CLOSING the turn (true) to trigger the model response.
await session.SendAsync(
    content: newTurn,
    turnComplete: true
);
```

### Update system instructions mid-session


|---|
| *Only available when using the Agent Platform Gemini API (formerly Vertex AI) as your API provider.* |

<br />

You can update the system instructions during an active session. Use this to
adapt the model's responses, for example to change the response language or
modify the tone.

To update the system instructions mid-session, you can send text content with
the `system` role. The updated system instructions will remain in effect for the
remainder of the session.

### Swift

```swift
await session.sendContent(
  [ModelContent(
    role: "system",
    parts: [TextPart("new system instruction")]
  )],
  turnComplete: false
)
```

### Kotlin

```kotlin
// In a coroutine scope
session.send(
    content = content("system") {
        text("new system instruction")
    },
    turnComplete = false
)
```

### Java

```java
session.send(
    new Content.Builder()
        .setRole("system")
        .addText("new system instruction")
        .build(),
    /* turnComplete: */ false
);
```

### Web

```javascript
Not yet supported for Web apps - check back soon!

```

### Dart

```dart
try {
  await _session.send(
    input: Content(
      'system',
      [TextPart('new system instruction')],
    ),
    turnComplete: false,
  );
} catch (e) {
  print('Failed to update system instructions: $e');
}
```

### Unity

```c#
try
{
    await session.SendAsync(
        content: new ModelContent(
            "system",
            new ModelContent.TextPart("new system instruction")
        ),
        turnComplete: false
    );
}
catch (Exception e)
{
    Debug.LogError($"Failed to update system instructions: {e.Message}");
}
```

## Manually signal user activity

By default, the Live API automatically detects the beginning and end of user
speech using voice activity detection (VAD), which lets you interrupt the model
at any time (learn how to
[handle interruptions](https://firebase.google.com/docs/ai-logic/live-api/configuration.md.txt#vad-handle-interruptions)).

However, you can take complete manual control over conversational turns (such as
for a push-to-talk interface) by disabling automatic
voice activity detection (VAD).

If you disable VAD, the server won't automatically detect user speech or trigger
responses based on audio silence. In this scenario, you must manually
demarcate the boundaries of each user activity turn.

Learn how to
[disable automatic voice activity detection (VAD) and manually signal user activity](https://firebase.google.com/docs/ai-logic/live-api/configuration.md.txt#vad-disable).

## Compress the context window

|---|
| *Click your Gemini API provider to view provider-specific content and code on this page.* <button value="dev" default="">Gemini Developer API</button> <button value="vertex">Agent Platform Gemini API</button> |

The Live API *session context window* stores real-time streamed data
(25 tokens per second (TPS) for audio and 258 TPS for video) as well as other
content, including text inputs and model outputs. All Live API models have a
session context window limit of 128k tokens.

By default, due to this context window limit, here are the approximate maximum
session lengths based on input modalities:

- Audio-only input sessions are limited to 15 minutes.
- Video + audio input are limited to 2 minutes.

In long-running sessions, as the conversation progresses, the history of audio
and/or video tokens accumulates. If this history exceeds the model's limit, the
model may hallucinate, slow down, or the session may be forcibly terminated.

To enable longer sessions, you can enable *context window compression* by
setting the `contextWindowCompression` field as part of the
`LiveGenerationConfig`. When enabled, the server uses a *sliding-window*
mechanism to automatically discard the oldest turns or summarize them to
maintain the context size within the default or specified limits. System
instructions are not discarded and will always stay at the beginning of context
window.

From the user's perspective, this allows for theoretically infinite session
durations since the "memory" is constantly managed.

You can configure the sliding-window mechanism as well as *optionally* the
number of tokens that triggers compression: (see available settings and values
below). Here are some high-level considerations about using these settings:

- Setting `targetTokens` very low will free up more context room for continuous
  streams, but the model will rapidly "forget" older turns of the conversation.

- Setting `targetTokens` closer to `triggerTokens` preserves more memory but
  will trigger compression routines far more frequently.

> [!NOTE]
> **Note:** Discarding parts of the context window causes temporary latency. Set these values so that they don't trigger compression frequently.

| Setting | Default for sliding window if not set in the config | Minimum value | Maximum value |
|---|---|---|---|
| `triggerTokens` the context length before compression is triggered | 80% of the model's context window limit | 5,000 | 128,000 |
| `targetTokens` the target number of tokens to keep | 50% of the `triggerTokens` value - If `triggerTokens` is not *explicitly* set, then `targetTokens` defaults to 50% of the *default* `triggerTokens` value. - The `targetTokens` value must be less than the `triggerTokens` value. | 0 | 128,000 |

### Swift

```swift

// ...

let liveModel = FirebaseAI.firebaseAI(backend: .googleAI()).liveModel(
  modelName: "GEMINI_LIVE_API_MODEL_NAME",
  // Enable context window compression.
  // (Optional) Configure the number of tokens in the context window that triggers the compression.
  generationConfig: LiveGenerationConfig(
    responseModalities: [.audio],
    contextWindowCompression: ContextWindowCompressionConfig(
      triggerTokens: 10000,
      slidingWindow: SlidingWindow(
        targetTokens: 2000,
      )
    )
  )
)

```

### Kotlin

```kotlin

// ...

val liveModel = Firebase.ai(backend = GenerativeBackend.googleAI()).liveModel(
    modelName = "GEMINI_LIVE_API_MODEL_NAME",
    // Enable context window compression.
    // (Optional) Configure the number of tokens in the context window that triggers the compression.
    generationConfig = liveGenerationConfig {
        responseModality = ResponseModality.AUDIO,
        contextWindowCompression = ContextWindowCompressionConfig(
            triggerTokens = 10000,
            slidingWindow = SlidingWindow(targetTokens = 2000)
        )
    }
)

```

### Java

```java

// ...

LiveGenerativeModel lm = FirebaseAI.getInstance(GenerativeBackend.googleAI()).liveModel(
        "GEMINI_LIVE_API_MODEL_NAME",
        // Enable context window compression.
        // (Optional) Configure the number of tokens in the context window that triggers the compression.
        new LiveGenerationConfig.Builder()
                .setResponseModality(ResponseModality.AUDIO)
                .setContextWindowCompression(
                        new ContextWindowCompressionConfig(10000, new SlidingWindow(2000))
                )
                .build()
);

```

### Web

```javascript

// ...

const ai = getAI(firebaseApp, { backend: new GoogleAIBackend() });

const liveModel = getLiveGenerativeModel(ai, {
  model: "GEMINI_LIVE_API_MODEL_NAME",
  // Enable context window compression.
  // (Optional) Configure the number of tokens in the context window that triggers the compression.
  generationConfig: {
    responseModalities: [ResponseModality.AUDIO],
    contextWindowCompression: {
      triggerTokens: 10000,
      slidingWindow: {
        targetTokens: 2000,
      },
    },
  },
});

```

### Dart

```dart

// ...

final _liveModel = FirebaseAI.googleAI().liveGenerativeModel(
  model: 'GEMINI_LIVE_API_MODEL_NAME',
  // Enable context window compression.
  // (Optional) Configure the number of tokens in the context window that triggers the compression.
  liveGenerationConfig: LiveGenerationConfig(
    responseModalities: [ResponseModalities.audio],
    contextWindowCompression: ContextWindowCompressionConfig(
      triggerTokens: 10000,
      slidingWindow: SlidingWindow(targetTokens: 2000),
    ),
  ),
);

```

### Unity

```c#

// ...

var liveModel = FirebaseAI.GetInstance(FirebaseAI.Backend.GoogleAI()).GetLiveModel(
    modelName: "GEMINI_LIVE_API_MODEL_NAME",
    // Enable context window compression.
    // (Optional) Configure the number of tokens in the context window that triggers the compression.
    liveGenerationConfig: new LiveGenerationConfig(
        responseModalities: new[] { ResponseModality.Audio },
        contextWindowCompression: new ContextWindowCompressionConfig(
            triggerTokens: 10000,
            slidingWindow: new SlidingWindow(targetTokens: 2000)
        )
    )
);

```

## Detect when a session is going to end

The maximum duration of a single, continuous WebSocket connection is about
10 minutes. A *going away* notification is sent to the client
60 seconds before the connection ends, which can help you take
further actions (for example, by [resuming a session](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#resume-session)).

The following example shows how to detect an impending connection termination by
listening for a *going away* notification:

### Swift

```swift
for try await response in session.responses {
  switch response.payload {

  case .goingAwayNotice(let goingAwayNotice):
    // Prepare for the session to close soon
    if let timeLeft = goingAwayNotice.timeLeft {
        print("Server going away in \(timeLeft) seconds")
    }
  }
}
```

### Kotlin

```kotlin
for (response in session.responses) {
    when (val message = response.payload) {
        is LiveServerGoAway -> {
            // Prepare for the session to close soon
            val remaining = message.timeLeft
            logger.info("Server going away in $remaining")
        }
    }
}
```

### Java

```java
session.getResponses().forEach(response -> {
    if (response.getPayload() instanceof LiveServerResponse.GoingAwayNotice) {
        LiveServerResponse.GoingAwayNotice notice = (LiveServerResponse.GoingAwayNotice) response.getPayload();
        // Prepare for the session to close soon
        Duration timeLeft = notice.getTimeLeft();
    }
});
```

### Web

```javascript
for await (const message of session.receive()) {
  switch (message.type) {

  ...
  case "goingAwayNotice":
    console.log("Server going away. Time left:", message.timeLeft);
    break;
  }
}
```

### Dart

```dart
Future _handleLiveServerMessage(LiveServerResponse response) async {
  final message = response.message;
  if (message is GoingAwayNotice) {
     // Prepare for the session to close soon
     developer.log('Server going away. Time left: ${message.timeLeft}');
  }
}
```

### Unity

```c#
foreach (var response in session.Responses) {
    if (response.Payload is LiveSessionGoingAway notice) {
        // Prepare for the session to close soon
        TimeSpan timeLeft = notice.TimeLeft;
        Debug.Log($"Server going away notice received. Remaining: {timeLeft}");
    }
}
```

## Resume a session

The Live API supports session resumption to prevent losing conversation
context. Every session has a handle, and it can be used in the following ways:

- **Maintaining a session before reaching the connection time limit**

  The maximum duration of a single, continuous WebSocket connection is about
  10 minutes. You can detect when a connection is about to end by
  [listening for a *going away* notification](https://firebase.google.com/docs/ai-logic/live-api/sessions.md.txt#going-away-notification), and
  then extending the session by establishing a new connection using the session
  handle.
- **Resuming a session just after a connection drop**

  If a connection terminates or drops before the maximum connection time limit
  (for example, switching from WiFi to 5G), the server keeps the session state
  for about 10 minutes. During this window, you can resume the
  session by establishing a new connection using the session handle.
- **Resuming a session after an extended time period**

  After a connection ends, the server keeps the session state for a few hours.
  During this window, you can resume the session by establishing a new
  connection using the session handle. Note that this window is different for
  the two Gemini API providers:
  Gemini Developer API is 2 hours \|
  Agent Platform Gemini API (formerly Vertex AI) is 24 hours.

By default, session resumption is disabled. To enable session resumption, pass
an empty resumption configuration when establishing a new connection. When
enabled, the server periodically sends updates containing a session resumption
handle. If the session is disconnected, you can reconnect and pass this handle
to resume the session with its context intact.

The following examples show two options for resuming the session:

### Swift

```swift
// Local variable to save the active session handle
var activeSessionHandle: String?

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var session = try await liveModel.connect(
  sessionResumption: SessionResumptionConfig()
)

// Start receiving responses
for try await message in session.responses {
  // Check for new session handles inside your message handling loop
  switch message.payload {
  case let .sessionResumptionUpdate(updateMessage):
    guard let newHandle = updateMessage.newHandle, updateMessage.resumable else {
      continue
    }
    activeSessionHandle = newHandle
    print("SessionResumptionUpdate: handle \(newHandle)")
  // ... handle other LiveServerMessage types ...
  default:
    break
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if let handle = activeSessionHandle {
  session = try await liveModel.connect(
    sessionResumption: SessionResumptionConfig(handle: handle)
  )
}

// Option 2: Resume the session directly on an existing session object
if let handle = activeSessionHandle {
  try await session.resumeSession(
    sessionResumption: SessionResumptionConfig(handle: handle)
  )
}
```

### Kotlin

```kotlin
// Local variable to save the active session handle
var activeSessionHandle: String? = null

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var session = liveModel.connect(
    sessionResumption = SessionResumptionConfig()
)

// Start receiving responses
session.receive().collect { message ->
    // Process other received response types...

    // Check for new session handles inside your message handling loop
    if (message is LiveSessionResumptionUpdate) {
        if (message.resumable == true && message.newHandle != null) {
            activeSessionHandle = message.newHandle
            Log.d("TAG", "SessionResumptionUpdate: handle ${message.newHandle}")
        }
    }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
activeSessionHandle?.let { handle ->
    session = liveModel.connect(
        sessionResumption = SessionResumptionConfig(handle = handle)
    )
}

// Option 2: Resume the session directly on an existing session object
activeSessionHandle?.let { handle ->
    session.resumeSession(
        sessionResumption = SessionResumptionConfig(handle = handle)
    )
}
```

### Java

```java
For Java, session resumption is **not yet** supported. Check back soon!
```

### Web

```javascript
// Local variable to save the active session handle
let activeSessionHandle = null;

// Initialize the session. Passing an empty object requests the server to send SessionResumptionUpdate
let session = await liveModel.connect({});

// Start receiving responses
for await (const message of session.receive()) {
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  if (message.type === 'sessionResumptionUpdate') {
    if (message.resumable && message.newHandle) {
      activeSessionHandle = message.newHandle;
      console.log(`SessionResumptionUpdate: handle ${activeSessionHandle}`);
    }
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if (activeSessionHandle) {
  session = await liveModel.connect({
    handle: activeSessionHandle
  });
}

// Option 2: Resume the session directly on an existing session object
if (activeSessionHandle) {
  await session.resumeSession({
    handle: activeSessionHandle
  });
}
```

### Dart

```dart
// Local variable to save the active session handle
String? _activeSessionHandle;

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var _session = await _liveModel.connect(
  sessionResumption: SessionResumptionConfig(),
);

// Start receiving responses
await for (final response in _session.receive()) {
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  final message = response.message;
  if (message is SessionResumptionUpdate &&
      message.resumable != null &&
      message.resumable!) {
    _activeSessionHandle = message.newHandle;
    log('SessionResumptionUpdate: handle ${message.newHandle}');
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if (_activeSessionHandle != null) {
  _session = await _liveModel.connect(
    sessionResumption: SessionResumptionConfig.resume(_activeSessionHandle!),
  );
}

// Option 2: Alternatively, resume the session directly on an existing session object
if (_activeSessionHandle != null) {
  await _session.resumeSession(
    sessionResumption: SessionResumptionConfig.resume(_activeSessionHandle!),
  );
}
```

### Unity

```c#
// Local variable to save the active session handle
string activeSessionHandle = null;

// Initialize the session. Passing an empty config requests the server to send SessionResumptionUpdate
var session = await liveModel.ConnectAsync(
    sessionResumption: new SessionResumptionConfig()
);

// Start receiving responses
await foreach (var response in session.ReceiveAsync())
{
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  if (response.Message is LiveSessionResumptionUpdate updateMessage)
  {
    if (updateMessage.Resumable == true && !string.IsNullOrEmpty(updateMessage.NewHandle))
    {
      activeSessionHandle = updateMessage.NewHandle;
      Debug.Log($"SessionResumptionUpdate: handle {activeSessionHandle}");
    }
  }
}

// The following are alternative options to resume a session. Choose only one.

// Option 1: Create and connect a session to resume with the saved handle
if (!string.IsNullOrEmpty(activeSessionHandle)) {
  session = await liveModel.ConnectAsync(
      sessionResumption: new SessionResumptionConfig(activeSessionHandle)
  );
}

// Option 2: Resume the session directly on an existing session object
if (!string.IsNullOrEmpty(activeSessionHandle)) {
  await session.ResumeSessionAsync(
      sessionResumption: new SessionResumptionConfig(activeSessionHandle)
  );
}
```