실시간 음성 에이전트

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

개요

실시간 에이전트는 텍스트 기반 LlmAgents와 몇 가지 주요 방식에서 다릅니다:

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

아키텍처

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

RealtimeAgentLlmAgent와 동일한 Agent trait을 구현하며 다음을 공유합니다:

  • 명령 (정적 및 동적)
  • Tool 등록 및 실행
  • 콜백 (before_agent, after_agent, before_tool, after_tool)
  • 하위 Agent 핸드오프

빠른 시작

설치

Cargo.toml에 추가하세요:

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

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

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

# For all transports (except WebRTC which needs cmake)
# adk-realtime = { version = "2.0.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")
    );

    // 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-realtimeWebSocketopenaiPCM16 24kHz
OpenAIgpt-realtimeWebRTCopenai-webrtcOpus
구글gemini-live-2.5-flash-native-audioWebSocketgeminiPCM16 16kHz/24kHz
GoogleGemini 통해 Vertex AIWebSocket + OAuth2vertex-livePCM16 16kHz/24kHz
LiveKit어떤 (Gemini/OpenAI로 연결)WebRTClivekitPCM16

참고: gpt-realtime은 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 빌더

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

Tool Calling

실시간 에이전트는 음성 대화 중에 Tool Calling을 지원합니다:

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?;
        }
        _ => {}
    }
}

다중 Agent 핸드오프

전문화된 Agent 간에 대화를 전송합니다:

// 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가 handoff를 자동으로 처리합니다.

오디오 형식

형식샘플 속도비트채널사용 사례
PCM1624000 Hz16MonoOpenAI (기본값)
PCM1616000 Hz16MonoGemini 입력
G711 u-law8000 Hz8MonoTelephony
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)

Vertex AI를 통해 엔터프라이즈 인증(ADC, 서비스 계정, WIF)으로 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-live-2.5-flash-native-audio");

    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-live-2.5-flash-native-audio",
).await?;

Vertex AI Live with Tool Calling

vertex_live_tools 예제는 Vertex AI Live session에서 function calling을 시연합니다:

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-authVertex AI 실시간 ADC/서비스 계정 인증
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을 사용하여 저장되며 디버그 출력에서 수정됩니다:

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 방에 연결하기 위한 타입스테이트 빌더입니다. 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. 서버 VAD 사용: 서버가 음성 감지를 처리하여 지연 시간을 줄입니다.
  2. 방해 처리: 자연스러운 대화를 위해 interrupt_response를 활성화합니다.
  3. 지침을 간결하게 유지: 음성 응답은 간결해야 합니다.
  4. 텍스트로 먼저 테스트: 오디오를 추가하기 전에 텍스트로 에이전트 로직을 디버그합니다.
  5. 오류를 우아하게 처리: WebSocket 연결에서는 네트워크 문제가 흔합니다.

OpenAI Agents SDK와 비교

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

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

이전: ← Graph Agents | 다음: Model Providers →