Evalgent
Back to Blog
Voice AI Evaluation

How to Evaluate a Realtime Voice API: GPT-Live, Gemini Live, Grok, Deepgram, ElevenLabs Compared

Deepesh Jayal
Updated
25 min read
How to Evaluate a Realtime Voice API: GPT-Live, Gemini Live, Grok, Deepgram, ElevenLabs Compared
On this page

Most comparisons of realtime voice APIs grade the voice. This guide grades the plumbing. Two APIs can sit on equally good models and still behave very differently on a phone line, because one lets you tell the model which words the caller never heard and the other does not. One bills a flat rate per minute. The other re-bills the whole conversation on every turn, so minute nine costs more than minute one.

We read the current docs for five APIs as of October 2026: OpenAI GPT-Live (`gpt-live-1`) and the OpenAI Realtime API (`gpt-realtime-2.1`), Google's Gemini Live API (`gemini-3.8-live`), xAI's Grok Voice API (`grok-voice-think-fast-2.0`), Deepgram's Voice Agent API, and ElevenLabs Agents. Then we turned the findings into a checklist and a harness that measures each one the same way. For output quality, see our guide to evaluating a speech-to-speech model.

$0.05/min
GPT-Live voice time, billed per second (OpenAI model page)
15 min
Gemini Live audio-only session cap without compression (Google docs)
0.85
Default Grok Voice VAD threshold, vs 0.5 in OpenAI's example (xAI, OpenAI docs)

What evaluating a realtime voice API actually means

A realtime voice API is a long-lived, stateful connection that carries audio both ways and a stream of control events alongside it. You are not scoring a request and a response. You are scoring a session that has to survive pauses, interruptions, tool calls, network loss, and a context window that fills up.

In 2026 these APIs fall into four protocol families. The family decides what you can control and what you can measure.

FamilyAPIsWho decides when the agent speaksWhat you control
Turn-based event protocolOpenAI Realtime, xAI Grok VoiceServer VAD or your `response.create`VAD thresholds, response creation, conversation items, truncation
Full-duplex with delegationOpenAI GPT-LiveThe voice model itselfInstructions, context appends, delegated backend work
Bidirectional generate streamGemini Live APIServer activity detection or your `activityStart` / `activityEnd`VAD sensitivity, manual activity signals, compression, resumption
Managed pipeline agentDeepgram Voice Agent, ElevenLabs AgentsThe platform's turn detectorListen, think, and speak providers, prompts, tools

The family matters for testing. xAI says most OpenAI Realtime client libraries work against its API by changing the base URL, so one adapter covers both. GPT-Live looks similar but does not use Realtime's input-buffer commit and `response.create` turn loop. Moving a Realtime integration to GPT-Live means a rewrite, which our GPT-Live build guide walks through.

The mechanics matrix: five APIs side by side

Every cell comes from current vendor docs. Where a doc is silent, treat that as a test item, not a pass.

MechanicGPT-Live (`gpt-live-1`)OpenAI Realtime (`gpt-realtime-2.1`)Gemini Live (`gemini-3.8-live`)Grok VoiceDeepgram Voice AgentElevenLabs Agents
TransportWebRTC, WebSocket, SIP, sideband WebSocketWebRTC, WebSocket, SIP, sidebandWebSocketWebSocket, WebRTC, SIP, LiveKitWebSocketWebSocket, WebRTC (via LiveKit), SIP trunk
Audio inPCM16 24 kHz or 16 kHz, G.711 u-law or A-lawPCM16, G.711PCM16, natively 16 kHz, rate set in MIME typePCM at 8 to 48 kHz, G.711, Opus`linear16` and others, set in `Settings`Format reported in `conversation_initiation_metadata`
Audio outSame format as input, fixed at startConfigurablePCM16 at 24 kHz, alwaysPCM at 8 to 48 kHz, G.711, OpusSet in `Settings`Example shows `pcm_44100`
Turn detectionModel-owned, no VAD knobs`server_vad` or `semantic_vad`Automatic with sensitivities, or manual`server_vad` or `null`Platform detector (Flux has built-in end of turn)Platform detector, `vad_score` events
Remove unheard audio from contextNot supported; discarded speech stays in context`conversation.item.truncate`, or `output_audio_buffer.clear` on WebRTC and SIPNo documented truncate event`conversation.item.truncate`Not documented`agent_response_correction` reports truncated text
Non-blocking toolsDelegation; backend work continues across interruptionsFunction calls inside responses`NON_BLOCKING` with `INTERRUPT`, `WHEN_IDLE`, `SILENT` (not on 3.1 Flash Live)Function calls; result after `response.done`Function call request and response, cancel eventClient tools, webhook tools, tool interruption settings
Session limitEnds with reason `expired`; number not in the docs we read60 minutes15 min audio-only, connection about 10 minResumption history dropped after 30 min idleBilled per connection timeBilled per connection time
Billing unit$0.05 per minute plus backend tokensTokens: audio in $32/M, out $64/MTokens: audio in $3/M, out $12/M$0.08 per minute$0.075 per minute (Standard, pay as you go)$0.08 per minute example rate plus LLM
Concurrency25 (Tier 1) to 500 (Tier 5) sessionsRPM and TPM by tierPer project, by tierNot documented on the page we read45 pay as you go, 60 GrowthPlan limit, burst up to 3x or 300

