Agents vocaux en temps réel

Les agents en temps réel permettent des interactions vocales avec des assistants d’IA grâce à la diffusion audio bidirectionnelle. La crate adk-realtime fournit une interface unifiée pour créer des agents dotés de fonctionnalités vocales qui fonctionnent avec le API Realtime de OpenAI et le API Live de Gemini de Google.

Vue d’ensemble

Les agents en temps réel diffèrent des LlmAgents basés sur le texte de plusieurs façons essentielles :

FonctionnalitéLlmAgentRealtimeAgent
EntréeTexteAudio/Texte
SortieTexteAudio/Texte
Connexionrequêtes HTTPWebSocket
LatenceRequête/réponseStreaming en temps réel
VADN/ADétection vocale côté serveur

Architecture

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

RealtimeAgent implémente le même trait Agent que LlmAgent, en partageant :

  • Instructions (statiques et dynamiques)
  • Enregistrement et exécution des outils
  • Rappels (before_agent, after_agent, before_tool, after_tool)
  • Transferts vers des sous-agents

Démarrage rapide

Installation

Ajoutez à votre 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"] }

Utilisation de base

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

Fournisseurs pris en charge

FournisseurModèleTransportIndicateur de fonctionnalitéFormat audio
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
LiveKitTout (pont vers Gemini/OpenAI)WebRTClivekitPCM16

Remarque : gpt-realtime-2.1 est la valeur par défaut actuelle de OpenAI en temps réel de ADK-Rust.

Options de transport

ADK-Realtime prend en charge plusieurs couches de transport :

  • WebSocket (par défaut) : Connexion directe à OpenAI ou Gemini. Simple, avec une faible latence, et fonctionne partout.
  • Vertex AI Live : Se connecte à Gemini via Google Cloud avec une authentification OAuth2 (Application Default Credentials). À utiliser lorsque vous avez besoin d’une authentification d’entreprise et d’une intégration avec GCP.
  • LiveKit WebRTC : Pont WebRTC de niveau production. Achemine l’audio via un serveur LiveKit pour les scénarios évolutifs avec plusieurs participants.
  • OpenAI WebRTC : Connexion directe WebRTC à OpenAI avec le codec Opus et des canaux de données. Nécessite cmake pour compiler la bibliothèque C Opus.

Constructeur RealtimeAgent

RealtimeAgentBuilder fournit une API fluide pour configurer les agents :

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

Détection d’activité vocale (VAD)

La VAD permet un déroulement naturel de la conversation en détectant quand l’utilisateur commence et arrête de parler.

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

Configuration VAD personnalisée

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 sémantique (Gemini)

Pour les modèles Gemini, vous pouvez utiliser la VAD sémantique, qui prend en compte le sens :

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

Appel d’outils

Les agents en temps réel prennent en charge les appels d’outils pendant les conversations vocales :

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

Transferts entre agents

Transférez les conversations entre des agents spécialisés :

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

Lorsque le modèle appelle transfer_to_agent, RealtimeRunner gère automatiquement le transfert.

Formats audio

FormatFréquence d’échantillonnageBitsCanauxCas d’utilisation
PCM1624000 Hz16MonoOpenAI (par défaut)
PCM1616000 Hz16MonoEntrée Gemini
G711 u-law8000 Hz8MonoTéléphonie
G711 A-law8000 Hz8MonoTéléphonie
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)?;

Types d’événements

Événements du serveur

ÉvénementDescription
SessionCreatedConnexion établie
AudioDeltaSegment audio (PCM en base64)
TextDeltaFragment de réponse textuelle
TranscriptDeltaTranscription audio d'entrée
FunctionCallDoneDemande d'appel d'outil
ResponseDoneRéponse terminée
SpeechStartedDébut de la parole détecté par VAD
SpeechStoppedFin de la parole détectée par VAD
ErrorUne erreur s’est produite

Événements client

ÉvénementDescription
AudioInputEnvoyer un fragment audio
AudioCommitValider la mémoire tampon audio
ItemCreateEnvoyer du texte ou une réponse d’outil
CreateResponseDemander une réponse
CancelResponseAnnuler la réponse actuelle
SessionUpdateMettre à jour la configuration

Vertex AI Live (Google Cloud)

Connectez-vous à Gemini Live via Vertex AI avec une authentification d’entreprise (ADC, comptes de service, 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(())
}

Il existe également un constructeur pratique pour ADC :

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

Vertex AI Live avec appel d’outils

L’exemple vertex_live_tools montre l’appel de fonctions via une session 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,
        _ => {}
    }
}

Indicateurs de fonctionnalité

FonctionnalitéDépendancesCas d’utilisation
vertex-livegemini + google-cloud-authVertex AI Live avec authentification ADC/par compte de service
livekitlivekit + livekit-apiPasserelle LiveKit WebRTC
openai-webrtcopenai + str0m + audiopusOpenAI WebRTC avec Opus (nécessite cmake)
fullopenai + gemini + vertex-live + livekitTous les transports sauf WebRTC
full-webrtcfull + openai-webrtcTout (nécessite cmake)

Pont LiveKit WebRTC

Pour les applications vocales de production, le pont LiveKit achemine l’audio via un serveur LiveKit pour les scénarios évolutifs impliquant plusieurs participants.

LiveKitConfig

Configurez de manière sécurisée les identifiants LiveKit. Les clés et secrets API sont stockés à l’aide de secrecy::SecretString et masqués dans la sortie de débogage :

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() valide le format URL et rejette les identifiants vides lors de la construction.

LiveKitRoomBuilder

Un générateur typestate pour se connecter aux salles LiveKit. Le champ identity est requis à la compilation — connect() n’est disponible qu’une fois celui-ci défini :

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;

Pontage audio

Utilisez les utilitaires de pont pour connecter l’audio LiveKit à 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));

Exemples

Exécutez les exemples inclus :

# 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

Bonnes pratiques

  1. Utilisez le VAD côté serveur : laissez le serveur gérer la détection de la parole afin de réduire la latence
  2. Gérez les interruptions : activez interrupt_response pour des conversations naturelles
  3. Gardez les instructions concises : les réponses vocales doivent être brèves
  4. Commencez par tester avec du texte : déboguez la logique de votre agent avec du texte avant d’ajouter l’audio
  5. Gérez les erreurs avec élégance : les problèmes réseau sont fréquents avec les connexions WebSocket

Comparaison avec les agents OpenAI SDK

L’implémentation temps réel de ADK-Rust suit le modèle des agents OpenAI SDK :

FonctionnalitéOpenAI SDKADK-Rust
Classe de base de l’agentAgenttrait Agent
Agent en temps réelRealtimeAgentRealtimeAgent
OutilsDéfinitions de fonctionsTool trait + ToolDefinition
Transfertstransfer_to_agentsub_agents + outil généré automatiquement
Fonctions de rappelPoints d’accrocheFonctions de rappel before_* / after_*

Précédent : ← Agents graphiques | Suivant : Fournisseurs de modèles →