रीयलटाइम आर्किटेक्चर
यह पृष्ठ समझाता है कि 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 │
└──────────────────────────────────────────────────────────────┘
RealtimeModel → RealtimeSession
एक 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.1 | 24 kHz | 24 kHz |
| Gemini Live | 16 kHz | 24 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 / SpeechStopped | VAD ने उपयोगकर्ता के बोलना शुरू/बंद करना पहचाना |
FunctionCallDone { name, arguments, call_id, .. } | मॉडल किसी टूल का उपयोग करना चाहता है |
ResponseDone { .. } | प्रतिक्रिया पूरी हुई |
TextDelta { delta, .. } | गैर-मौखिक पाठ (जैसे Gemini का "thinking") — आमतौर पर नहीं दिखाया जाता |
Error { error, .. } | प्रदाता त्रुटि |
#[non_exhaustive]:ServerEventका मिलान करते समय हमेशा एक_ => {}आर्म शामिल करें।
अगला: प्रदाता →