Gemini Live API 음성 챗봇 구현 — 3.1 Flash Live 오디오 스트리밍 실전 가이드

이 글에서는 Gemini Live API 음성 챗봇 구현 방법도 다룹니다.

Gemini Live API 음성 챗봇 구현 — Gemini Live API 음성 챗봇 구현 — 3.1 Flash Live 오디오 스트리밍 실전 가이드

목차

Gemini Live API 음성 챗봇 구현은 일반적인 REST 기반 LLM 호출과 구조가 다르다. WebSocket 세션 위에서 raw PCM 오디오를 직접 스트리밍하는 방식이기 때문에, HTTP 요청-응답 패턴에 익숙하다면 처음 접근할 때 혼란스러울 수 있다. 이 글은 gemini-3.1-flash-live-preview 모델에 Python google-genai SDK로 연결하고, 오디오를 송수신하며, VAD 설정과 Function Calling까지 연동하는 전체 흐름을 정리한다.

Gemini 3.1 Flash Live 모델 스펙

모델 ID는 gemini-3.1-flash-live-preview다. 공식 모델 페이지에 명시된 스펙은 다음과 같다 (2026-04 기준).

항목
모델 IDgemini-3.1-flash-live-preview
입력 모달리티텍스트, 이미지, 오디오, 비디오
출력 모달리티텍스트, 오디오
입력 토큰 한도131,072
출력 토큰 한도65,536
지식 기준일2025년 1월
최종 업데이트2026년 3월

지원 기능과 미지원 기능의 경계가 뚜렷하다. Function calling, Search grounding, Thinking은 지원된다. Batch API, Caching, Structured outputs는 미지원이다. 함수 호출은 동기(synchronous)만 가능하고 비동기 함수 호출(async function calling)은 아직 지원하지 않는다.

Live API 전용 모델
`gemini-3.1-flash-live-preview`는 Live API에서만 작동한다. 일반 REST 엔드포인트(`generateContent`)로는 호출할 수 없으므로 반드시 WebSocket 세션을 통해 접근해야 한다.
`thinkingLevel` 설정도 제공된다. `minimal`, `low`, `medium`, `high` 네 단계가 있고, 기본값은 `minimal`이다. 실시간 대화에서 지연을 최소화하기 위한 설정인데, 복잡한 추론이 필요하면 `medium` 이상으로 올릴 수 있다. 레벨이 올라갈수록 첫 응답까지의 지연이 커지므로, Gemini Live API 음성 챗봇처럼 빠른 응답이 중요한 경우에는 기본값을 유지하는 쪽이 낫다.

Live API의 WebSocket 구조

Live API는 Stateful WebSocket(WSS) 프로토콜을 사용한다. 한번 연결을 열면 그 세션 안에서 양방향으로 오디오와 메시지를 계속 주고받는 구조다. HTTP처럼 매번 새 연결을 여는 게 아니다.

sequenceDiagram
    participant Client as Python Client
    participant WS as WebSocket (WSS)
    participant Gemini as Gemini 3.1 Flash Live

    Client->>WS: connect(model, config)
    WS->>Gemini: 세션 생성
    Gemini-->>WS: 세션 확인
    WS-->>Client: Session started

    loop 실시간 대화
        Client->>WS: send_realtime_input(audio PCM)
        WS->>Gemini: 오디오 스트림 전달
        Gemini-->>WS: 오디오 응답 스트림
        WS-->>Client: receive() → inline_data
    end

서버-서버 vs 클라이언트-서버

Live API 공식 문서에 따르면, 연결 방식은 두 가지다.

  • 서버-서버(백엔드 직접 연결): Python 등 백엔드에서 API 키로 바로 연결한다. 개발·테스트에 적합하다.
  • 클라이언트-서버(프론트엔드 직접 연결): 브라우저나 모바일 앱에서 직접 연결한다. API 키를 클라이언트에 노출하면 안 되므로 ephemeral token 사용이 권장된다.

프로덕션 환경에서는 표준 API 키 대신 ephemeral token을 발급해서 클라이언트에 전달하는 방식이 맞다. 이 글에서 다루는 코드는 서버-서버 방식 기준이다. Python 백엔드에서 직접 API 키를 사용해 연결하므로 인증 흐름이 단순하다.

Python SDK로 세션 연결하기

google-genai SDK의 client.aio.live.connect()로 비동기 WebSocket 세션을 연다. 설치부터 연결까지 순서대로 보자.