Sources: OpenAI's GPT-Live model page, session guide, Realtime guide, and `gpt-realtime-2.1` page; Google's Live API capabilities, session management, and pricing; xAI's Speech to Speech guide and launch post; Deepgram's pricing and message flow; ElevenLabs' event reference. All as of October 2, 2026.

Transport: what changes between WebRTC, WebSocket, and SIP

Transport decides who knows what the caller heard, how audio survives packet loss, and how much bandwidth each call burns.

WebSocket runs over TCP. TCP delivers bytes in order. When a packet is lost, everything behind it waits for the retransmit. That is head-of-line blocking. On a clean server-to-server link it rarely matters. On a lossy last mile, one lost packet stalls a run of audio frames, and then they arrive in a burst. Most WebSocket voice APIs also wrap audio in JSON as base64, which inflates bytes by 4/3. So 24 kHz PCM16 (384 kbps raw) becomes about 512 kbps before JSON overhead. G.711 at 8 kHz is 64 kbps raw. Deepgram sends raw binary frames, and Grok Voice offers a `binary` transport option that does the same.

WebRTC runs media over UDP with a jitter buffer and usually Opus. A late packet gets concealed or dropped instead of blocking the rest. The bigger difference for evaluation is that the server owns the playback path. OpenAI's Realtime docs say that on WebRTC and SIP, `output_audio_buffer.clear` stops playback and truncates the conversation for you. On WebSocket, the server cannot know what your speaker played, so you must tell it.

SIP connects a phone call directly. Media is narrowband G.711 at 8 kHz. Every API that accepts 8 kHz input either works natively in it or resamples. Resampling 8 kHz up to 24 kHz adds no information. Deepgram's own guidance says 16 kHz is the sweet spot for its models and that upsampling band-limited 8 kHz telephony audio gives no benefit. So test phone traffic at 8 kHz, not with clean wideband files.

Benchmark the transport you will ship. Our SIP vs WebRTC guide covers the network side in more depth.

Event protocols: the same turn in five dialects

The cleanest way to compare protocols is to trace one turn: caller speaks, agent answers, caller barges in. Here are the event names each API documents for that sequence.

StepOpenAI Realtime and Grok VoiceGPT-LiveGemini LiveDeepgram Voice AgentElevenLabs Agents
Configure`session.update``session.start` (most fields fixed)Setup config at connect`Settings`, then wait for `SettingsApplied`Agent config; `conversation_initiation_metadata` returned
Send audio`input_audio_buffer.append``session.input_audio.append``send_realtime_input` with an audio blobBinary frames`user_audio_chunk`
Speech start`input_audio_buffer.speech_started`Transcript deltas onlyHandled server-side`UserStartedSpeaking``vad_score` stream
Agent audio`response.output_audio.delta``session.output_audio.delta``server_content` audio partsBinary frames`audio` with character alignment
Turn or reply done`response.done`No per-reply done event`turn_complete`, `generation_complete``AgentAudioDone``agent_response`
Barge-in signal`speech_started` plus `response.cancel`Model handles it; no signal`server_content.interrupted``UserStartedSpeaking``agent_response_correction`
Comparison of the interruption signal each of five realtime voice APIs emits during a barge-in, and whether each removes unheard agent audio from model context

Three protocol details change how you build the harness.

First, GPT-Live has no reply boundary. Its WebSocket guide says output audio events carry no timing fields and there is no output-audio-done event. Transcript deltas carry `start_ms` and `end_ms`, but the docs say these do not mark playback completion. Your harness has to track playback itself.

Second, Deepgram's `AgentAudioDone` means the server finished sending audio, not that the caller finished hearing it. If the client buffers two seconds, the event fires two seconds before the last syllable plays.

Third, Grok Voice changes one transcription event. OpenAI's `conversation.item.input_audio_transcription.delta` becomes `conversation.item.input_audio_transcription.updated` in xAI's API, and it carries the full transcript so far, with corrections, instead of a delta. xAI also documents that `rate_limits.updated` is not emitted. A harness that reads rate-limit headroom from that event will log nothing on Grok.

