Agentes de voz em tempo real

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

Visão geral

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

RecursoLlmAgentRealtimeAgent
EntradaTextoÁudio/Texto
SaídaTextoÁudio/Texto
ConexãoHTTP solicitaçõesWebSocket
LatênciaSolicitação/respostaStreaming em tempo real
VADN/DDetecção de voz no servidor

Arquitetura

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

RealtimeAgent implementa o mesmo trait Agent que LlmAgent, compartilhando:

  • Instruções (estáticas e dinâmicas)
  • Registro e execução de ferramentas
  • Retornos de chamada (before_agent, after_agent, before_tool, after_tool)
  • Transferências para subagentes

Início rápido

Instalação

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

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

Provedores compatíveis

ProvedorModeloTransporteFlag de recursoFormato de áudio
OpenAIgpt-realtime-2.1WebSocketopenaiPCM16 24kHz
OpenAIgpt-realtime-2.1WebRTCopenai-webrtcOpus
Googlegemini-3.1-flash-live-previewWebSocketgeminiPCM16 16kHz/24kHz
GoogleGemini via Vertex AIWebSocket + OAuth2vertex-livePCM16 16kHz/24kHz
LiveKitQualquer (ponte para Gemini/OpenAI)WebRTClivekitPCM16

Observação: gpt-realtime-2.1 é o padrão atual de tempo real de ADK-Rust para OpenAI.

Opções de transporte

ADK-Realtime oferece várias camadas de transporte:

  • WebSocket (padrão): Conexão direta com OpenAI ou Gemini. Simples, com baixa latência e compatível em qualquer lugar.
  • Vertex AI Live: Conecta-se ao Gemini pelo 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 o áudio por meio de um servidor LiveKit para cenários escaláveis com vários participantes.
  • OpenAI WebRTC: Conexão direta WebRTC com OpenAI, com codec Opus e canais de dados. Requer cmake para compilar a biblioteca C do Opus.

Construtor RealtimeAgent

O RealtimeAgentBuilder fornece uma API fluente 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)

O VAD permite um fluxo natural de conversa 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 do 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 o VAD semântico, que considera o significado:

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

Chamadas de ferramentas

Agentes em tempo real oferecem suporte a chamadas 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 entre vários agentes

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 gerencia a transferência automaticamente.

Formatos de áudio

FormatoTaxa de amostragemBitsCanaisCaso de uso
PCM1624000 Hz16MonoOpenAI (padrão)
PCM1616000 Hz16MonoEntrada do 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
AudioDeltaBloco de áudio (PCM em base64)
TextDeltaFragmento da resposta de texto
TranscriptDeltaTranscrição do áudio de entrada
FunctionCallDoneSolicitação de chamada de ferramenta
ResponseDoneResposta concluída
SpeechStartedInício da fala detectado pelo VAD
SpeechStoppedFim da fala detectado pelo VAD
ErrorOcorreu um erro

Eventos do Cliente

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

Vertex AI Live (Google Cloud)

Conecte-se ao Gemini Live via Vertex AI com autenticação empresarial (ADC, contas de serviço, 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-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(())
}

Também há um construtor conveniente para ADC:

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

Vertex AI Live com chamada de ferramentas

O exemplo vertex_live_tools demonstra a chamada de funções 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,
        _ => {}
    }
}

Sinalizadores de recursos

RecursoDependênciasCaso de uso
vertex-livegemini + google-cloud-authVertex AI Live com autenticação ADC/conta de serviço
livekitlivekit + livekit-apiPonte LiveKit WebRTC
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 ponte LiveKit roteia o áudio por meio de um servidor LiveKit para cenários escaláveis com vários participantes.

LiveKitConfig

Configure com segurança as credenciais de LiveKit. As chaves e os segredos de API são armazenados usando secrecy::SecretString e ocultados na saída de depuração:

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 formato de URL e rejeita credenciais vazias no momento da construção.

LiveKitRoomBuilder

Um builder de typestate para conexão com salas LiveKit. O campo identity é obrigatório em tempo de compilação — connect() só fica disponível depois que ele é 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;

Fazendo a ponte de áudio

Use os utilitários da ponte para conectar o áudio de LiveKit 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

Práticas recomendadas

  1. Use o VAD do servidor: deixe o servidor cuidar da detecção de fala para obter menor latência
  2. Trate as interrupções: habilite interrupt_response para conversas naturais
  3. Mantenha as instruções concisas: as respostas de voz devem ser breves
  4. Teste primeiro com texto: depure a lógica do seu agente com texto antes de adicionar áudio
  5. Trate os erros adequadamente: problemas de rede são comuns em conexões WebSocket

Comparação com agentes OpenAI SDK

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

RecursoOpenAI SDKADK-Rust
Classe base do agenteAgentAgent trait
Agente em tempo realRealtimeAgentRealtimeAgent
FerramentasDefinições de funçãoTool trait + ToolDefinition
Transferênciastransfer_to_agentsub_agents + ferramenta gerada automaticamente
Retornos de chamadaGanchosretornos de chamada before_* / after_*

Anterior: ← Agentes de Grafo | Próximo: Provedores de Modelos →

Agentes de voz em tempo real - Documentação ADK-Rust | ADK-Rust