रीयलटाइम आर्किटेक्चर

यह पृष्ठ समझाता है कि 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,
}

सर्वर-साइड ब्रिज (वेब ऐप्स)

ब्राउज़र प्रदाता 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 Audio कोड के लिए वेब ऐप्स बनाना देखें।

ऑडियो पाइपलाइन

रीयलटाइम ऑडियो रॉ PCM16, मोनो, लिटिल-एंडियन है — इसमें कोई कंटेनर नहीं होता। केवल सैंपल दर बदलती है, और यह प्रति प्रदाता तथा प्रति दिशा अलग-अलग होती है:

प्रदाताइनपुट (माइक्रोफ़ोन → मॉडल)आउटपुट (मॉडल → आप)
OpenAI gpt-realtime-2.124 kHz24 kHz
Gemini Live16 kHz24 kHz

चूंकि दरें अलग-अलग होती हैं, इसलिए कोई भी ऑडियो प्रवाहित होने से पहले ब्रिज उन्हें ब्राउज़र के साथ समन्वित करता है (उदाहरण ready संदेश को input_rate/output_rate के साथ भेजते हैं, और ब्राउज़र उन्हीं दरों पर अपने कैप्चर/प्लेबैक AudioContext बनाता है)। ऑडियो आपके WebSocket से होकर base64-एन्कोडेड रूप में गुजरता है; 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

इसीलिए UI को टर्न समाप्त तभी मानना चाहिए जब उसे ऐसा ResponseDone मिले जिसमें टूल कॉल शामिल न हो। ADK-Rust प्रति टर्न ठीक एक अनुवर्ती response.create जारी करता है, भले ही एक साथ कई टूल कॉल किए गए हों — टूल देखें।

आपके द्वारा संभाले जाने वाले सर्वर इवेंट

ServerEvent प्रदाता-निरपेक्ष इवेंट enum है जिसे रनर उत्पन्न करता है। आमतौर पर रेंडर किए जाने वाले इवेंट:

घटनाअर्थ
AudioDelta { delta, .. }एजेंट के भाषण की PCM16 बाइट्स — इन्हें चलाएँ
TranscriptDelta { delta, .. }एजेंट के बोले गए उत्तर का पाठ
InputTranscriptDelta { delta, .. }उपयोगकर्ता के भाषण का लाइव प्रतिलेख (स्ट्रीम किया गया)
InputTranscriptCompleted { transcript, .. }उपयोगकर्ता का अंतिम प्रतिलेख (OpenAI इसे भेजता है)
SpeechStarted / SpeechStoppedVAD ने उपयोगकर्ता के बोलना शुरू/बंद करना पहचाना
FunctionCallDone { name, arguments, call_id, .. }मॉडल किसी टूल का उपयोग करना चाहता है
ResponseDone { .. }प्रतिक्रिया पूरी हुई
TextDelta { delta, .. }गैर-मौखिक पाठ (जैसे Gemini का "thinking") — आमतौर पर नहीं दिखाया जाता
Error { error, .. }प्रदाता त्रुटि

#[non_exhaustive]: ServerEvent का मिलान करते समय हमेशा एक _ => {} आर्म शामिल करें।

अगला: प्रदाता →