실시간 음성 에이전트

실시간 에이전트는 양방향 오디오 스트리밍을 사용하여 AI 어시스턴트와 음성 기반으로 상호작용할 수 있도록 합니다. adk-realtime 크레이트는 OpenAI의 Realtime API 및 Google의 Gemini Live API와 함께 작동하는 음성 지원 에이전트를 구축하기 위한 통합 인터페이스를 제공합니다.

개요

실시간 에이전트는 다음과 같은 몇 가지 주요 측면에서 텍스트 기반 LlmAgents와 다릅니다:

기능LlmAgentRealtimeAgent
입력텍스트오디오/텍스트
출력텍스트오디오/텍스트
연결HTTP 요청WebSocket
지연 시간요청/응답실시간 스트리밍
VAD해당 없음서버 측 음성 감지

아키텍처

              ┌─────────────────────────────────────────┐
              │              Agent Trait                │
              │  (name, description, run, sub_agents)   │
              └────────────────┬────────────────────────┘
                               │
       ┌───────────────────────┼───────────────────────┐
       │                       │                       │
┌──────▼──────┐      ┌─────────▼─────────┐   ┌─────────▼─────────┐
│  LlmAgent   │      │  RealtimeAgent    │   │  SequentialAgent  │
│ (text-based)│      │  (voice-based)    │   │   (workflow)      │
└─────────────┘      └───────────────────┘   └───────────────────┘

RealtimeAgentLlmAgent과 동일한 Agent trait를 구현하며, 다음을 공유합니다.

  • 지침(정적 및 동적)
  • 도구 등록 및 실행
  • 콜백(before_agent, after_agent, before_tool, after_tool)
  • 하위 에이전트로의 핸드오프

빠른 시작

설치

Cargo.toml에 다음을 추가합니다.

[dependencies]
adk-realtime = { version = "2.1.0", features = ["openai"] }

# For Vertex AI Live (Google Cloud with ADC auth)
# adk-realtime = { version = "2.1.0", features = ["vertex-live"] }

# For LiveKit WebRTC bridge
# adk-realtime = { version = "2.1.0", features = ["livekit"] }

# For all transports (except WebRTC which needs cmake)
# adk-realtime = { version = "2.1.0", features = ["full"] }

기본 사용법

use adk_realtime::{
    RealtimeAgent, RealtimeModel, RealtimeConfig, ServerEvent,
    openai::OpenAIRealtimeModel,
};
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let api_key = std::env::var("OPENAI_API_KEY")?;

    // Create the realtime model
    let model: Arc<dyn RealtimeModel> = Arc::new(
        OpenAIRealtimeModel::new(&api_key, "gpt-realtime-2.1")
    );

    // Build the realtime agent
    let agent = RealtimeAgent::builder("voice_assistant")
        .model(model.clone())
        .instruction("You are a helpful voice assistant. Be concise.")
        .voice("alloy")
        .server_vad()  // Enable voice activity detection
        .build()?;

    // Or use the low-level session API directly
    let config = RealtimeConfig::default()
        .with_instruction("You are a helpful assistant.")
        .with_voice("alloy")
        .with_modalities(vec!["text".to_string(), "audio".to_string()]);

    let session = model.connect(config).await?;

    // Send text and get response
    session.send_text("Hello!").await?;
    session.create_response().await?;

    // Process events
    while let Some(event) = session.next_event().await {
        match event? {
            ServerEvent::TextDelta { delta, .. } => print!("{}", delta),
            ServerEvent::AudioDelta { delta, .. } => {
                // Play audio (delta is base64-encoded PCM)
            }
            ServerEvent::ResponseDone { .. } => break,
            _ => {}
        }
    }

    Ok(())
}

지원되는 제공자

제공업체모델전송 방식기능 플래그오디오 형식
OpenAIgpt-realtime-2.1WebSocketopenaiPCM16 24kHz
OpenAIgpt-realtime-2.1WebRTCopenai-webrtcOpus
Googlegemini-3.1-flash-live-previewWebSocketgeminiPCM16 16kHz/24kHz
GoogleVertex AI를 통한 GeminiWebSocket + OAuth2vertex-livePCM16 16kHz/24kHz
LiveKit모두 (Gemini/OpenAI로 브리지)WebRTClivekitPCM16

참고: gpt-realtime-2.1은 ADK-Rust의 현재 OpenAI 실시간 기본값입니다.

전송 옵션

ADK-Realtime은 여러 전송 계층을 지원합니다.

  • WebSocket (기본값): OpenAI 또는 Gemini에 직접 연결합니다. 간단하고 지연 시간이 짧으며 어디서나 작동합니다.
  • Vertex AI Live: OAuth2 인증(Application Default Credentials)을 사용하여 Google Cloud를 통해 Gemini에 연결합니다. 엔터프라이즈 인증과 GCP 통합이 필요한 경우 사용합니다.
  • LiveKit WebRTC: 프로덕션급 WebRTC 브리지입니다. 확장 가능한 다중 참여자 시나리오를 위해 LiveKit 서버를 통해 오디오를 라우팅합니다.
  • OpenAI WebRTC: Opus 코덱과 데이터 채널을 사용하는 OpenAI에 대한 직접적인 WebRTC 연결입니다. Opus C 라이브러리를 빌드하려면 cmake가 필요합니다.

RealtimeAgent Builder

RealtimeAgentBuilder는 에이전트 구성을 위한 유연한 API를 제공합니다.

let agent = RealtimeAgent::builder("assistant")
    // Required
    .model(model)

    // Instructions (same as LlmAgent)
    .instruction("You are helpful.")
    .instruction_provider(|ctx| format!("User: {}", ctx.user_name()))

    // Voice settings
    .voice("alloy")  // Options: alloy, coral, sage, shimmer, etc.

    // Voice Activity Detection
    .server_vad()  // Use defaults
    .vad(VadConfig {
        mode: VadMode::ServerVad,
        threshold: Some(0.5),
        prefix_padding_ms: Some(300),
        silence_duration_ms: Some(500),
        interrupt_response: Some(true),
        eagerness: None,
    })

    // Tools (same as LlmAgent)
    .tool(Arc::new(weather_tool))
    .tool(Arc::new(search_tool))

    // Sub-agents for handoffs
    .sub_agent(booking_agent)
    .sub_agent(support_agent)

    // Callbacks (same as LlmAgent)
    .before_agent_callback(|ctx| async { Ok(()) })
    .after_agent_callback(|ctx, event| async { Ok(()) })
    .before_tool_callback(|ctx, tool, args| async { Ok(None) })
    .after_tool_callback(|ctx, tool, result| async { Ok(result) })

    // Realtime-specific callbacks
    .on_audio(|audio_chunk| { /* play audio */ })
    .on_transcript(|text| { /* show transcript */ })

    .build()?;

음성 활동 감지(VAD)

VAD는 사용자가 말하기 시작하고 멈추는 시점을 감지하여 자연스러운 대화 흐름을 지원합니다.

let agent = RealtimeAgent::builder("assistant")
    .model(model)
    .server_vad()  // Uses sensible defaults
    .build()?;

사용자 지정 VAD 구성

use adk_realtime::{VadConfig, VadMode};

let vad = VadConfig {
    mode: VadMode::ServerVad,
    threshold: Some(0.5),           // Speech detection sensitivity (0.0-1.0)
    prefix_padding_ms: Some(300),   // Audio to include before speech
    silence_duration_ms: Some(500), // Silence before ending turn
    interrupt_response: Some(true), // Allow interrupting assistant
    eagerness: None,                // For SemanticVad mode
};

let agent = RealtimeAgent::builder("assistant")
    .model(model)
    .vad(vad)
    .build()?;

시맨틱 VAD(Gemini)

Gemini 모델의 경우 의미를 고려하는 시맨틱 VAD를 사용할 수 있습니다.

let vad = VadConfig {
    mode: VadMode::SemanticVad,
    eagerness: Some("high".to_string()),  // low, medium, high
    ..Default::default()
};

도구 호출

실시간 에이전트는 음성 대화 중 도구 호출을 지원합니다.

use adk_realtime::{config::ToolDefinition, ToolResponse};
use serde_json::json;

// Define tools
let tools = vec![
    ToolDefinition {
        name: "get_weather".to_string(),
        description: Some("Get weather for a location".to_string()),
        parameters: Some(json!({
            "type": "object",
            "properties": {
                "location": { "type": "string" }
            },
            "required": ["location"]
        })),
    },
];

let config = RealtimeConfig::default()
    .with_tools(tools)
    .with_instruction("Use tools to help the user.");

let session = model.connect(config).await?;

// Handle tool calls in the event loop
while let Some(event) = session.next_event().await {
    match event? {
        ServerEvent::FunctionCallDone { call_id, name, arguments, .. } => {
            // Execute the tool
            let result = execute_tool(&name, &arguments);

            // Send the response
            let response = ToolResponse::new(&call_id, result);
            session.send_tool_response(response).await?;
        }
        _ => {}
    }
}

다중 에이전트 핸드오프

전문 에이전트 간에 대화를 전달합니다.

// Create sub-agents
let booking_agent = Arc::new(RealtimeAgent::builder("booking_agent")
    .model(model.clone())
    .instruction("Help with reservations.")
    .build()?);