pip install google-genai

SDK 공식 가이드에 나온 기본 연결 코드다:

import asyncio
from google import genai

client = genai.Client(api_key="YOUR_API_KEY")
model = "gemini-3.1-flash-live-preview"
config = {"response_modalities": ["AUDIO"]}

async def main():
    async with client.aio.live.connect(model=model, config=config) as session:
        print("Session started")

if __name__ == "__main__":
    asyncio.run(main())

핵심은 response_modalities["AUDIO"]로 설정하는 부분이다. 이걸 빼면 텍스트만 반환되고, 오디오 응답을 받을 수 없다. async with 블록을 벗어나면 세션이 자동으로 닫힌다.

API 키 노출 주의
위 코드는 로컬 개발·테스트용이다. 프로덕션에서는 API 키를 코드에 직접 넣지 말고 환경 변수나 ephemeral token 방식을 사용해야 한다. 클라이언트 사이드에서 API 키를 직접 쓰면 키가 유출될 수 있다.
`config` 딕셔너리에 넣을 수 있는 추가 옵션으로는 `thinkingLevel`, VAD 설정, 그리고 `tools`(Function Calling용)가 있다. 기본 연결만 확인하려면 위 코드면 충분하다.

오디오 송수신 — PCM 포맷 이해가 먼저다

오디오 포맷 스펙

연결이 됐으면 오디오를 보내고 받아야 한다. 포맷을 잘못 맞추면 노이즈만 돌아오거나 아예 응답이 없다. Live API capabilities 문서에 명시된 오디오 포맷이다:

방향포맷비트엔디안샘플레이트
입력raw PCM16-bitlittle-endian16 kHz
출력raw PCM16-bit24 kHz

입력 쪽에서 16 kHz가 아닌 다른 샘플레이트를 보내면 서버가 자동 리샘플링을 해준다. 가능하면 16 kHz로 맞춰서 보내는 게 지연을 줄이는 데 유리하다. 출력은 24 kHz로 고정이므로, 수신 측에서 재생할 때 24 kHz에 맞춰야 정상적인 음성이 나온다.

오디오 전송과 수신 코드

오디오 전송은 session.send_realtime_input()을 사용한다. types.Blob에 PCM 바이너리 데이터와 MIME 타입을 지정해서 보낸다. 수신은 session.receive()async for로 반복하면서 part.inline_data.data에서 오디오 바이너리를 꺼낸다.

import asyncio
from google import genai
from google.genai import types

client = genai.Client(api_key="YOUR_API_KEY")
model = "gemini-3.1-flash-live-preview"
config = {"response_modalities": ["AUDIO"]}

async def audio_chat(audio_chunk: bytes):
    async with client.aio.live.connect(model=model, config=config) as session:
        # 오디오 전송
        await session.send_realtime_input(
            audio=types.Blob(
                data=audio_chunk,
                mime_type="audio/pcm;rate=16000"
            )
        )

        # 오디오 수신
        async for response in session.receive():
            if response.server_content and response.server_content.model_turn:
                for part in response.server_content.model_turn.parts:
                    if part.inline_data:
                        audio_data = part.inline_data.data
                        # audio_data: 24 kHz PCM 바이너리

audio_chunk는 마이크에서 캡처한 raw PCM 바이너리다. mime_typerate=16000 부분이 샘플레이트를 명시하는데, 실제 오디오 데이터의 샘플레이트와 반드시 일치해야 한다. 수신된 audio_data는 24 kHz PCM이므로 스피커로 재생하려면 해당 레이트에 맞는 오디오 출력 스트림을 열어야 한다.

마이크·스피커 연동
공식 문서에는 마이크 입력/스피커 출력을 연결하는 end-to-end 예제가 충분히 포함되어 있지 않다. `pyaudio`나 `sounddevice` 같은 라이브러리로 16 kHz 입력 스트림과 24 kHz 출력 스트림을 각각 열어서 위 코드와 연결하면 된다. 구체적인 구현은 사용하는 오디오 라이브러리의 문서를 참조하라.
### 세션 시간 제한

오디오 전용 세션은 최대 15분, 오디오+비디오 세션은 최대 2분으로 제한된다. 15분이 지나면 세션이 끊기므로 장시간 대화가 필요한 Gemini Live API 음성 챗봇이라면 세션 재연결(session resumption) 로직이 필수다. 세션 재연결 패턴에 대한 상세 가이드는 공식 문서에서 별도 페이지에 분산되어 있어 한곳에서 정리된 자료를 찾기 어렵다.