Turn detection: what each API lets you tune

Turn detection decides two failure modes. Fire too early and the agent talks over a caller who paused to think. Fire too late and the caller waits in silence. Human conversation sets a hard bar. Stivers and colleagues measured turn transitions across 10 languages and found the most common gap fell between 0 and 200 ms in every one (PNAS, 2009). People plan their reply before the other speaker finishes. A pure silence timer cannot do that. It has to wait for the silence it is timing.

That is why the research moved toward prediction. Ekstedt and Skantze's Voice Activity Projection model predicts upcoming voice activity for both speakers from the raw audio stream, instead of waiting for silence. Semantic VAD and model-owned turn-taking are the commercial versions of that idea. Kyutai's Moshi paper goes further. It models the user's and the system's speech as parallel streams, with no explicit speaker turns, at a theoretical latency of 160 ms (200 ms in practice). That design is why full-duplex APIs like GPT-Live expose no VAD knobs at all.

Here is what each API exposes.

APIModeKnobs and defaults
OpenAI Realtime`server_vad` (default)`threshold` 0 to 1 (example 0.5), `prefix_padding_ms` (example 300), `silence_duration_ms` (example 500), `create_response`, `interrupt_response`
OpenAI Realtime`semantic_vad``eagerness`: `low`, `medium`, `high`, `auto` (`auto` equals `medium`)
Grok Voice`server_vad``threshold` 0.1 to 0.9, default 0.85; `prefix_padding_ms` default 333; `silence_duration_ms` 0 to 10,000; `idle_timeout_ms` re-engages a silent caller
Gemini LiveAutomatic`start_of_speech_sensitivity`, `end_of_speech_sensitivity`, `prefix_padding_ms`, `silence_duration_ms`
Gemini LiveManual or hybridDisable automatic detection and send `activityStart` and `activityEnd`, or keep it and send `audio_stream_end` from a client VAD
GPT-LiveModel-ownedNo VAD settings; the model decides when to listen and speak
Deepgram Voice AgentPlatformFlux emits `StartOfTurn` and `EndOfTurn` from the model; Deepgram recommends its model-level detection over an external VAD for barge-in
ElevenLabs AgentsPlatformTurn settings in agent config; `vad_score` events from 0 to 1

Sources: OpenAI's VAD guide, xAI's session parameters, Google's capabilities guide, and Deepgram's barge-in guide.

Two gotchas from these pages are worth testing directly.

A threshold number does not travel between vendors. Grok's default is 0.85 on a 0.1 to 0.9 scale. OpenAI's example is 0.5 on a 0 to 1 scale. They are different detectors, so calibrate each one by sweeping noise levels and logging false triggers.

Manual VAD on Gemini removes two safety nets. With automatic detection disabled, the server stops prepending pre-speech audio and acts on `activityEnd` at once, with no silence tolerance. In manual mode you are testing your VAD, not Google's.

Research confirms that these behaviors need direct measurement. Full-Duplex-Bench defines a Takeover Rate (TOR), the share of cases where the model takes the turn. Lower is better while the caller pauses. Higher is better once the caller has finished. In its turn-taking test on the Candor corpus, Gemini Live scored a TOR of only 0.655, so it failed to take the turn in about a third of cases. The Talking Turns study (ICLR 2025), using a judge model trained on human conversation, found that spoken dialogue systems sometimes miss when to speak, interrupt too aggressively, and rarely backchannel. Our turn-taking evaluation guide turns these into test cases.

Interruption and truncation: does the model know what the caller heard?

This is the mechanic most comparisons skip, and it causes the strangest production bugs.

The server generates audio faster than real time and streams it ahead into your playout buffer. When the caller barges in at second 1.5 of a 4-second reply, the server has already produced all 4 seconds, and its history says the agent said all of it. Unless something corrects that history, the model believes the caller heard words they never heard. Two turns later it says, "As I mentioned, the fee is waived," and the caller is lost. In this illustrative case, 2.5 s of unheard speech, about 62% of the reply, sits in context as if spoken.

