Realtime Mode Setup Guide
View SourceThis guide explains how to implement realtime voice sessions with Synaptic for both providers.
Realtime mode is best when you want:
- low-latency conversational transport
- streaming interaction
- provider-native realtime capabilities
1. Provider architecture differences
OpenAI realtime:
- client-direct WebRTC to provider
- recommended versioned Realtime 2.1 native conversation
- backward-compatible legacy orchestration for unversioned sessions
- optional server-side workflow tools through Synaptic and GPT-5.6 Luna
- transport payload includes provider bootstrap fields (
client_secret, etc.)
Gemini realtime:
- server holds provider Live WebSocket
- client talks to your server only
- your app relays audio/text events to Synaptic session
Do not assume transport semantics are interchangeable between providers.
2. Prerequisites
Server:
OPENAI_API_KEYand/orGEMINI_API_KEY- voice provider config with realtime model/voice
- optional
OPENAI_REALTIME_2_1_MODELfor the new experience - optional
OPENAI_REALTIME_MODELfor legacy sessions - optional
OPENAI_REALTIME_EXPERIENCE(legacyorrealtime_2_1) - optional
OPENAI_REALTIME_RESPONSE_MODEfor legacy response behavior
Client:
- for OpenAI: WebRTC support
- for Gemini: your own socket/event transport to your backend
3. Session bootstrap
Server creates session:
{:ok, %{session_id: session_id, run_id: run_id, transport: transport}} =
Synaptic.Voice.start_session(MyWorkflow, %{},
provider: :openai, # or :gemini
mode: :realtime,
keep_alive: true,
experience: :realtime_2_1,
profile: MyApp.Voice.AssistantProfile,
session_context: %{customer_name: "Maya", timezone: "Europe/Warsaw"},
session_authorization: %{capabilities: :all, scopes: ["calendar:read"]}
)For OpenAI, profile: implies experience: :realtime_2_1; keeping the
experience explicit makes migration intent visible. Without either option,
Synaptic preserves the legacy orchestration contract. For application
assistants, define a profile so Realtime sees the exact persona, capabilities,
limitations, and safe session context. See
../voice-profiles.md.
Then:
- subscribe to
session_idevents - subscribe to
run_idevents - return
transportand IDs to client
4. OpenAI realtime integration flow
- client receives transport bootstrap
- client uses the short-lived
client_secret.valueto open a WebRTC session againstPOST /v1/realtime/calls - when provider sends client-side events that need orchestration, your backend calls:
Synaptic.Voice.client_connected(session_id)Synaptic.Voice.ingest_provider_event(session_id, payload)Synaptic.Voice.client_disconnected(session_id)
- backend emits
:provider_outboundevents; client forwards those to provider data channel
You should not proxy raw audio through your backend in this mode unless you intentionally design a relay path.
The Realtime 2.1 experience mints its short-lived browser credential through
OpenAI's GA POST /v1/realtime/client_secrets endpoint. The server API key
never enters the transport payload. Legacy sessions retain the original
ephemeral-session bootstrap contract.
For model and reasoning experiments, pass realtime provider options:
Synaptic.Voice.start_session(MyWorkflow, %{},
provider: :openai,
mode: :realtime,
experience: :realtime_2_1,
provider_opts: [
realtime: [model: "gpt-realtime-2.1-mini", reasoning_effort: "low"]
]
)Choose the response policy independently:
Synaptic.Voice.start_session(MyWorkflow, %{},
provider: :openai,
mode: :realtime,
experience: :realtime_2_1,
response_mode: :native
):native: Realtime 2.1 responds directly with Marin, semantic VAD at low eagerness, near-field noise reduction,gpt-realtime-whisper, and low reasoning. It calls only the capabilities authorized for this session, then naturally voices the returned result.:orchestrated: Synaptic resumes the workflow for every final transcript and instructs Realtime to speak the prepared answer. It remains available as a deterministic fallback and A/B baseline.
5. Compatibility and migration
Experience selection is deterministic:
- Explicit
experience:wins. - A non-nil
profile:selects:realtime_2_1. - An application-level
default_experience: :realtime_2_1selects the new experience globally. - Otherwise the session uses
:legacy.
Legacy sessions retain orchestrated conversation ownership, configured legacy
model/voice defaults, server_vad, workflow-on-every-turn behavior, and the
original create_ephemeral_session/1 request and response contract. Explicit
session options continue to win over experience defaults.
Recommended incremental migration:
- Upgrade without changing existing calls.
- Add
experience: :realtime_2_1to one controlled session path. - Add a profile, capability authorization, and session context.
- Compare native and
response_mode: :orchestratedbehavior. - Set
default_experience: :realtime_2_1only after all unversioned callers have been reviewed.
To verify credentials, model access, and the GA client-secret request without starting a browser or consuming audio, run the opt-in live smoke check:
OPENAI_API_KEY=... mix run scripts/openai_realtime_smoke.exs
# Optional experiment overrides
OPENAI_REALTIME_2_1_MODEL=gpt-realtime-2.1-mini \
OPENAI_REALTIME_REASONING_EFFORT=low \
OPENAI_API_KEY=... mix run scripts/openai_realtime_smoke.exs
The script prints session metadata but never prints the short-lived client secret.
6. Gemini realtime integration flow
- backend owns Live connection lifecycle
- client streams/turns to backend
- backend forwards to Synaptic session:
push_audio(session_id, chunk, opts)push_text(session_id, text, opts)end_turn(session_id, opts)
- backend forwards normalized assistant events back to client
This is effectively realtime relay with Synaptic normalizing provider behavior.
7. Workflow requirements for realtime
In native mode, design the workflow as an on-demand tool. Configure
gpt-5.6-luna explicitly for Luna-backed workflow reasoning. In orchestrated
mode, design the workflow for repeated speech turns:
- suspend steps expecting user input should declare explicit
resume_schema - workflow should map resumed transcript into a stable context key
- workflow should produce assistant answer text for provider speech output
- for multi-turn conversations, re-enter a suspend point after response
Recommended pattern:
- router step -> tool/LLM steps -> assistant answer step -> wait-for-human step -> loop
8. Required event handling
Consume and route these events consistently:
:session_started,:session_stopped,:session_error:input_partial_text,:input_final_text:assistant_text_chunk,:assistant_response_done:assistant_audio_chunk,:assistant_audio_done(where applicable):provider_outbound(critical for OpenAI sideband)
Treat event sequence ordering as authoritative over local UI heuristics.
9. Failure handling
OpenAI realtime:
- if WebRTC setup fails, surface connect error and allow reconnect
- handle provider-side disconnects by stopping/cleaning session state
Gemini realtime:
- handle Live socket failure with automatic reconnect policy on backend
- if relay fails, keep workflow run recoverable where possible
General:
- classify
session_errorbysource(:workflow,:stt,:tts,:resume) - avoid hard session teardown on recoverable errors
10. Security and boundaries
- never expose server API keys to client
- OpenAI realtime bootstrap artifacts should be short-lived and session-scoped
- sanitize all client log/event payloads before appending or atomizing
- enforce per-session auth/ownership at your app boundary
11. Verification checklist
OpenAI realtime:
- connect voice session via WebRTC
- send/receive sideband events
- verify
provider_outboundforwarding - disconnect and reconnect cleanly
Gemini realtime:
- send text and audio turns through relay
- verify assistant text/audio events
- verify multi-turn continuity
- verify relay reconnect behavior
Both providers:
- workflow suspend/resume loops correctly across turns
- session errors are user-visible and recoverable
- session cleanup stops background resources
11. Reference implementation
Internal sample app:
tmp/voice_lab
Relevant code:
- realtime sessions:
lib/synaptic/voice/sessions/realtime/open_ai.exlib/synaptic/voice/sessions/realtime/gemini.ex
- provider realtime helpers:
lib/synaptic/voice/providers/open_ai/realtime/*lib/synaptic/voice/providers/gemini/live/*
- UI bridge:
tmp/voice_lab/lib/voice_lab_web/live/home_live.extmp/voice_lab/assets/js/app.js
Frontend-focused companion guide: