实时架构

本页面介绍 ADK-Rust 中实时会话的实际工作方式——各层、事件循环、音频管道以及轮次生命周期。理解这些内容后,本节的其余部分(工具、多模态、记忆)就会变得显而易见。

四个层级

┌──────────────────────────────────────────────────────────────┐
│ IntegratedRealtimeRunner   (feature: integration)             │
│  • SessionService  → persists each completed turn             │
│  • MemoryService   → profile-card injection + turn storage    │
│  • EnhancedPluginManager → before/after-tool hooks            │
│  • ADK-tool bridge → run any `adk_core::Tool` in a session    │
└───────────────┬──────────────────────────────────────────────┘
                │ wraps
┌───────────────▼──────────────────────────────────────────────┐
│ RealtimeRunner                                                 │
│  • pulls ServerEvents from the session                         │
│  • on FunctionCallDone → executes the tool handler             │
│  • sends the tool result back, triggers the spoken answer      │
└───────────────┬──────────────────────────────────────────────┘
                │ drives
┌───────────────▼──────────────────────────────────────────────┐
│ RealtimeSession   (the live transport — a WebSocket)           │
│  send_audio / send_text / send_video_frame / send_tool_output  │
│  next_event() → ServerEvent stream                             │
└───────────────┬──────────────────────────────────────────────┘
                │ created by connect()
┌───────────────▼──────────────────────────────────────────────┐
│ RealtimeModel    OpenAIRealtimeModel | GeminiRealtimeModel     │
└──────────────────────────────────────────────────────────────┘

RealtimeModelRealtimeSession

RealtimeModel 是一个轻量级工厂。OpenAIRealtimeModel::new(api_key, model_id)GeminiRealtimeModel::new(GeminiLiveBackend::studio(api_key), model_id) 可以创建一个;BoxedModel 只是 Arc<dyn RealtimeModel>。调用 connect() 会打开 WebSocket 并返回一个 RealtimeSession——真正负责与提供商线协议通信的对象。你很少会直接操作会话;运行器会持有它。它的接口很小,并且与提供商无关:

trait RealtimeSession {
    async fn send_audio_base64(&self, audio: &str) -> Result<()>;
    async fn send_text(&self, text: &str) -> Result<()>;
    async fn send_video_frame(&self, mime: &str, data_b64: &str) -> Result<()>;
    async fn send_tool_output(&self, response: ToolResponse) -> Result<()>;
    async fn create_response(&self) -> Result<()>;
    async fn next_event(&self) -> Option<Result<ServerEvent>>;
    async fn close(&self) -> Result<()>;
    // …commit/clear audio, interrupt, mutate_context
}

每个提供商在底层的实现方式都不同(OpenAI 的 input_audio_buffer.append 与 Gemini 的 realtimeInput),但上层的运行器并不关心这些。

RealtimeRunner

持有会话并运行事件循环。它的主要职责是工具调度:当收到 ServerEvent::FunctionCallDone 时,它会查找你的处理程序、运行该处理程序,并将结果发送回去(参见工具)。它公开会话的各项操作以及工具注册功能:

runner.connect().await?;
runner.send_audio(pcm16_base64).await?;     // mic frames
runner.send_text("…").await?;               // typed input
runner.send_video_frame("image/jpeg", b64).await?;
runner.create_response().await?;            // trigger a response to text input
let ev = runner.next_event().await;         // pull the next ServerEvent
runner.close().await?;

IntegratedRealtimeRunner

应用层。它包装 RealtimeRunner,并随着事件流动接入以下功能:

  • SessionService——已完成的轮次会追加到会话历史记录中。
  • MemoryService——连接时查询(用于上下文),并按轮次写入(可配置)。参见记忆
  • EnhancedPluginManager——工具调用会经过 before_tool_call / after_tool_call 钩子。
  • ADK 工具桥接——.adk_tool(Arc<dyn Tool>) 允许任何普通的 adk_core::Tool(例如 adk-tool 的内置工具)通过一个针对会话身份创建的合成 ToolContext 在实时会话中运行。

你可以使用类型化构建器来创建它:

let runner = IntegratedRealtimeRunner::builder()
    .model(model)
    .config(config)
    .identity("app", "user", "session-id")   // required
    .session_service(sessions)                // optional
    .memory_service(memory)                   // optional
    .integration_config(IntegrationConfig::default())
    .tool(weather_def(), weather_handler())   // native realtime ToolHandler
    .adk_tool(Arc::new(remember_tool))        // bridged adk_core::Tool
    .build()?;

