Agentes de voz en tiempo real

Los agentes en tiempo real permiten interacciones basadas en voz con asistentes de IA mediante transmisión bidireccional de audio. El crate adk-realtime proporciona una interfaz unificada para crear agentes con capacidades de voz que funcionan con API Realtime de OpenAI y API Live de Gemini de Google.

Descripción general

Los agentes en tiempo real se diferencian de los LlmAgents basados en texto en varios aspectos clave:

FunciónLlmAgentRealtimeAgent
EntradaTextoAudio/Texto
SalidaTextoAudio/Texto
ConexiónHTTP solicitudesWebSocket
LatenciaSolicitud/respuestaTransmisión en tiempo real
VADN/DDetección de voz del lado del servidor

Arquitectura

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

RealtimeAgent implementa el mismo trait Agent que LlmAgent, compartiendo:

  • Instrucciones (estáticas y dinámicas)
  • Registro y ejecución de herramientas
  • Callbacks (before_agent, after_agent, before_tool, after_tool)
  • Transferencias entre subagentes

Inicio rápido

Instalación

Añádelo a tu 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(())
}

Proveedores compatibles

ProveedorModeloTransporteIndicador de funcionalidadFormato de audio
OpenAIgpt-realtime-2.1WebSocketopenaiPCM16 24kHz
OpenAIgpt-realtime-2.1WebRTCopenai-webrtcOpus
Googlegemini-3.1-flash-live-previewWebSocketgeminiPCM16 16kHz/24kHz
GoogleGemini mediante Vertex AIWebSocket + OAuth2vertex-livePCM16 16kHz/24kHz
LiveKitCualquiera (puente a Gemini/OpenAI)WebRTClivekitPCM16

Nota: gpt-realtime-2.1 es el OpenAI predeterminado actual de tiempo real de ADK-Rust.

Opciones de transporte

ADK-Realtime admite varias capas de transporte:

  • WebSocket (predeterminado): conexión directa con OpenAI o Gemini. Sencilla, de baja latencia y funciona en todas partes.
  • Vertex AI Live: se conecta a Gemini mediante Google Cloud con autenticación OAuth2 (Credenciales predeterminadas de la aplicación). Úselo cuando necesite autenticación empresarial e integración con GCP.
  • LiveKit WebRTC: puente WebRTC de nivel de producción. Enruta el audio a través de un servidor LiveKit para escenarios escalables con múltiples participantes.
  • OpenAI WebRTC: conexión directa WebRTC con OpenAI mediante el códec Opus y canales de datos. Requiere cmake para compilar la biblioteca C de Opus.

Constructor de RealtimeAgent

RealtimeAgentBuilder proporciona una API fluida 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()?;

Detección de actividad de voz (VAD)

VAD permite un flujo de conversación natural al detectar cuándo el usuario comienza y deja de hablar.

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

Configuración de VAD personalizada

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 los modelos Gemini, puede usar VAD semántico, que tiene en cuenta el significado:

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

Llamadas a herramientas

Los agentes en tiempo real admiten llamadas a herramientas durante las conversaciones de 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?;
        }
        _ => {}
    }
}

Transferencias entre múltiples agentes

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

Cuando el modelo llama a transfer_to_agent, RealtimeRunner gestiona la transferencia automáticamente.

Formatos de audio

FormatoFrecuencia de muestreoBitsCanalesCaso de uso
PCM1624000 Hz16MonoOpenAI (predeterminado)
PCM1616000 Hz16MonoEntrada de Gemini
G711 u-law8000 Hz8MonoTelefonía
G711 A-law8000 Hz8MonoTelefonía
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 del servidor

EventoDescripción
SessionCreatedConexión establecida
AudioDeltaFragmento de audio (PCM en base64)
TextDeltaFragmento de respuesta de texto
TranscriptDeltaTranscripción del audio de entrada
FunctionCallDoneSolicitud de llamada a la herramienta
ResponseDoneRespuesta completada
SpeechStartedVAD detectó el inicio del habla
SpeechStoppedVAD detectó el final del habla
ErrorSe produjo un error

Eventos del cliente

EventoDescripción
AudioInputEnviar fragmento de audio
AudioCommitConfirmar búfer de audio
ItemCreateEnviar texto o respuesta de herramienta
CreateResponseSolicitar una respuesta
CancelResponseCancelar la respuesta actual
SessionUpdateActualizar la configuración

Vertex AI Live (Google Cloud)

Conéctate a Gemini Live mediante Vertex AI con autenticación empresarial (ADC, cuentas de servicio, 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(())
}

También hay un constructor de conveniencia para ADC:

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

Vertex AI Live con llamadas a herramientas

El ejemplo vertex_live_tools demuestra las llamadas a funciones en una sesión de 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,
        _ => {}
    }
}

Indicadores de funciones

FunciónDependenciasCaso de uso
vertex-livegemini + google-cloud-authVertex AI Live con autenticación mediante ADC/cuenta de servicio
livekitlivekit + livekit-apiPuente de LiveKit WebRTC
openai-webrtcopenai + str0m + audiopusOpenAI WebRTC con Opus (requiere cmake)
fullopenai + gemini + vertex-live + livekitTodos los transportes excepto WebRTC
full-webrtcfull + openai-webrtcTodo (requiere cmake)

LiveKit WebRTC Puente

Para aplicaciones de voz en producción, el puente LiveKit enruta el audio a través de un servidor LiveKit para escenarios escalables con múltiples participantes.

LiveKitConfig

Configura de forma segura las credenciales de LiveKit. Las claves y secretos de API se almacenan mediante secrecy::SecretString y se redactan en la salida de depuración:

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 el formato de URL y rechaza las credenciales vacías durante la construcción.

LiveKitRoomBuilder

Un constructor basado en typestate para conectarse a salas de LiveKit. El campo identity es obligatorio en tiempo de compilación; connect() solo está disponible después de establecerlo:

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;

Puente de audio

Usa las utilidades del puente para conectar el audio de LiveKit a un 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));

Ejemplos

Ejecuta los ejemplos incluidos:

# 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ácticas recomendadas

  1. Usa VAD del servidor: permite que el servidor gestione la detección del habla para reducir la latencia
  2. Gestiona las interrupciones: habilita interrupt_response para mantener conversaciones naturales
  3. Mantén las instrucciones concisas: las respuestas de voz deben ser breves
  4. Prueba primero con texto: depura la lógica de tu agente con texto antes de añadir audio
  5. Gestiona los errores adecuadamente: los problemas de red son habituales en las conexiones WebSocket

Comparación con los agentes OpenAI SDK

La implementación en tiempo real de ADK-Rust sigue el patrón de agentes OpenAI SDK:

CaracterísticaOpenAI SDKADK-Rust
Clase base del agenteAgenttrait Agent
Agente en tiempo realRealtimeAgentRealtimeAgent
HerramientasDefiniciones de funcionesTool rasgo + ToolDefinition
Transferenciastransfer_to_agentsub_agents + herramienta generada automáticamente
Devoluciones de llamadaGanchosbefore_* / after_* devoluciones de llamada

Anterior: ← Agentes de grafo | Siguiente: Proveedores de modelos →