VAD 설정 — 발화 감지 튜닝

음성 챗봇의 대화 품질을 결정하는 핵심 요소 중 하나가 VAD(Voice Activity Detection)다. 사용자가 말하기 시작한 시점, 말을 멈춘 시점을 얼마나 정확하게 감지하느냐에 따라 대화의 자연스러움이 달라진다.

자동 모드

capabilities 문서에 따르면, VAD는 자동 모드와 수동 모드 두 가지를 지원한다. 자동 모드에서 설정 가능한 파라미터는 네 가지다:

from google.genai.types import StartSensitivity, EndSensitivity

vad_config = {
    "automatic_activity_detection": {
        "disabled": False,
        "start_of_speech_sensitivity": StartSensitivity.START_SENSITIVITY_LOW,
        "end_of_speech_sensitivity": EndSensitivity.END_SENSITIVITY_LOW,
        "prefix_padding_ms": 20,
        "silence_duration_ms": 100
    }
}

각 파라미터의 역할:

  • start_of_speech_sensitivity: 발화 시작 감지 민감도. LOW로 설정하면 짧은 소음에 반응하지 않는다. 시끄러운 환경이면 LOW가 맞다.
  • end_of_speech_sensitivity: 발화 종료 감지 민감도. LOW면 짧은 침묵을 발화 종료로 판단하지 않아서, 사용자가 잠깐 쉬는 동안 모델이 끼어들지 않는다.
  • prefix_padding_ms: 발화 시작 전 얼마만큼의 오디오를 포함할지 설정한다. 값이 클수록 발화 시작 전의 오디오가 더 많이 포함되어, 첫 음절이 잘리는 문제를 방지할 수 있다.
  • silence_duration_ms: 발화 종료로 판단하기까지의 침묵 시간(밀리초). 기본값보다 크게 잡으면 사용자가 생각하며 잠깐 멈추는 경우에도 모델이 기다린다.

이 네 파라미터를 앞서 본 config 딕셔너리에 합쳐서 client.aio.live.connect()config로 넘기면 된다.

수동 모드

자동 VAD를 끄고 수동으로 전환하려면 disabledTrue로 설정한다:

manual_vad_config = {
    "automatic_activity_detection": {
        "disabled": True
    }
}

수동 모드에서는 클라이언트가 직접 발화 시작/종료 시점을 서버에 알려야 한다. 자체 VAD 엔진을 쓰거나, 버튼 기반 push-to-talk UI를 구현할 때 적합하다. 자동 모드보다 구현 복잡도가 높지만, 발화 감지를 완전히 제어할 수 있다는 장점이 있다.

Barge-in 처리

Barge-in은 모델이 응답 중일 때 사용자가 말을 끼어드는 상황을 뜻한다. 자동 VAD가 활성화된 상태에서 사용자 발화가 감지되면, 모델은 현재 진행 중인 오디오 응답을 중단하고 새 입력을 처리한다. 별도 설정 없이 자동 VAD에 내장된 동작이다.

수동 모드에서는 barge-in을 직접 구현해야 한다. 사용자 발화를 감지한 시점에 현재 재생 중인 응답 오디오를 중단하고, 새 오디오 입력을 서버로 보내는 로직이 필요하다.

Function Calling 연동

Gemini Live API 음성 챗봇에 외부 기능을 붙이려면 Function Calling을 연동해야 한다. 사용자가 “서울 날씨 알려줘”라고 말하면 모델이 날씨 API를 호출하도록 도구를 선언하는 식이다.

도구 선언과 호출 흐름

도구는 세션 연결 시 configtools 필드에 선언한다. 모델이 도구 호출이 필요하다고 판단하면, 응답에 tool_call 이벤트가 포함된다. 핵심은 tool_call.function_calls가 리스트라는 점이다. 단일 호출이 아닌 여러 함수 호출이 한 번에 올 수 있으므로 반복문으로 순회해야 한다.

import asyncio
from google import genai
from google.genai import types

client = genai.Client(api_key="YOUR_API_KEY")
model = "gemini-3.1-flash-live-preview"

weather_tool = {
    "function_declarations": [{
        "name": "get_weather",
        "description": "Get current weather for a given city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name"
                }
            },
            "required": ["city"]
        }
    }]
}