IntegrationConfig 控制自动行为:

IntegrationConfig {
    persist_transcripts: true,    // append turns to the session
    store_to_memory: true,        // memory_service.add_session per turn
    inject_memory_context: true,  // query memory at connect
    max_memory_injection: 10,
}

服务器端桥接(Web 应用)

浏览器无法安全地持有提供商 WebSocket — —否则你的 API 密钥会泄露,而音频/事件处理也应归属于服务器端。因此,推荐的拓扑结构是服务器端桥接:浏览器是一个精简的音频/视频设备,而你的 Rust 服务器负责实时会话。

  browser ──mic PCM16 + camera JPEG (base64 over your WS)──▶  your Axum /ws
  browser ◀──agent PCM16 + transcripts + tool events──────    IntegratedRealtimeRunner ──▶ provider

API 密钥永远不会到达浏览器;工具在你的服务器上运行。本节中的所有 Web 示例都采用这种模式 — —完整协议和 Web Audio 代码请参阅构建 Web 应用

音频管道

实时音频是原始 PCM16、单声道、小端序 — —不使用容器。唯一有所不同的是采样率,并且采样率会根据提供商和方向分别变化

提供商输入(麦克风 → 模型)输出(模型 → 您)
OpenAI gpt-realtime-2.124 kHz24 kHz
Gemini Live16 kHz24 kHz

由于速率不同,桥接程序会在任何音频流动之前将速率协商给浏览器(示例会发送带有 input_rate/output_rateready 消息,浏览器则会以这些速率创建其捕获/播放 AudioContext)。 音频会以 base64 编码形式通过你的 WebSocket;ServerEvent::AudioDelta 携带解码后的 PCM16 字节,你需要重新编码这些字节,以便浏览器无间隙地播放。

轮次生命周期

“轮次”是一次交互。使用服务器端 VAD时,提供方会检测语音边界并自动响应;对于音频,你无需调用 create_response()。一次典型的语音轮次会产生以下事件序列:

SpeechStarted                 → user began talking (flush any playing audio = barge-in)
InputTranscriptDelta…         → live transcript of what the user is saying
SpeechStopped                 → user finished
  (model thinks)
TranscriptDelta…              → the agent's spoken answer, as text
AudioDelta…                   → the agent's spoken answer, as PCM16
ResponseDone                  → turn complete

对于文本输入(聊天框),没有 VAD 触发器,因此你必须在 send_text() 之后调用 create_response(),请求模型进行回复。

工具轮次跨越两个响应

当模型调用工具时,轮次会更长:

(maybe a short spoken preamble) + FunctionCallDone(name, args)
ResponseDone                  ← the "dispatch" response ends here
  → runner executes your handler, sends the result back,
    and triggers ONE follow-up response
TranscriptDelta… / AudioDelta…  ← the spoken answer using the tool result
ResponseDone                  ← turn truly complete

因此,只有在收到一个不包含工具调用ResponseDone 后,界面才应将轮次视为已完成。即使同时调用了多个工具,ADK-Rust 也会在每个轮次中恰好发出一次后续 response.create——请参阅 工具

你将处理的服务器事件

ServerEvent 是运行器生成的与提供方无关的事件枚举。你通常会渲染的事件包括:

事件含义
AudioDelta { delta, .. }代理语音的 PCM16 字节——播放它们
TranscriptDelta { delta, .. }代理的语音回答,以文本形式表示
InputTranscriptDelta { delta, .. }用户语音的实时转录(流式传输)
InputTranscriptCompleted { transcript, .. }最终用户转录(OpenAI 发送一次)
SpeechStarted / SpeechStoppedVAD 检测到用户开始/停止说话
FunctionCallDone { name, arguments, call_id, .. }模型需要使用工具
ResponseDone { .. }响应已完成
TextDelta { delta, .. }非语音文本(例如 Gemini 的“思考”)——通常不显示
Error { error, .. }提供商错误

#[non_exhaustive]:匹配 ServerEvent 时始终包含一个 _ => {} 分支。

下一步:提供商 →

实时架构 - ADK-Rust 文档 | ADK-Rust