Each API handles this differently.

  • OpenAI Realtime gives you the fix, but only partly automatic. On WebSocket you send `conversation.item.truncate` with the item ID and `audio_end_ms`, the playback position. The server cuts the audio there and removes the unplayed transcript, but the docs say it cannot precisely align text and audio, so you get no truncated transcript back. On WebRTC and SIP, `output_audio_buffer.clear` stops playback and truncates for you.
  • Grok Voice documents `conversation.item.truncate`. Its API reference says it removes audio and transcript content after the specified duration. `output_audio_buffer.clear` works only on WebRTC and SIP.
  • GPT-Live does not support truncation. Its server-controls guide is explicit. If you withhold or cut audio, the discarded speech remains in the model's conversation context. You can stop playback at the client, but you cannot tell the model what was not heard.
  • Gemini Live sends `server_content.interrupted` and tells you to stop playback and clear the queue. It also cancels pending function calls and sends their IDs. We found no client event to report the playback position.
  • Deepgram tells you to stop playback the moment `UserStartedSpeaking` arrives. Its docs do not describe how the agent's history treats the unplayed part.
  • ElevenLabs is the only API here that tells the client what it corrected. `agent_response_correction` carries both the original response and the corrected one, truncated after the interruption. Our ElevenLabs testing guide shows how to assert on it.
Timeline of a barge-in showing generated audio, audio actually played to the caller, and what each API keeps in model context after the interruption

You can test this in a few minutes with what we call the phantom-reference probe. Script a reply that puts a unique fact at the end, such as "and your confirmation code is 4471." Interrupt during the first third of the sentence. Then ask, "What was the code you gave me?" A correct system says it did not finish giving one. A system with context drift recites 4471. Run it 50 times per vendor and transport. Report the phantom rate. Our interruption detection guide covers the detection side of the same event.

Function calling during audio: blocking vs non-blocking

The API question is what happens to the conversation while a tool runs, and when the caller interrupts.

  • Gemini Live has the most explicit model. Functions block by default. Mark one `NON_BLOCKING` and the conversation continues, then choose how the result lands: `INTERRUPT` (speak it now), `WHEN_IDLE` (after the current utterance), or `SILENT` (keep it for later). One catch: Google's tools page says async calling is not yet supported on Gemini 3.1 Flash Live, where the agent stays silent until your tool responds.
  • Grok Voice documents an ordering trap. During a spoken reply, the server sends all audio deltas first, then the function call events with `response.done`. Send the result and `response.create` at once and the next reply overlaps audio still playing. xAI's fix is to wait for playback to finish first. Our Grok Voice guide covers the rest of its setup.
  • GPT-Live separates voice from work. The voice model delegates to a backend that OpenAI or you run. Backend work can continue when the caller interrupts, and your application decides whether to cancel it. Test that an interrupted "cancel my order" does not complete anyway.
  • Deepgram exposes function call request and response messages and a Function Call Cancelled server event. ElevenLabs sends `client_tool_call` and expects `client_tool_result` with an `is_error` flag. It also documents tool interruption settings.

Test tools with injected delays of 0.5 s, 2 s, and 8 s, plus one that fails. Measure dead air, whether the agent fills it, and whether an interruption cancels the action. Our guide to silent tool failures lists the failure signatures.

Sessions, context limits, and what happens at minute 15

Long calls hit limits that differ in kind, not just size.

Gemini Live has the tightest documented ceilings: 15 minutes for audio-only sessions without compression, and about 10 minutes per connection. Context window compression (`contextWindowCompression` with a sliding window) extends the session. Session resumption sends `SessionResumptionUpdate` handles, valid for 2 hours, that you pass on the next connection. Before a connection drops, the server sends `GoAway` with `timeLeft`. Handle it, then measure the audio gap during reconnect.

OpenAI Realtime caps a session at 60 minutes. Inside that, `gpt-realtime-2.1` has a 128,000-token context window. When the conversation exceeds the input limit, the server drops the oldest messages. You can tune this with `truncation.retention_ratio` and `token_limits.post_instructions`, or set `truncation` to `"disabled"` and get an error instead.

GPT-Live manages context for you. The default window is 128,000 tokens, including audio tokens absent from the transcript. Past 90% usage, GPT-Live starts a replacement voice engine in the same session with your original instructions and up to 8,192 tokens of history, recent messages plus a summary. A fact from minute 3 may survive only as a summary line by minute 40, a case our GPT-Live testing guide builds tests for. `session.usage.updated` reports `context_window.usage_ratio`, so log when the swap happens. Sessions end with reason `expired` at a duration limit the docs we read do not publish. Measure it.

Grok Voice offers opt-in resumption. Set `resumption.enabled`, save the `conversation_id`, and pass it on reconnect. The server replays transcripts, tool calls, and your function results. History is dropped after 30 minutes of inactivity.

Deepgram and ElevenLabs bill on connection time, so a session left open after hangup costs money until your code closes it.

Test with a 45-minute scripted call that plants five facts in the first five minutes and asks for them at minutes 20, 30, and 40. Score recall per fact, and log every context event the API emits.

Pricing units converted to per-minute, with worked math

Three vendors bill per minute. Two bill per token. Per-token billing in a session API has a property per-minute billing does not: every new response re-reads the conversation so far.

