Agentes de Voz en Tiempo Real

Los agentes en tiempo real permiten interacciones basadas en voz con asistentes de IA utilizando transmisión de audio bidireccional. La crate adk-realtime proporciona una interfaz unificada para construir agentes habilitados para voz que funcionan con la API en tiempo real de OpenAI y la API Gemini Live de Google.

Resumen

Los agentes en tiempo real difieren de los LlmAgent basados en texto de varias maneras clave:

CaracterísticaLlmAgentRealtimeAgent
EntradaTextoAudio/Texto
SalidaTextoAudio/Texto
ConexiónSolicitudes HTTPWebSocket
LatenciaSolicitud/respuestaStreaming en tiempo real
VADN/ADetecció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 Tool
  • Callbacks (before_agent, after_agent, before_tool, after_tool)
  • Transferencias de Sub-agent

Inicio Rápido

Instalación

Agrega a tu Cargo.toml:

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

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-4o-realtime-preview-2024-12-17")
    );

    // 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

ProveedorModeloFeature FlagFormato de Audio
OpenAIgpt-4o-realtime-preview-2024-12-17openaiPCM16 24kHz
OpenAIgpt-realtimeopenaiPCM16 24kHz
Googlegemini-2.0-flash-live-preview-04-09geminiPCM16 16kHz/24kHz

Nota: gpt-realtime es el último modelo en tiempo real de OpenAI con calidad de voz, emoción y capacidades de llamada a funciones mejoradas.

Constructor de RealtimeAgent

El 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.

VAD del Servidor (Recomendado)

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),           // Sensibilidad de detección de voz (0.0-1.0)
    prefix_padding_ms: Some(300),   // Audio a incluir antes del habla
    silence_duration_ms: Some(500), // Silencio antes de finalizar el turno
    interrupt_response: Some(true), // Permitir interrumpir al asistente
    eagerness: None,                // Para el modo SemanticVad
};

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

VAD Semántico (Gemini)

Para los modelos Gemini, puedes usar VAD semántico que considera el significado:

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

Llamada a herramientas

Los agentes en tiempo real soportan la llamada a herramientas durante las conversaciones de voz:

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

// Definir herramientas
let tools = vec![
    ToolDefinition {
        name: "get_weather".to_string(),
        description: Some("Obtener el clima para una ubicación".to_string()),
        parameters: Some(json!({
            "type": "object",
            "properties": {
                "location": { "type": "string" }
            },
            "required": ["location"]
        })),
    },
];

let config = RealtimeConfig::default()
    .with_tools(tools)
    .with_instruction("Usar herramientas para ayudar al usuario.");

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

// Manejar llamadas a herramientas en el bucle de eventos
while let Some(event) = session.next_event().await {
    match event? {
        ServerEvent::FunctionCallDone { call_id, name, arguments, .. } => {
            // Ejecutar la herramienta
            let result = execute_tool(&name, &arguments);

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

Traspasos Multi-Agente

Transferir conversaciones entre agentes especializados:

// Crear sub-agentes
let booking_agent = Arc::new(RealtimeAgent::builder("booking_agent")
    .model(model.clone())
    .instruction("Ayudar con las reservas.")
    .build()?);

let support_agent = Arc::new(RealtimeAgent::builder("support_agent")
    .model(model.clone())
    .instruction("Ayudar con problemas técnicos.")
    .build()?);

// Crear agente principal con sub-agentes
let receptionist = RealtimeAgent::builder("receptionist")
    .model(model)
    .instruction(
        "Dirigir clientes: reservas → booking_agent, problemas → support_agent. \
         Usar la herramienta transfer_to_agent para el traspaso."
    )
    .sub_agent(booking_agent)
    .sub_agent(support_agent)
    .build()?;

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

Formatos de Audio

FormatoFrecuencia de MuestreoBitsCanalesCaso de Uso
PCM1624000 Hz16MonoOpenAI (por defecto)
PCM1616000 Hz16MonoEntrada Gemini
G711 u-law8000 Hz8MonoTelefonía
G711 A-law8000 Hz8MonoTelefonía
use adk_realtime::{AudioFormat, AudioChunk};

// Crear formato de audio
let format = AudioFormat::pcm16_24khz();

// Trabajar con fragmentos de audio
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 base64)
TextDeltaFragmento de respuesta de texto
TranscriptDeltaTranscripción de audio de entrada
FunctionCallDoneSolicitud de llamada a herramienta
ResponseDoneRespuesta completada
SpeechStartedInicio de habla detectado por VAD
SpeechStoppedFin de habla detectado por VAD
ErrorOcurrió 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 respuesta actual
SessionUpdateActualizar configuración

Ejemplos

Ejecutar los ejemplos incluidos:

# Sesión básica solo de texto
cargo run --example realtime_basic --features realtime-openai

# Asistente de voz con VAD
cargo run --example realtime_vad --features realtime-openai

# Llamada a herramientas
cargo run --example realtime_tools --features realtime-openai

# Traspasos multi-agente
cargo run --example realtime_handoff --features realtime-openai

Mejores Prácticas

  1. Usar VAD del Servidor: Permita que el servidor gestione la detección de voz para una menor latencia
  2. Gestionar interrupciones: Habilite interrupt_response para conversaciones naturales
  3. Mantenga las instrucciones concisas: Las respuestas de voz deben ser breves
  4. Pruebe primero con texto: Depure la lógica de su Agent con texto antes de añadir audio
  5. Maneje los errores con elegancia: Los problemas de red son comunes con las conexiones WebSocket

Comparación con el SDK de OpenAI Agents

La implementación en tiempo real de adk-rust sigue el patrón del SDK de OpenAI Agents:

CaracterísticaOpenAI SDKadk-rust
Clase base de AgentAgenttrait Agent
Agent en tiempo realRealtimeAgentRealtimeAgent
HerramientasDefiniciones de funcionestrait Tool + ToolDefinition
Traspasostransfer_to_agentsub_agents + herramienta autogenerada
CallbacksGanchoscallbacks before_* / after_*

Anterior: ← Agents de Grafo | Siguiente: Proveedores de Modelos →