Realtime & Multimodal Agents

Build agents that talk, listen, see, and act in real time — over a live, low-latency connection to OpenAI's Realtime API or Google's Gemini Live API.

A realtime agent isn't a request/response chatbot. It holds an open session: the user's microphone streams up continuously, the model streams audio + a live transcript back, it detects when the user starts and stops speaking, it can be interrupted mid-sentence (barge-in), it can see frames from a camera, and it can call your tools and speak the result — all without ever ending the "call."

New to ADK-Rust? Read the Introduction and Quickstart first. This section assumes you know what an agent, tool, and session are.

What you can build

CapabilityWhat it meansPage
Voice conversationsBidirectional PCM16 audio with VAD and barge-inArchitecture
Two providers, one APIOpenAI Realtime (GA) or Gemini Live, swappable per sessionProviders
Server-side toolsThe model calls process_refund(...); your Rust runs it and the model speaks the resultTools
Multimodal visionStream camera frames — the agent sees what the user shows itMultimodal
Affective dialogueThe agent reads the user's emotional tone and adapts (Gemini native-audio)Affective dialogue
Long-horizon memoryA knowledge graph the agent reads at session start and curates as it learnsMemory
Web appsA browser front-end (mic + camera) bridged through your serverBuilding web apps

Then see the four runnable example apps.

The mental model

There are two layers, and most applications use the top one:

  IntegratedRealtimeRunner          ← sessions, memory, tools, plugins (use this)
        │ wraps
  RealtimeRunner                    ← event loop + tool dispatch
        │ drives
  RealtimeSession  (a transport)    ← the live WebSocket to the provider
        │ created by
  RealtimeModel    (OpenAI | Gemini)
  • A RealtimeModel (e.g. OpenAIRealtimeModel, GeminiRealtimeModel) knows how to connect() and produce a RealtimeSession — the live transport.
  • RealtimeRunner owns that session: it pumps events, and when the model asks for a tool it executes the handler and sends the result back.
  • IntegratedRealtimeRunner wraps the runner and connects it to the rest of ADK — SessionService (transcript persistence), MemoryService (recall + storage), EnhancedPluginManager (hooks), and a bridge that lets any adk-core Tool run in a realtime session. This is what the examples use.

(There is also a higher-level RealtimeAgent for simple voice-only agents, and direct transports — WebRTC, LiveKit, Vertex AI Live — documented there.)

Install

# Voice + tools + the integration layer (recommended)
adk-realtime = { version = "2.0.0", features = ["openai", "gemini", "integration"] }
FeatureAdds
openaiOpenAI Realtime (WebSocket)
geminiGemini Live (WebSocket)
integrationIntegratedRealtimeRunner — sessions, memory, plugins, tool bridge
openai-webrtcOpenAI over WebRTC (needs cmake)
vertex-liveGemini via Vertex AI Live (OAuth2 / ADC)
livekitLiveKit WebRTC bridge

60-second quick start

A headless voice turn: connect, ask by text, and pump the streamed reply.

use adk_realtime::config::{RealtimeConfig, VadConfig};
use adk_realtime::events::ServerEvent;
use adk_realtime::integration::{IntegratedRealtimeRunner, IntegrationConfig};
use adk_realtime::model::BoxedModel;
use adk_realtime::openai::OpenAIRealtimeModel;
use adk_session::{CreateRequest, InMemorySessionService, SessionService};
use std::sync::Arc;

# async fn run() -> anyhow::Result<()> {
let model: BoxedModel = Arc::new(OpenAIRealtimeModel::new(
    std::env::var("OPENAI_API_KEY")?,
    "gpt-realtime",
));

let config = RealtimeConfig::default()
    .with_instruction("You are a friendly assistant. Keep replies short.")
    .with_voice("marin")
    .with_audio_only()
    .with_vad(VadConfig::server_vad())   // model decides turn boundaries
    .with_transcription();               // emit a text transcript too

let sessions = Arc::new(InMemorySessionService::new());
sessions.create(CreateRequest {
    app_name: "demo".into(), user_id: "u1".into(),
    session_id: Some("s1".into()), state: Default::default(),
}).await?;

let runner = IntegratedRealtimeRunner::builder()
    .model(model)
    .config(config)
    .identity("demo", "u1", "s1")
    .session_service(sessions)
    .integration_config(IntegrationConfig::default())
    .build()?;

runner.connect().await?;
runner.send_text("Say hello in one short sentence.").await?;
runner.create_response().await?;       // text input needs an explicit trigger

while let Some(event) = runner.next_event().await {
    match event? {
        ServerEvent::TranscriptDelta { delta, .. } => print!("{delta}"),
        ServerEvent::AudioDelta { .. } => { /* PCM16 to play */ }
        ServerEvent::ResponseDone { .. } => break,
        _ => {}
    }
}
runner.close().await?;
# Ok(()) }

In a real app you stream microphone audio with runner.send_audio(base64_pcm16) instead of send_text, and server VAD drives responses automatically — no create_response() needed. See Building web apps.

Where to go next

  1. Architecture — how the pieces fit, the event loop, the audio pipeline.
  2. Providers — OpenAI vs Gemini models, voices, endpoints, auth.
  3. Tools — server-side function calling.
  4. Multimodal — sending video frames.
  5. Affective dialogue — emotion-aware responses.
  6. Memory — knowledge-graph long-term memory.
  7. Building web apps — the browser bridge.
  8. Examples — four runnable apps.