let support_agent = Arc::new(RealtimeAgent::builder("support_agent")
    .model(model.clone())
    .instruction("Help with technical issues.")
    .build()?);

// Create main agent with sub-agents
let receptionist = RealtimeAgent::builder("receptionist")
    .model(model)
    .instruction(
        "Route customers: bookings → booking_agent, issues → support_agent. \
         Use transfer_to_agent tool to hand off."
    )
    .sub_agent(booking_agent)
    .sub_agent(support_agent)
    .build()?;

모델이 transfer_to_agent를 호출하면 RealtimeRunner가 핸드오프를 자동으로 처리합니다.

오디오 형식

형식샘플레이트비트채널사용 사례
PCM1624000 Hz16모노OpenAI (기본값)
PCM1616000 Hz16모노Gemini 입력
G711 u-law8000 Hz8단일 채널전화 통신
G711 A-law8000 Hz8단일 채널전화 통신
use adk_realtime::{AudioFormat, AudioChunk};

// Create audio format
let format = AudioFormat::pcm16_24khz();

// Work with audio chunks
let chunk = AudioChunk::new(audio_bytes, format);
let base64 = chunk.to_base64();
let decoded = AudioChunk::from_base64(&base64, format)?;

이벤트 유형

서버 이벤트

이벤트설명
SessionCreated연결 설정됨
AudioDelta오디오 청크(base64 PCM)
TextDelta텍스트 응답 청크
TranscriptDelta입력 오디오 전사
FunctionCallDone도구 호출 요청
ResponseDone응답 완료
SpeechStartedVAD가 감지한 발화 시작
SpeechStoppedVAD가 감지한 발화 종료
Error오류 발생

클라이언트 이벤트

이벤트설명
AudioInput오디오 청크 전송
AudioCommit오디오 버퍼 커밋
ItemCreate텍스트 또는 도구 응답 보내기
CreateResponse응답 요청
CancelResponse현재 응답 취소
SessionUpdate구성 업데이트

Vertex AI Live (Google Cloud)

엔터프라이즈 인증(ADC, 서비스 계정, WIF)을 사용하여 Vertex AI를 통해 Gemini Live에 연결합니다:

use adk_realtime::gemini::{GeminiLiveBackend, GeminiRealtimeModel};
use adk_realtime::{RealtimeConfig, RealtimeModel};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let project_id = std::env::var("GOOGLE_CLOUD_PROJECT")?;
    let region = std::env::var("GOOGLE_CLOUD_REGION")
        .unwrap_or_else(|_| "us-central1".to_string());

    // Use Application Default Credentials
    let credentials = google_cloud_auth::credentials::Builder::default()
        .build()
        .await?;

    let backend = GeminiLiveBackend::Vertex { credentials, region, project_id };
    let model = GeminiRealtimeModel::new(backend, "models/gemini-3.1-flash-live-preview");

    let config = RealtimeConfig::default()
        .with_instruction("You are a helpful voice assistant.");

    let session = model.connect(config).await?;
    session.send_text("Hello from Vertex AI!").await?;
    session.create_response().await?;

    // Process events...
    Ok(())
}

ADC를 위한 편의 생성자도 있습니다:

let model = GeminiRealtimeModel::vertex_adc(
    "us-central1",
    "my-project-id",
    "models/gemini-3.1-flash-live-preview",
).await?;

도구 호출을 사용하는 Vertex AI Live

vertex_live_tools 예제에서는 Vertex AI Live 세션을 통해 함수 호출을 보여 줍니다:

use adk_realtime::config::ToolDefinition;
use adk_realtime::events::ToolResponse;
use serde_json::json;

// Declare tools
let tools = vec![
    ToolDefinition {
        name: "get_weather".to_string(),
        description: Some("Get current weather for a city".to_string()),
        parameters: Some(json!({
            "type": "object",
            "properties": {
                "city": { "type": "string" }
            },
            "required": ["city"]
        })),
    },
];

let config = RealtimeConfig::default()
    .with_tools(tools)
    .with_instruction("Use tools to answer questions about weather.");

let session = model.connect(config).await?;

// Handle FunctionCallDone events and send ToolResponse back
while let Some(event) = session.next_event().await {
    match event? {
        ServerEvent::FunctionCallDone { call_id, name, arguments, .. } => {
            let result = match name.as_str() {
                "get_weather" => json!({"temperature": "22°C", "condition": "sunny"}),
                _ => json!({"error": "unknown tool"}),
            };
            session.send_tool_response(ToolResponse::new(&call_id, result)).await?;
        }
        ServerEvent::TextDelta { delta, .. } => print!("{delta}"),
        ServerEvent::ResponseDone { .. } => break,
        _ => {}
    }
}

기능 플래그