OpenAI's cost guide states the rule. Audio tokens are 1 per 100 ms for user audio and 1 per 50 ms for assistant audio. The entire conversation is sent to the model for each response, so later turns cost more. Google's Agent Platform pricing page says the same for the Live API: past-turn tokens are re-processed and billed every turn, at 25 tokens per second of audio.

Here is one illustrative call, used for every vendor. It lasts 5 minutes and has 10 turns. Each turn has 12 s of caller speech and 15 s of agent speech. Instructions are 1,500 text tokens. LLM and telephony fees are excluded unless included in the vendor's rate.

OpenAI Realtime, `gpt-realtime-2.1`. Rates are $32 per million audio input tokens, $64 per million audio output tokens, $0.40 per million cached input, and $4 per million text input.

  • Caller audio per turn: 12 s x 10 tokens/s = 120 tokens. Agent audio per turn: 15 s x 20 tokens/s = 300 tokens.
  • Output: 10 turns x 300 x $64/M = $0.192.
  • Input, nothing cached: each response re-reads all prior turns at $32/M. Summed over 10 turns, that comes to $0.703. Total: $0.895, or $0.179 per minute.
  • Input, history fully cached: prior turns bill at $0.40/M and only new caller audio at $32/M. Input is $0.052. Total: $0.244, or $0.049 per minute.

The same call costs 3.7 times more when caching misses. OpenAI calls caching best-effort and says removing or changing conversation content busts the cache from that point on. So your interruption handling, which truncates items, directly affects cost. Read `cached_tokens` from each `response.done` in your harness.

Gemini Live, `gemini-3.8-live`. Audio is $3 per million input tokens and $12 per million output tokens, which Google lists as about $0.005 and $0.018 per minute. At 25 tokens per second with per-turn re-billing, the same call costs about $0.156, or $0.031 per minute. Without re-billing it would be $0.054. We found no Live API caching discount documented, so verify against the usage metadata the API returns.

Per-minute vendors on the same 5-minute call:

APIRate5-minute callWhat is not included
GPT-Live$0.05/min, billed per second$0.25Backend model and tools
OpenAI Realtime 2.1Tokens$0.24 to $0.90, depending on cacheInput transcription, reasoning tokens
Gemini 3.8 LiveTokensabout $0.16Search grounding beyond free quota
Grok Voice$0.08/min$0.40Telephony is +$0.01/min on an xAI number
Deepgram Voice Agent, Standard$0.075/min pay as you go$0.375Tier-dependent; check which LLMs the tier includes
ElevenLabs Agents$0.08/min example rate$0.40LLM passed through separately

Four billing footnotes change real bills:

  • GPT-Live bills silence and backend waiting as active time. Each WebRTC session bills 15 seconds at creation, credited once it runs, so reconnect storms pay it repeatedly.
  • ElevenLabs bills on connection duration, with a 95% discount on silences longer than 10 seconds. Deepgram also bills by connection time.
  • Gemini's free tier is marked "used to improve our products." The paid tier is not. Keep customer audio off free-tier keys.
  • A cheaper API that needs a second call to finish the task costs more per resolved call. Our hidden costs guide works through that math.

Rate limits and concurrency: what happens at the ceiling

Concurrency is where load tests mislead people, because each vendor fails differently at its limit.

  • GPT-Live limits concurrent sessions by tier: 25 at Tier 1, 50 at Tier 2, 200 at Tier 3, 300 at Tier 4, 500 at Tier 5. The Free tier is not supported.
  • OpenAI Realtime limits requests and tokens per minute. `gpt-realtime-2.1` lists 40,000 TPM at Tier 1. Because each response re-reads the conversation, a few long calls can exhaust a token budget sized for short ones. In our worked call, the last response alone reads about 3,900 audio tokens plus 1,500 instruction tokens.
  • Deepgram lists Voice Agent concurrency of up to 45 on pay as you go and 60 on Growth. When you exceed your limit, new connections get throttled with 429 responses. Our Deepgram load-testing guide covers the ramp.
  • ElevenLabs burst pricing lets an agent run up to 3 times its concurrency limit, capped at 300 for non-enterprise accounts, at double the rate. The docs add that burst calls get lower priority for speech-to-text and text-to-speech and may see higher latency. A load test above your plan limit measures that degraded tier. With call queueing on, a caller who times out gets a WebSocket close with code 4300.
  • Gemini applies limits per project, not per key. Developers on Google's forum report Live sessions closing with error 1011, "Resource has been exhausted," while the dashboard showed usage under the cap. Log close codes, not just HTTP errors.

At the ceiling, record which of four behaviors you get: clean rejection, queue, silent latency climb, or dropped live session. The silent latency climb is the worst, because nothing alerts.