config = {
    "response_modalities": ["AUDIO"],
    "tools": [weather_tool]
}

async def live_with_tools(audio_chunk: bytes):
    async with client.aio.live.connect(model=model, config=config) as session:
        await session.send_realtime_input(
            audio=types.Blob(
                data=audio_chunk,
                mime_type="audio/pcm;rate=16000"
            )
        )

        async for response in session.receive():
            # Function Call 응답 처리
            if response.tool_call:
                for fc in response.tool_call.function_calls:
                    if fc.name == "get_weather":
                        city = fc.args.get("city", "")
                        # 실제 날씨 API 호출 후 결과를 돌려준다
                        result = {"temperature": "18°C", "condition": "맑음"}
                        await session.send_tool_response(
                            function_responses=[
                                types.FunctionResponse(
                                    name=fc.name,
                                    id=fc.id,
                                    response=result
                                )
                            ]
                        )

            # 오디오 응답 처리
            if response.server_content and response.server_content.model_turn:
                for part in response.server_content.model_turn.parts:
                    if part.inline_data:
                        audio_data = part.inline_data.data
                        # 24 kHz PCM 오디오 재생 처리
function_calls는 리스트다
`response.tool_call`에서 `name`이나 `id`에 바로 접근하면 동작하지 않는다. `response.tool_call.function_calls` 리스트를 순회하면서 각 `fc.name`, `fc.id`, `fc.args`를 꺼내야 한다. 모델이 한 턴에 여러 함수를 호출할 수 있기 때문이다.
`send_tool_response()`로 결과를 돌려보내면, 모델은 그 결과를 반영한 오디오 응답을 생성한다. 위 예제에서는 날씨 결과를 하드코딩했지만, 실제로는 외부 API를 호출한 결과를 넣으면 된다.

Google Search Grounding

Function Calling 외에 Google Search를 도구로 연결할 수도 있다. tools 배열에 google_search 타입을 추가하면 모델이 필요할 때 자동으로 검색을 수행한다:

config_with_search = {
    "response_modalities": ["AUDIO"],
    "tools": [{"google_search": {}}]
}

별도의 함수 선언 없이 빈 딕셔너리로 지정하면 된다. 모델이 최신 정보가 필요하다고 판단할 때 자동으로 Google 검색을 실행하고, 그 결과를 반영한 응답을 생성한다. Function Calling과 Google Search를 동시에 tools에 넣어서 함께 사용할 수 있다.

전체 연결 구조 정리

지금까지 다룬 요소를 한 흐름으로 연결하면 다음 구조가 된다:

graph TD
    A[마이크 입력
16 kHz PCM] -->|send_realtime_input| B[WebSocket 세션] B -->|오디오 스트림| C[Gemini 3.1 Flash Live] C -->|server_content| D{응답 타입} D -->|inline_data| E[오디오 응답
24 kHz PCM] D -->|tool_call| F[Function Calling] F -->|send_tool_response| B E --> G[스피커 출력] H[VAD 자동 감지] -.->|발화 구간 판별| B I[Session Config] -.->|thinkingLevel, tools| C

구성 요소별 역할 정리:

구성 요소역할핵심 설정
client.aio.live.connect()WebSocket 세션 생성model, config
send_realtime_input()오디오 전송types.Blob, MIME 타입
session.receive()응답 수신 (async generator)server_content, tool_call 분기
VAD발화 시작/종료 감지start_of_speech_sensitivity
Function Calling외부 도구 연동tools, send_tool_response()

세션이 열리면 오디오 전송과 수신이 동시에 이뤄진다. 실제 구현에서는 asyncio.create_task()로 송신 루프와 수신 루프를 분리해서 동시에 돌리는 패턴이 일반적이다.

프로덕션 체크리스트

인증 방식

개발 단계에서는 API 키를 직접 사용해도 무방하다. 프로덕션에서는 두 가지 선택지가 있다:

  • 서버 사이드: 환경 변수(GOOGLE_API_KEY)로 API 키를 관리하고, 코드에 하드코딩하지 않는다.
  • 클라이언트 사이드: 서버에서 ephemeral token을 발급하고, 클라이언트는 그 토큰으로 직접 WebSocket에 연결한다. 토큰은 단기간만 유효하므로 키 유출 위험이 줄어든다.

Vertex AI 환경에서는 서비스 계정 기반 인증도 가능하다. genai.Client()에 API 키 대신 Vertex AI 프로젝트 설정을 넘기는 방식인데, 기업 환경에서 IAM 기반 접근 제어가 필요할 때 적합하다.

세션 재연결

15분 세션 제한은 Gemini Live API 음성 챗봇 운영에서 반드시 대비해야 할 사항이다. 세션이 끊기기 전에 새 세션을 열고 컨텍스트를 이어가는 로직이 필요하다. 공식 문서에서 session resumption에 대한 가이드가 별도 페이지에 분산되어 있어, 한곳에서 전체 흐름을 파악하기는 쉽지 않다.

기본 전략은 다음과 같다:

  • 세션 시작 후 경과 시간을 추적한다
  • 제한 시간에 가까워지면 (예: 13분) 새 세션을 미리 열어둔다
  • 기존 세션의 대화 히스토리를 새 세션의 시스템 프롬프트나 컨텍스트에 요약해서 넘긴다
  • 기존 세션을 닫고 새 세션으로 전환한다
세션 재연결 공식 가이드 부재
2026-04 기준, 세션 재연결의 전체 흐름을 하나로 정리한 공식 가이드는 찾기 어렵다. capabilities 문서와 SDK 레퍼런스를 교차 참조해서 구현해야 하는 상황이다. [Live API 공식 문서](https://ai.google.dev/gemini-api/docs/live-api)에서 관련 섹션을 확인하라.
### 한국어 지원 상태

Gemini 3.1 Flash Live 모델은 다국어를 지원하며, 한국어 음성 입출력도 가능하다. 시스템 프롬프트에서 응답 언어를 지정할 수 있다:

config = {
    "response_modalities": ["AUDIO"],
    "system_instruction": "한국어로 응답하세요."
}

system_instruction 필드에 언어 지시를 넣으면 모델이 해당 언어로 음성 응답을 생성한다. 영어 대비 한국어 음성의 자연스러움이나 지연 차이에 대한 공식 벤치마크는 현재 공개되어 있지 않다.

오디오 재생 구현

수신한 24 kHz PCM 데이터를 실제로 재생하려면 오디오 출력 라이브러리가 필요하다. Python 생태계에서는 pyaudio, sounddevice, wave 모듈 등을 사용할 수 있다.

구현 시 주의할 점:

  • 출력 스트림의 샘플레이트를 24000 Hz로 설정해야 한다. 16000 Hz로 열면 음성이 느리게 재생된다.
  • 채널은 **모노(1채널)**다.
  • PCM 데이터가 청크 단위로 수신되므로, 버퍼링 없이 바로 재생하면 끊김이 발생할 수 있다. 적절한 버퍼 크기를 설정하거나, 수신 큐를 두고 별도 스레드에서 재생하는 방식이 안정적이다.
오디오 라이브러리 선택
`sounddevice`는 NumPy 배열 기반으로 간결한 API를 제공하고, `pyaudio`는 PortAudio 바인딩으로 저수준 제어가 가능하다. 프로젝트 요구사항에 맞게 선택하면 되며, 두 라이브러리 모두 위 코드의 PCM 바이너리와 호환된다.
## Gemini Live API 음성 챗봇, 다음 단계

Gemini Live API의 WebSocket 세션 연결, PCM 오디오 송수신, VAD 튜닝, Function Calling 연동까지 전체 흐름을 정리했다. 핵심은 세 가지다: 오디오 포맷(입력 16 kHz, 출력 24 kHz)을 정확히 맞출 것, tool_call.function_calls 리스트를 순회할 것, 15분 세션 제한에 대비한 재연결 로직을 넣을 것.

여기까지 기본 구조가 잡혔으면 다음 단계로 넘어갈 만한 주제가 몇 가지 있다. Vertex AI 환경에서 서비스 계정 인증과 VPC 내부 배포를 연결하면 기업 환경에 맞는 보안 구성이 가능하다. 오디오 품질 개선이 목표라면 입력 단에서 노이즈 제거(noise suppression)를 적용하는 전처리 파이프라인을 추가하는 것도 고려할 만하다. 그리고 Gemini의 멀티모달 입력을 활용해서 오디오와 함께 화면 공유나 이미지를 실시간으로 전송하는 비디오 연동도 Live API가 지원하는 기능이다. 다만 비디오 세션은 2분 제한이 있으므로 용도에 맞는 설계가 필요하다.

관련 글