Agentes de Voz em Tempo Real

Agentes em tempo real permitem interações baseadas em voz com assistentes de IA usando streaming de áudio bidirecional. A crate adk-realtime fornece uma interface unificada para construir agentes habilitados para voz que funcionam com o Realtime API da OpenAI e o Gemini Live API do Google.

Visão Geral

Agentes em tempo real diferem dos LlmAgents baseados em texto de várias maneiras importantes:

FuncionalidadeLlmAgentRealtimeAgent
EntradaTextoÁudio/Texto
SaídaTextoÁudio/Texto
ConexãoHTTP requisiçõesWebSocket
LatênciaRequisição/respostaStreaming em tempo real
VADN/ADetecção de voz do lado do servidor

Arquitetura

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

RealtimeAgent implementa o mesmo Agent trait que LlmAgent, compartilhando:

  • Instruções (estáticas e dinâmicas)
  • Registro e execução de Tool
  • Callbacks (before_agent, after_agent, before_tool, after_tool)
  • Transferências de sub-agent

Início Rápido

Instalação

Adicione ao seu 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"] }

Uso Básico

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

Provedores Suportados

ProvedorModeloTransporteFlag de RecursoFormato de Áudio
OpenAIgpt-realtimeWebSocketopenaiPCM16 24kHz
OpenAIgpt-realtimeWebRTCopenai-webrtcOpus
Googlegemini-live-2.5-flash-native-audioWebSocketgeminiPCM16 16kHz/24kHz
GoogleGemini via Vertex AIWebSocket + OAuth2vertex-livePCM16 16kHz/24kHz
LiveKitQualquer (ponte para Gemini/OpenAI)WebRTClivekitPCM16

Nota: gpt-realtime é o modelo em tempo real mais recente de OpenAI com qualidade de fala, emoção e recursos de chamada de função aprimorados.

Opções de Transporte

ADK-Realtime suporta múltiplas camadas de transporte:

  • WebSocket (padrão): Conexão direta com OpenAI ou Gemini. Simples, baixa latência, funciona em qualquer lugar.
  • Vertex AI Live: Conecta-se ao Gemini via Google Cloud com autenticação OAuth2 (Application Default Credentials). Use quando precisar de autenticação corporativa e integração com GCP.
  • LiveKit WebRTC: Ponte WebRTC de nível de produção. Encaminha áudio através de um servidor LiveKit para cenários escaláveis e com múltiplos participantes.
  • OpenAI WebRTC: Conexão WebRTC direta com OpenAI com codec Opus e canais de dados. Requer cmake para construir a biblioteca C do Opus.

Construtor RealtimeAgent

O RealtimeAgentBuilder fornece um construtor fluente API para configurar agentes:

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

Detecção de Atividade de Voz (VAD)

VAD permite um fluxo de conversa natural ao detectar quando o usuário começa e para de falar.

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

Configuração Personalizada de 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 Semântico (Gemini)

Para modelos Gemini, você pode usar VAD semântico que considera o significado:

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

Chamada de Ferramentas

Agentes em tempo real suportam chamada de ferramentas durante conversas por voz:

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

Transferências Multi-Agente

Transfira conversas entre agentes especializados:

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

Quando o modelo chama transfer_to_agent, o RealtimeRunner lida com a transferência automaticamente.

Formatos de Áudio

FormatoTaxa de AmostragemBitsCanaisCaso de Uso
PCM1624000 Hz16MonoOpenAI (padrão)
PCM1616000 Hz16MonoEntrada Gemini
G711 u-law8000 Hz8MonoTelefonia
G711 A-law8000 Hz8MonoTelefonia
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)?;

Tipos de Eventos

Eventos do Servidor

EventoDescrição
SessionCreatedConexão estabelecida
AudioDeltaFragmento de áudio (base64 PCM)
TextDeltaFragmento de resposta de texto
TranscriptDeltaTranscrição de áudio de entrada
FunctionCallDoneSolicitação de chamada de ferramenta
ResponseDoneResposta concluída
SpeechStartedInício de fala detectado pelo VAD
SpeechStoppedFim de fala detectado por VAD
ErrorOcorreu um erro

Eventos do Cliente

EventoDescrição
AudioInputEnviar pedaço de áudio
AudioCommitConfirmar buffer de áudio
ItemCreateEnviar texto ou resposta de ferramenta
CreateResponseSolicitar uma resposta
CancelResponseCancelar resposta atual
SessionUpdateAtualizar configuração

Vertex AI Live (Google Cloud)

Conecte-se ao Gemini Live via Vertex AI com autenticação corporativa (ADC, service accounts, WIF):

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

Há também um construtor de conveniência para ADC:

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

Vertex AI Live com Chamada de Ferramentas

O exemplo vertex_live_tools demonstra a chamada de função em uma sessão do 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,
        _ => {}
    }
}

Flags de Recurso

FuncionalidadeDependênciasCaso de Uso
vertex-livegemini + google-cloud-authVertex AI ao Vivo com autenticação ADC/conta de serviço
livekitlivekit + livekit-apiLiveKit WebRTC ponte
openai-webrtcopenai + str0m + audiopusOpenAI WebRTC com Opus (requer cmake)
fullopenai + gemini + vertex-live + livekitTodos os transportes, exceto WebRTC
full-webrtcfull + openai-webrtcTudo (requer cmake)

LiveKit WebRTC Ponte

Para aplicações de voz em produção, a LiveKit ponte direciona o áudio através de um LiveKit servidor para cenários escaláveis e com múltiplos participantes.

LiveKitConfig

Configure com segurança LiveKit credenciais. API chaves e segredos são armazenados usando secrecy::SecretString e ocultados na saída de 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() valida o URL formato e rejeita credenciais vazias no momento da construção.

LiveKitRoomBuilder

Um construtor de typestate para conectar-se a LiveKit salas. O campo identity é obrigatório em tempo de compilação — connect() está disponível apenas depois de ser definido:

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;

Ponte de Áudio

Use os utilitários da ponte para conectar LiveKit áudio a um 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));

Exemplos

Execute os exemplos incluídos:

# 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

Melhores Práticas

  1. Usar VAD do Servidor: Deixe o servidor lidar com a detecção de fala para menor latência
  2. Lidar com interrupções: Ative interrupt_response para conversas naturais
  3. Mantenha as instruções concisas: As respostas de voz devem ser breves
  4. Teste com texto primeiro: Depure a lógica do seu agente com texto antes de adicionar áudio
  5. Lidar com erros de forma elegante: Problemas de rede são comuns com conexões WebSocket

Comparação com OpenAI Agents SDK

A implementação em tempo real de ADK-Rust segue o OpenAI Agents SDK padrão:

FuncionalidadeOpenAI SDKADK-Rust
Classe base do AgentAgentAgent trait
Agente em tempo realRealtimeAgentRealtimeAgent
FerramentasDefinições de funçãoTool trait + ToolDefinition
Transferênciastransfer_to_agentsub_agents + ferramenta gerada automaticamente
Chamadas de RetornoGanchosbefore_* / after_* chamadas de retorno

Anterior: ← Graph Agents | Próximo: Model Providers →