Regions, storage, and data retention

Data handling can block an evaluation outright. GPT-Live storage defaults to off (`store: false`). With it on, recordings stay available for 30 days as stereo WAV, caller on the left channel and agent on the right. Zero Data Retention forces `store` off and disables forking, which is how OpenAI suggests running repeatable trials from one reference conversation. Deepgram offers an EU endpoint, `api.eu.deepgram.com`. xAI documents regional endpoints. ElevenLabs documents audio retention controls and an Enterprise Zero Retention Mode. Settle region and retention before any real customer audio enters the harness.

The realtime voice API evaluation checklist

Here are 24 items, from transport to data policy, each with a test and a pass bar. The thresholds are starting points. Tune them to your use case and fix them before you test any vendor.

#ItemHow to testPass bar (starting point)
1Transport matches productionRun on the transport you shipSame transport as production
2Native telephony codecSend G.711 at 8 kHzAccepted without your own resampling
3Audio format fixed or changeableTry a mid-session format changeDocumented behavior; rejection is fine
4Reconnect audio gapKill the socket at 2 and 9 minutesUnder 2 s of lost caller audio
5Client-side time to first audio, p50200 or more turns, clean networkYour target (for example, under 800 ms)
6Client-side time to first audio, p95Same turnsUnder 1.5x your p50 target
7Latency under 3% packet lossImpairment layerp95 rises under 30%
8Early takeover on mid-sentence pauses60 pause scenariosUnder 10%
9Missed turn end60 clear turn endsUnder 5% with no reply in 2 s
10Barge-in stop timeInterrupt at a random pointp95 under 300 ms from caller onset to silence
11Phantom-reference rateProbe described aboveUnder 5%
12False barge-in at 10 dB SNRBabble noise, no speechUnder 5% of agent turns cut
13Tool dead air at 2 s delayInjected latencyFiller or acknowledgment within 1.2 s
14Cancel on interruptInterrupt during a tool callNo side effect committed
15Tool result orderingTool call mid-replyNo overlapping agent audio
16Tool failure handlingTool returns an errorAgent states the failure; no invented result
1745-minute call survivesScripted long callNo drop; or resumes with history
18Fact recall at minute 40Five planted factsAt least 4 of 5 recalled
19Limit signalingRun past documented limitsWarning event before close (`GoAway`, usage ratio)
20Cost per call matches modelCompare billed usage with your estimateWithin 10%
21Cache behavior (token APIs)Log cached tokens per responseDocumented and stable across runs
22Behavior at the concurrency ceilingRamp past the limitClean rejection or queue, never a silent slowdown
23Close codes and reasons loggedForce each failureEvery session ends with a reason
24Region and retention fitReview configMeets your data policy before any real audio

The test harness: measure every vendor from the client side

Vendor latency numbers are measured at the vendor. Deepgram's `LatencyReport` is an honest example. It splits a turn into `stt_latency`, `ttt_text_latency`, `tts_latency`, and `total_latency`, defined as user utterance end to first audio byte. That excludes the network path, your playout buffer, and any WebRTC jitter buffer. Callers hear the client's view, so the harness times everything on one monotonic clock at the client and uses vendor reports only to break the total down.

Architecture of a client-side test harness that feeds identical labeled audio through an impairment layer into per-vendor adapters, a playout simulator, and an event recorder on one clock

The harness has five parts.

1. Labeled audio source. Recorded caller turns with ground-truth speech onset and offset labels. Every latency timer starts from these labels, never from a vendor's `speech_stopped` event, which is part of what you are testing.

2. Real-time pacer. Send audio in fixed frames at wall-clock speed, such as 20 ms frames. GPT-Live's WebSocket guide warns that piping a file all at once does not simulate a live microphone. Full-Duplex-Bench streamed 30 ms chunks of 16 kHz PCM to Gemini Live for the same reason.

3. Impairment layer. Apply codec (G.711 or Opus), packet loss (0%, 1%, 3%), jitter, and noise at fixed SNR levels (clean, 20 dB, 10 dB), with a seeded random generator so every vendor gets the same impairments.

4. Vendor adapters. Each one turns one internal action, such as sending a frame, truncating at a playback position, or returning a tool result, into that vendor's events. One adapter covers OpenAI Realtime and Grok Voice.

5. Playout simulator and recorder. Model a real playout buffer. Count audio as heard only when it leaves the buffer. Timestamp every event and every played sample on the same clock.