기능종속성사용 사례
vertex-livegemini + google-cloud-authADC/서비스 계정 인증을 사용하는 Vertex AI Live
livekitlivekit + livekit-apiLiveKit WebRTC 브리지
openai-webrtcopenai + str0m + audiopusOpenAI WebRTC 및 Opus (cmake 필요)
fullopenai + gemini + vertex-live + livekitWebRTC을 제외한 모든 전송 방식
full-webrtcfull + openai-webrtc전체 (cmake 필요)

LiveKit WebRTC 브리지

프로덕션 음성 애플리케이션에서 LiveKit 브리지는 확장 가능한 다중 참여자 시나리오를 위해 오디오를 LiveKit 서버를 통해 라우팅합니다.

LiveKitConfig

LiveKit 자격 증명을 안전하게 구성합니다. API 키와 시크릿은 secrecy::SecretString을 사용해 저장되며 Debug 출력에서는 제거됩니다:

use adk_realtime::livekit::{LiveKitConfig, LiveKitRoomBuilder};

let config = LiveKitConfig::new(
    "wss://your-server.livekit.cloud",
    std::env::var("LIVEKIT_API_KEY")?,
    std::env::var("LIVEKIT_API_SECRET")?,
)?;

LiveKitConfig::new()은 URL 형식의 유효성을 검사하고 생성 시점에 비어 있는 자격 증명을 거부합니다.

LiveKitRoomBuilder

LiveKit 룸에 연결하기 위한 typestate 빌더입니다. identity 필드는 컴파일 시점에 필수이며, 설정한 후에만 connect()을 사용할 수 있습니다:

let bundle = LiveKitRoomBuilder::new(config)
    .identity("my-agent")           // required — enables connect()
    .name("Voice Agent")            // optional display name
    .room_name("session-room-123")  // optional — auto-generated if omitted
    .auto_subscribe(true)           // subscribe to remote tracks
    .with_audio(24_000, 1)          // publish a local audio track (sample rate, channels)
    .connect()
    .await?;

// The bundle contains everything you need
let room = bundle.room;
let mut events = bundle.events;
let audio_source = bundle.audio_source;  // for publishing audio
let audio_track = bundle.audio_track;

오디오 브리징

브리지 유틸리티를 사용해 LiveKit 오디오를 RealtimeRunner에 연결합니다:

use adk_realtime::livekit::{LiveKitEventHandler, bridge_input};

// Wrap your event handler to publish model audio to LiveKit
let lk_handler = LiveKitEventHandler::new(inner_handler, audio_source, 24000, 1);

// Bridge participant audio from LiveKit into the RealtimeRunner
tokio::spawn(bridge_input(remote_track, runner));

예제

포함된 예제를 실행합니다:

# OpenAI Realtime (WebSocket)
cargo run -p adk-realtime --example openai_session_update --features openai

# Vertex AI Live (requires gcloud auth application-default login)
cargo run -p adk-realtime --example vertex_live_voice --features vertex-live
cargo run -p adk-realtime --example vertex_live_tools --features vertex-live

# LiveKit Bridge (requires LiveKit server)
cargo run -p adk-realtime --example livekit_bridge --features livekit,openai
cargo run -p adk-realtime --example livekit_gemini_bridge --features livekit,gemini

# Debug utilities
cargo run -p adk-realtime --example debug_gemini --features gemini
cargo run -p adk-realtime --example debug_livekit_auth --features livekit

# OpenAI WebRTC (requires cmake)
cargo run -p adk-realtime --example openai_webrtc --features openai-webrtc

모범 사례

  1. Server VAD 사용: 지연 시간을 줄이려면 서버에서 음성 감지를 처리하도록 합니다
  2. 중단 처리: 자연스러운 대화를 위해 interrupt_response을 활성화합니다
  3. 지침을 간결하게 유지: 음성 응답은 짧아야 합니다
  4. 먼저 텍스트로 테스트: 오디오를 추가하기 전에 텍스트로 에이전트 로직을 디버깅합니다
  5. 오류를 적절히 처리: WebSocket 연결에서는 네트워크 문제가 흔히 발생합니다

OpenAI 에이전트 SDK와의 비교

ADK-Rust의 실시간 구현은 OpenAI 에이전트 SDK 패턴을 따릅니다:

기능OpenAI SDKADK-Rust
에이전트 기본 클래스AgentAgent 트레이트
실시간 에이전트RealtimeAgentRealtimeAgent
도구함수 정의Tool 트레이트 + ToolDefinition
핸드오프transfer_to_agentsub_agents + 자동 생성 도구
콜백before_* / after_* 콜백

이전: ← 그래프 에이전트 | 다음: 모델 제공자 →

실시간 음성 에이전트 - ADK-Rust 문서 | ADK-Rust