Realtime Voice Agents in production: phone support and voice assistants with Realtime API + function calling
Wire GPT-Realtime-2.1 into phone / voice assistant scenarios for Realtime Voice Agents. WebRTC vs WebSocket selection, server_vad tuning, mid-conversation function calling, barge-in handling, call quality monitoring.
How to
Provision Twilio number + media streams
Buy a number in Twilio console, enable Media Streams. In TwiML route the call to wss://your-server/realtime-twilio, forwarding μ-law 8kHz audio over WebSocket.
Implement WebSocket server
Node.js + ws: receive audio chunks and forward them to OpenAI Realtime WebSocket for a bidirectional stream. Note Twilio audio is base64 μ-law, OpenAI expects PCM16 24kHz - resample in between.
Configure server_vad
In session.update set server_vad: silence_duration_ms=350, prefix_padding_ms=250, threshold=0.5. Support scenarios want shorter pauses. Threshold can start at default and tune for noisy rooms.
Wire function calling
Declare tools array: query_order(order_id), change_address(order_id, new_address). Set tool_choice='auto'. Function results go back via conversation.item.create.
Enable barge-in + monitoring
interrupt_response=true. Monitor TTFB, turn latency, WER. Expose Prometheus metrics to a dashboard, alert when over threshold.
A Realtime Voice Agent is a different beast from Chat Completions: it streams audio, starts generating while the user is still pausing, and triggers functions mid-conversation. This guide is for builders shipping phone / voice-assistant products: WebRTC vs WebSocket, VAD tuning, mid-conversation function calling, barge-in, monitoring. Production template at the end.
When to use a Realtime Voice Agent
Not every voice scenario needs Realtime - decide first.
- Phone support: user calls and asks "where is my package". AI listens and queries the order live. Use Realtime - users expect human-speed; traditional ASR+LLM+TTS pipeline with 3-5s latency feels robotic.
- Voice assistants: smart speaker / car / smart home. User says "turn the living room lights up". AI executes and replies. Use Realtime - latency matters, frequent mid-conversation function calls.
- Meeting transcription: convert recordings to text + summary. Don't use Realtime - Whisper + GPT-4o batch is fine; Realtime is overpriced here.
- Audiobooks / voiceover: pure TTS. Use TTS API; Realtime works but is more expensive.
WebRTC vs WebSocket selection
| Dimension | WebRTC | WebSocket |
|---|---|---|
| Browser / native app | native | needs AudioWorklet |
| NAT traversal | automatic (ICE) | needs STUN/TURN |
| Server-to-PSTN | awkward (needs gateway) | direct (Twilio Media Streams) |
| Codec | opus (default) | flexible (μ-law / PCM) |
| Latency | low (200-500ms) | medium (400-800ms) |
| Complexity | signaling + STUN/TURN | one WS service |
- Browser / App: WebRTC. MediaStream → RTCPeerConnection → DataChannel → Realtime API.
- Phone integration (Twilio / Vonage): WebSocket. Twilio Media Streams forwards μ-law 8kHz over wss://, you relay to Realtime API.
- IVR / self-hosted: WebSocket + SIP gateway.
server_vad tuning
Realtime API's VAD is server-side - the model decides when the user has finished. Three knobs:
session.update({
turn_detection: {
type: 'server_vad',
threshold: 0.5, // VAD trigger (0-1)
silence_duration_ms: 350, // user pause considered 'done' (ms)
prefix_padding_ms: 250, // audio prefix buffer (ms)
},
});
Per scenario:
| Scenario | silence_duration_ms | prefix_padding_ms | threshold | Why |
|---|---|---|---|---|
| Phone support | 300-400 | 200-300 | 0.5 | users pause briefly (eager to speak next) |
| Voice assistant (smart home) | 500-700 | 300-400 | 0.5 | balanced |
| Voice notes / meetings | 800-1200 | 400-600 | 0.6 | allow long thinking pauses |
| Noisy (cafe / car) | 400-500 | 200-300 | 0.7-0.8 | raise threshold to reduce false triggers |
After tuning, transcribe recordings with Whisper and measure WER - target under 5%. WER over 10% means the agent is unusable.
Mid-conversation function calling
This is the single biggest advantage of Realtime Voice Agent over traditional pipelines - the model fires functions mid-stream while TTS says "let me check".
session.update({
tools: [
{
type: 'function',
name: 'query_order',
description: 'Query order status. Trigger: when user asks "where is my order X" or similar.',
parameters: {
type: 'object',
properties: {
order_id: { type: 'string', description: 'Order ID' },
},
required: ['order_id'],
},
},
],
tool_choice: 'auto',
});
// Listen for response.function_call_arguments.done
ws.on('response.function_call_arguments.done', async (event) => {
const args = JSON.parse(event.arguments);
const result = await queryOrder(args.order_id);
// Send result back to the model
ws.send(JSON.stringify({
type: 'conversation.item.create',
item: {
type: 'function_call_output',
call_id: event.call_id,
output: JSON.stringify(result),
},
}));
});
End-to-end:
User: Can you check order 12345?
AI: Sure, let me check. ← TTS starts immediately (does not wait for function)
[function_call: query_order(12345)] ← model fires tool simultaneously
[function returns: arrived at XX hub, expected this afternoon]
AI: Your order is at XX hub, arriving this afternoon.
Hallucination guard: three layers -
- Function descriptions spell out trigger conditions to prevent misuse.
- Treat function returns as untrusted input. Validate order IDs, amounts, addresses.
- High-risk actions (refunds, address changes) require explicit user confirmation. AI says "I will change your address to XX" and waits for "confirm" before executing.
Barge-in
User interruptions are part of natural conversation - you say "your order is at XX hub, expected..." and user jumps in "no wait, I need to change the address". Handle it:
session.update({
turn_detection: {
type: 'server_vad',
interrupt_response: true, // enable barge-in
},
});
Barge-in flow:
- User starts talking → server_vad detects
- Stop TTS output immediately
- Reset audio stream to last user turn (drop AI's unfinished spoken output)
- Continue processing user's new input
Barge-in delay under 200ms feels natural. Over 500ms and the user feels they "cannot get a word in".
Call quality monitoring
Production-grade Realtime Voice Agent must monitor three numbers:
# pseudocode
def record_call(call_id):
return {
'ttfb_ms': time_to_first_byte, # user-stopped to AI-started
'turn_latency_ms': turn_latency, # whole-turn latency
'wer': compute_wer(audio, transcript), # ASR accuracy
'interrupt_count': interrupt_count, # barge-ins (>5/min means AI too verbose)
}
| Metric | Healthy | Warning | Failed |
|---|---|---|---|
| TTFB | < 600ms | 600-1000ms | > 1000ms |
| turn latency | < 1.2s | 1.2-2.0s | > 2.0s |
| WER | < 3% | 3-5% | > 5% |
| barge-ins / minute | < 3 | 3-5 | > 5 |
WER > 5% means VAD or microphone issues; turn latency > 2s means Realtime API rate limiting or network issues.
Production template: Twilio + Realtime
import { WebSocketServer } from 'ws';
import OpenAI from 'openai';
const wss = new WebSocketServer({ port: 8080, path: '/realtime-twilio' });
wss.on('connection', async (twilioWs) => {
// 1. Connect to OpenAI Realtime
const openaiWs = new WebSocket(
'wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1',
{ headers: { 'Authorization': `Bearer ${process.env.OPENAI_API_KEY}` } }
);
// 2. session config
openaiWs.send(JSON.stringify({
type: 'session.update',
session: {
voice: 'alloy',
turn_detection: {
type: 'server_vad',
threshold: 0.5,
silence_duration_ms: 350,
prefix_padding_ms: 250,
interrupt_response: true,
},
tools: [{
type: 'function',
name: 'query_order',
description: 'Query order status',
parameters: { /* ... */ },
}],
input_audio_format: 'g711_ulaw', // Twilio is μ-law 8kHz
output_audio_format: 'pcm16',
},
}));
// 3. Twilio → OpenAI
twilioWs.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.event === 'media') {
openaiWs.send(JSON.stringify({
type: 'input_audio_buffer.append',
audio: msg.media.payload, // base64 μ-law
}));
}
});
// 4. OpenAI → Twilio
openaiWs.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'response.audio.delta') {
twilioWs.send(JSON.stringify({
event: 'media',
streamSid: '...',
media: { payload: msg.delta },
}));
}
});
});
Common pitfalls
- Codec mismatch: Twilio is g711_ulaw 8kHz, OpenAI Realtime defaults to pcm16 24kHz. Without resampling it sounds robotic. Fix: set
input_audio_format: 'g711_ulaw'insession.update- OpenAI handles resampling. - AI repeats itself after barge-in: audio stream not reset after interrupt causes AI to continue its unfinished sentence. Fix: actively send
response.cancelon interrupt. - Function call hangs: function not returning within 5s makes the model wait, user thinks AI is stuck. Fix: 3s function timeout + fallback ('system busy, will reply shortly').
- WebRTC blocked by firewall: enterprise networks often block UDP. Fix: configure TURN server, fallback to WebSocket.
Next steps
- New to Realtime API? Read Realtime Voice Complete Guide: gpt-realtime and Voice Mode.
- Latency obsessed? Read Advanced Voice Deep Manual: GPT-Realtime-2.1 in production and latency tuning.
- Curious about the model family? Read The complete guide to GPT models (2026-07): GPT-5.6 Sol, Terra, Luna.
Key points
- WebRTC for client endpoints (browsers and mobile apps), WebSocket for server-to-PSTN (Twilio / Vonage / IVR). Wrong pick doubles latency.
- server_vad is the linchpin: silence_duration_ms (default 500ms), prefix_padding_ms (default 300ms), threshold (default 0.5). Different scenarios need different values.
- Mid-conversation function calling: model fires function (order lookup) while TTS says 'let me check' - this is the single biggest advantage over ASR+LLM+TTS pipelines.
- Barge-in must be on (interrupt_response=true). When user interrupts AI: stop TTS, reset audio stream to last user turn, continue. Under 200ms feels natural; over 500ms feels broken.
- Monitor three numbers: TTFB (time to first byte, under 800ms), turn latency (user stop to AI start, under 1.2s), WER (ASR accuracy, under 5%).
Frequently asked questions
Official references
Related articles
Realtime API multilingual production: zh / en / ja auto-detect + cross-language dialog + dialect robustness
GPT-Realtime-2.1 multilingual capability: auto-detect user language (zh / en / ja / ko / es etc.), cross-language dialog (user speaks A, AI answers B), dialect robustness (Cantonese / Sichuanese), translate mode auto-translation.
Read articleAdvanced Voice Deep Dive: GPT-Realtime-2.1, Latency Tuning, and Production Patterns
A production-ready guide to GPT-Realtime-2.1 and ChatGPT Advanced Voice: WebRTC vs WebSocket, turn detection tuning, mid-conversation function calling, VAD parameters, and deployment.
Read articleRealtime Voice guide: gpt-realtime and Voice Mode
How to ship low-latency voice AI with OpenAI's gpt-realtime / gpt-realtime-mini: WebRTC vs WebSocket, mid-conversation function calling, and Voice Mode best practices.
Read articleSubscribe to GPTMap Weekly
One email every Monday: curated OpenAI updates, deep dives, and best practices. No ads, unsubscribe anytime.