From those timestamps, compute the metrics with the same formula for every vendor:

  • Time to first audio (client) = time first agent sample leaves playout - ground-truth end of caller speech.
  • Barge-in stop time = time of last agent sample played after caller onset - ground-truth caller onset.
  • Unheard-in-context seconds = agent audio generated - agent audio played, per interrupted turn, kept only where the API cannot truncate.
  • Server-to-client overhead = client time to first audio - vendor-reported latency, where the vendor reports one, such as Deepgram's `total_latency`.

Here is a simplified adapter for the shared OpenAI Realtime and Grok Voice protocol. Event names come from each vendor's docs. Error handling, auth refresh, and the tool loop are left out.

# Simplified harness adapter for the OpenAI Realtime protocol (also used by xAI Grok Voice).
# Illustrative: no retries, no tool loop. Event names per vendor docs, October 2026.
import asyncio, base64, json, time
import websockets

FRAME_MS = 20
RATE = 24000                                  # PCM16 mono
FRAME_BYTES = RATE * 2 * FRAME_MS // 1000     # 960 bytes per 20 ms frame

class RealtimeAdapter:
    def __init__(self, url, headers, clock=time.monotonic):
        self.url, self.headers, self.clock = url, headers, clock
        self.events = []          # (t, type, payload) on one client clock
        self.current_item = None  # assistant item id for truncation

    async def run_turn(self, pcm: bytes, player):
        async with websockets.connect(self.url, additional_headers=self.headers) as ws:
            await ws.send(json.dumps({
                "type": "session.update",
                "session": {"turn_detection": {"type": "server_vad"}},
            }))
            sender = asyncio.create_task(self._pace(ws, pcm))
            async for raw in ws:
                ev = json.loads(raw)
                t = self.clock()
                self.events.append((t, ev["type"], ev))
                if ev["type"] == "response.output_audio.delta":
                    self.current_item = ev.get("item_id")
                    player.enqueue(base64.b64decode(ev["delta"]), t)
                elif ev["type"] == "input_audio_buffer.speech_started":
                    # Barge-in: stop local playout, then tell the server what was heard.
                    played_ms = player.flush()
                    if self.current_item:
                        await ws.send(json.dumps({
                            "type": "conversation.item.truncate",
                            "item_id": self.current_item,
                            "content_index": 0,
                            "audio_end_ms": played_ms,
                        }))
                elif ev["type"] == "response.done":
                    usage = ev["response"].get("usage", {})
                    self.events.append((t, "usage", usage))  # cached_tokens lives here
            await sender

    async def _pace(self, ws, pcm):
        start = self.clock()
        for i in range(0, len(pcm), FRAME_BYTES):
            await ws.send(json.dumps({
                "type": "input_audio_buffer.append",
                "audio": base64.b64encode(pcm[i:i + FRAME_BYTES]).decode(),
            }))
            # Sleep to wall-clock position, not a fixed interval, so drift cannot build up.
            target = start + (i // FRAME_BYTES + 1) * FRAME_MS / 1000
            await asyncio.sleep(max(0, target - self.clock()))

The Gemini adapter sends `send_realtime_input` blobs with `mime_type="audio/pcm;rate=16000"` and watches `interrupted`, `go_away`, and `session_resumption_update`. The GPT-Live adapter streams `session.input_audio.append` and logs `session.usage.updated`. The Deepgram adapter waits for `SettingsApplied`, sends binary frames, and logs `LatencyReport`. All of them write the same event log, so one scoring script covers every vendor.

How many runs you need

Plan sample sizes up front.

  • Percentiles. With 200 turns per condition, the 95% confidence interval for p95 spans the 184th to 196th ordered values. With 60 turns, it spans the 53rd value to the maximum, which tells you little.
  • Rates. To detect a barge-in failure rate of 10% vs 5% at 80% power and two-sided alpha 0.05, you need about 435 trials per vendor. The formula is n = (z_a x sqrt(2 x p x (1 - p)) + z_b x sqrt(p1(1 - p1) + p2(1 - p2)))^2 / (p1 - p2)^2, with p1 = 0.10, p2 = 0.05, pooled p = 0.075, z_a = 1.96, and z_b = 0.84. Fewer trials can only find large differences.
  • Repeat across days. Run the matrix at least three times on different days and hours. Shared infrastructure has daily load cycles.

A practical matrix is 12 scenarios (clean turn, mid-sentence pause, early and late barge-in, three tool delays, tool failure, long call, noise-only, digits, name spelling) x 4 network conditions x 3 runs. Every vendor gets the same 144 cells. Our time to first audio guide defines the latency metrics in more detail.

How to run a realtime voice API evaluation

1. Freeze the requirements. Write down transport, codec, call length, tool latency, data policy, and checklist pass bars before you open a vendor console.

2. Build the labeled audio set. Record the 12 scenarios with ground-truth speech onset and offset labels, plus 8 kHz versions for phone traffic.

3. Write one adapter per protocol family. OpenAI Realtime and Grok Voice share one. The others each need their own, all writing one event log format.

4. Calibrate turn detection per vendor. Sweep VAD settings on the pause and noise-only scenarios until each vendor meets your false-takeover bar.

5. Run the matrix. Run 12 scenarios x 4 network conditions x 3 runs, paced in real time with seeded impairments, plus the 45-minute call.

6. Run the probes. Run the phantom-reference probe, cancel-on-interrupt, and fact recall, logging every context and usage event.

7. Ramp concurrency. Go to your production peak, then past the documented limit, and record the ceiling behavior.

8. Reconcile cost. Compare billed usage with your per-call model, including the cache-miss case for token-billed APIs.

9. Score and decide. Fill in the checklist with measured values and confidence intervals, and reject any vendor that fails a must-pass item.

10. Rerun on every change. Pin model versions instead of aliases like `grok-voice-latest`, and rerun the matrix before any upgrade.

Steps 5 through 9 are where vendor-run benchmarks tend to pick the transport, network, and scenarios that suit them. Evalgent runs this harness against the APIs you shortlist and reports the numbers with no stake in which vendor wins. Our bake-off guide covers the commercial side.

Frequently asked questions

What is the best way to evaluate a realtime voice API?

Feed every API the same labeled audio, network impairments, and scenarios. Measure latency, barge-in, tools, and session limits on one client-side clock. Check whether interrupted speech is removed from model context, and convert pricing to cost per call on your traffic. Vendor dashboards measure the vendor's view, not your caller's.

Is the GPT-Live API available, and how is it priced?

Yes. OpenAI's `gpt-live-1` model page lists it on the `/v1/live/sessions` endpoint at $0.05 per minute of voice time, billed per second with no rounding up. Backend model and tool usage bill separately. Silence counts as active time. WebRTC sessions bill 15 seconds at creation, credited once the session runs. Concurrency ranges from 25 sessions at Tier 1 to 500 at Tier 5.

What is the difference between GPT-Live and the OpenAI Realtime API?

GPT-Live is full duplex. The voice model decides when to speak and delegates reasoning and tools to a backend, and it bills per minute. The Realtime API is turn-based, with `server_vad` or `semantic_vad`, and it bills per token. Realtime supports `conversation.item.truncate` for unheard audio. GPT-Live does not, so discarded speech stays in its context.

Does Gemini Live API have a session time limit?

Yes. Google's docs limit audio-only sessions to 15 minutes without context window compression, and audio-video sessions to 2 minutes. Each connection lasts about 10 minutes. Enable `contextWindowCompression` to extend sessions, and use session resumption handles, valid for 2 hours, to carry a session across connections. Watch for the `GoAway` message, which reports the time left.

Which realtime voice APIs remove unheard audio after an interruption?

OpenAI Realtime and Grok Voice support `conversation.item.truncate`. OpenAI also truncates automatically when you clear the output buffer on WebRTC or SIP. ElevenLabs reports a corrected, truncated response through `agent_response_correction`. GPT-Live does not support truncation. Gemini Live and Deepgram document stopping playback but no way to report the playback position. Test each with a phantom-reference probe.

How much does a realtime voice API cost per minute?

In our 5-minute worked example, costs ranged from about $0.03 per minute on Gemini 3.8 Live to $0.08 per minute on Grok Voice and ElevenLabs. OpenAI Realtime ranged from $0.05 to $0.18 per minute depending on cache hits. Token-billed APIs re-read the conversation every turn, so long calls cost more per minute. LLM inclusion differs by vendor.

How do I measure realtime voice API latency correctly?

Measure at the client. Start the timer at the labeled end of caller speech, not the vendor's speech-stopped event. Stop it when the first agent sample leaves a simulated playout buffer. Pace input in real time, report p50 and p95 over at least 200 turns per condition, and compare with vendor-reported latency.

Can I use the same code for OpenAI Realtime and Grok Voice?

Mostly. xAI documents compatibility with the OpenAI Realtime protocol. Change the base URL to `wss://api.x.ai/v1/realtime`, swap the API key, and choose a Grok model. Differences remain. Input transcription arrives as cumulative `updated` events. `rate_limits.updated` is not emitted. `output_audio_buffer.clear` works only on WebRTC and SIP. VAD defaults differ too, so recalibrate.

The bottom line

A realtime voice API is a protocol with limits, defaults, and billing rules that decide how your agent behaves at minute 15, on a lossy phone line, and right after a caller interrupts. Measure those mechanics from the client side with identical inputs for every vendor, and choose on the checklist results rather than the demo.

Related Articles