Live API 세션 관리

Gemini Live API세션이라고 하는 오디오 또는 텍스트의 연속 스트림을 처리합니다. 초기 핸드셰이크부터 단계적 종료까지 세션 수명 주기를 관리할 수 있습니다.

세션 제한

Live API의 경우 세션은 연결을 통해 입력과 출력이 지속적으로 스트리밍되는 영구 연결을 의미합니다.

세션이 다음 한도 중 하나라도 초과하면 연결이 종료됩니다. 하지만 Live API에서는 이러한 세션 관련 한도를 처리할 수 있는 몇 가지 옵션을 제공합니다 (아래 참고).

  • 세션 컨텍스트 윈도우는 토큰 128,000개로 제한됩니다.

    이 컨텍스트 윈도우 제한으로 인해 입력 모달리티에 따른 대략적인 최대 세션 길이는 다음과 같습니다.

    • 오디오 전용 입력 세션은 15분으로 제한됩니다.
    • 동영상 + 오디오 입력은 2분으로 제한됩니다.
  • 연결 길이는 약 10분으로 제한됩니다.

    연결이 종료되기 60초 전에 종료 알림이 전송됩니다.

세션 관련 제한을 처리하는 몇 가지 옵션은 다음과 같습니다.

  • 서버가 한도 내에서 컨텍스트 크기를 자동으로 유지하도록 세션 컨텍스트 윈도우를 압축합니다.

  • 세션을 재개하여 일시적인 네트워크 연결 해제 중이나 외출 알림을 받은 후 대화 컨텍스트가 손실되지 않도록 합니다.

세션 시작

세션을 시작하는 방법을 보여주는 전체 스니펫은 Live API 시작 가이드를 참고하세요.

세션 중 업데이트

Live API 모델은 세션 중 업데이트를 위해 다음과 같은 고급 기능을 지원합니다.

증분 콘텐츠 업데이트 추가

활성 세션 중에 증분 업데이트를 추가할 수 있습니다. 텍스트 입력을 전송하거나, 세션 컨텍스트를 설정하거나, 세션 컨텍스트를 복원하는 데 사용합니다.

  • 컨텍스트가 긴 경우 후속 상호작용을 위해 컨텍스트 윈도우를 확보할 수 있도록 단일 메시지 요약을 제공하는 것이 좋습니다.

  • 짧은 컨텍스트의 경우 아래 스니펫과 같이 정확한 이벤트 순서를 나타내기 위해 세부 경로 안내 상호작용을 보낼 수 있습니다.

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

Not yet supported for Android apps - check back soon!

Java

Not yet supported for Android apps - check back soon!

Web

const turns = [{ 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 [{ 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

// Define initial turns (history/context).
final List turns = [
  Content(
    "user",
    [Part.text("What is the capital of France?")],
  ),
  Content(
    "model",
    [Part.text("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",
    [Part.text("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

// 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
);

세션 중에 시스템 요청 사항 업데이트

Vertex AI Gemini API를 API 제공업체로 사용하는 경우에만 사용할 수 있습니다.

활성 세션 중에 시스템 안내를 업데이트할 수 있습니다. 이를 사용하여 모델의 대답을 조정합니다(예: 대답 언어 변경 또는 어조 수정).

세션 중에 시스템 요청 사항을 업데이트하려면 system 역할로 텍스트 콘텐츠를 전송하면 됩니다. 업데이트된 시스템 요청 사항이 남은 세션 동안 적용됩니다.

Swift

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

Kotlin

Not yet supported for Android apps - check back soon!

Java

Not yet supported for Android apps - check back soon!

Web

Not yet supported for Web apps - check back soon!

Dart

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

Unity

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}");
}

컨텍스트 윈도우 압축

Gemini API 제공업체를 클릭하여 이 페이지에서 제공업체별 콘텐츠와 코드를 확인합니다.

Live API 세션 컨텍스트 윈도우는 실시간 스트리밍 데이터(오디오의 경우 초당 토큰 (TPS) 25개, 동영상의 경우 TPS 258개)와 텍스트 입력, 모델 출력 등 기타 콘텐츠를 저장합니다. 모든 Live API 모델의 세션 컨텍스트 윈도우 한도는 128,000개 토큰입니다.

기본적으로 이 컨텍스트 윈도우 제한으로 인해 입력 모달리티에 따른 대략적인 최대 세션 길이는 다음과 같습니다.

  • 오디오 전용 입력 세션은 15분으로 제한됩니다.
  • 동영상 + 오디오 입력은 2분으로 제한됩니다.

장기 실행 세션에서는 대화가 진행됨에 따라 오디오 또는 동영상 토큰의 기록이 누적됩니다. 이 기록이 모델의 한도를 초과하면 모델이 할루시네이션을 일으키거나 속도가 느려지거나 세션이 강제로 종료될 수 있습니다.

더 긴 세션을 사용 설정하려면 LiveGenerationConfig의 일부로 contextWindowCompression 필드를 설정하여 컨텍스트 윈도우 압축을 사용 설정하면 됩니다. 사용 설정하면 서버는 슬라이딩 윈도우 메커니즘을 사용하여 가장 오래된 턴을 자동으로 삭제하거나 요약하여 기본 또는 지정된 한도 내에서 컨텍스트 크기를 유지합니다. 시스템 안내는 삭제되지 않으며 항상 컨텍스트 창의 시작 부분에 유지됩니다.

사용자 관점에서 보면 '메모리'가 지속적으로 관리되므로 이론적으로 무한한 세션 기간이 가능합니다.

슬라이딩 윈도우 메커니즘과 압축을 트리거하는 토큰 수를 선택적으로 구성할 수 있습니다(아래에서 사용 가능한 설정 및 값 참고). 다음은 이러한 설정을 사용할 때 고려해야 할 몇 가지 사항입니다.

  • targetTokens을 매우 낮게 설정하면 연속 스트림을 위한 컨텍스트 여유 공간이 늘어나지만 모델이 이전 대화 턴을 빠르게 '잊어버립니다'.

  • targetTokenstriggerTokens에 더 가깝게 설정하면 메모리가 더 많이 보존되지만 압축 루틴이 훨씬 더 자주 트리거됩니다.

설정 구성에서 설정되지 않은 경우 슬라이딩 윈도우의 기본값 최솟값 최댓값
triggerTokens
압축이 트리거되기 전의 컨텍스트 길이
모델의 컨텍스트 윈도우 한도의 80% 5,000 128,000
targetTokens
유지할 타겟 토큰 수
triggerTokens 값의 50%
  • triggerTokens명시적으로 설정되지 않은 경우 targetTokens기본 triggerTokens 값의 50% 로 기본 설정됩니다.
  • targetTokens 값은 triggerTokens 값보다 작아야 합니다.
0 128,000

Swift


// Initialize the Gemini Developer API backend service
let liveModel = FirebaseAI.firebaseAI(backend: .googleAI()).liveModel(
  modelName: "gemini-2.5-flash-native-audio-preview-12-2025",
  // 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


// Initialize the Gemini Developer API backend service
val liveModel = Firebase.ai(backend = GenerativeBackend.googleAI()).liveModel(
    modelName = "gemini-2.5-flash-native-audio-preview-12-2025",
    // 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


// Initialize the Gemini Developer API backend service
LiveGenerativeModel lm = FirebaseAI.getInstance(GenerativeBackend.googleAI()).liveModel(
        "gemini-2.5-flash-native-audio-preview-12-2025",
        // 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


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

const liveModel = getLiveGenerativeModel(ai, {
  model: "gemini-2.5-flash-native-audio-preview-12-2025",
  // 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


final _liveModel = FirebaseAI.googleAI().liveGenerativeModel(
  model: 'gemini-2.5-flash-native-audio-preview-12-2025',
  // 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


var liveModel = FirebaseAI.GetInstance(FirebaseAI.Backend.GoogleAI()).GetLiveModel(
    modelName: "gemini-2.5-flash-native-audio-preview-12-2025",
    // 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)
        )
    )
);

세션이 종료되는 시점 감지

단일 연속 WebSocket 연결의 최대 지속 시간은 약 10분입니다. 연결이 종료되기 60초 전에 클라이언트에 종료 알림이 전송되므로 세션을 재개하는 등 추가 조치를 취할 수 있습니다.

다음 예시에서는 going away 알림을 리슨하여 임박한 연결 종료를 감지하는 방법을 보여줍니다.

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

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

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

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

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

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

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}");
    }
}

세션 재개

Live API는 대화 컨텍스트가 손실되지 않도록 세션 재개를 지원합니다. 모든 세션에는 핸들이 있으며 다음과 같은 방식으로 사용할 수 있습니다.

  • 연결 시간 제한에 도달하기 전에 세션 유지

    단일 연속 WebSocket 연결의 최대 지속 시간은 약 10분입니다. going away 알림을 수신 대기하여 연결이 종료되려고 할 때를 감지한 다음 세션 핸들을 사용하여 새 연결을 설정하여 세션을 연장할 수 있습니다.

  • 연결이 끊긴 직후 세션 재개

    최대 연결 시간 제한 전에 연결이 종료되거나 끊어지면(예: Wi-Fi에서 5G로 전환) 서버는 약 10분 동안 세션 상태를 유지합니다. 이 기간 동안 세션 핸들을 사용하여 새 연결을 설정하여 세션을 재개할 수 있습니다.

  • 장시간이 지난 후 세션 재개

    연결이 종료된 후 서버는 몇 시간 동안 세션 상태를 유지합니다. 이 기간 동안 세션 핸들을 사용하여 새 연결을 설정하여 세션을 재개할 수 있습니다. 이 기간은 두 Gemini API 제공업체에 따라 다릅니다. Gemini Developer API2시간, Vertex AI Gemini API24시간입니다.

기본적으로 세션 재개는 중지되어 있습니다. 세션 재개를 사용 설정하려면 새 연결을 설정할 때 빈 재개 구성을 전달하세요. 사용 설정된 경우 서버는 세션 재개 핸들이 포함된 업데이트를 주기적으로 전송합니다. 세션 연결이 끊어진 경우 다시 연결하고 이 핸들을 전달하여 컨텍스트가 그대로 유지된 상태로 세션을 재개할 수 있습니다.

다음 예에서는 세션을 재개하는 두 가지 옵션을 보여줍니다.

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

// 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

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

Web

// 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

// 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 message in _session.receive()) {
  // Process other received response types...

  // Check for new session handles inside your message handling loop
  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

// 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)
  );
}