GPTMap

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.

TL;DR
A Realtime Voice Agent runs GPT-Realtime-2.1 over a phone or voice-assistant pipeline where the model listens, talks, and triggers functions mid-stream. Five production essentials: (1) WebRTC vs WebSocket pick by endpoint; (2) server_vad tuning; (3) mid-conversation function calling; (4) barge-in under 200ms; (5) call quality monitoring on TTFB, turn latency, WER. Copy-paste...
A Realtime Voice Agent is a real-time spoken-dialogue system built on GPT-Realtime-2.1 (or GPT-Realtime-2.1 mini) for phone / voice-assistant / smart-device scenarios: it streams audio in, generates audio out, and triggers external functions mid-conversation (order lookup, address change, dispatch).

How to

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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

DimensionWebRTCWebSocket
Browser / native appnativeneeds AudioWorklet
NAT traversalautomatic (ICE)needs STUN/TURN
Server-to-PSTNawkward (needs gateway)direct (Twilio Media Streams)
Codecopus (default)flexible (μ-law / PCM)
Latencylow (200-500ms)medium (400-800ms)
Complexitysignaling + STUN/TURNone 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:

Scenariosilence_duration_msprefix_padding_msthresholdWhy
Phone support300-400200-3000.5users pause briefly (eager to speak next)
Voice assistant (smart home)500-700300-4000.5balanced
Voice notes / meetings800-1200400-6000.6allow long thinking pauses
Noisy (cafe / car)400-500200-3000.7-0.8raise 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 -

  1. Function descriptions spell out trigger conditions to prevent misuse.
  2. Treat function returns as untrusted input. Validate order IDs, amounts, addresses.
  3. 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:

  1. User starts talking → server_vad detects
  2. Stop TTS output immediately
  3. Reset audio stream to last user turn (drop AI's unfinished spoken output)
  4. 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)
    }
MetricHealthyWarningFailed
TTFB< 600ms600-1000ms> 1000ms
turn latency< 1.2s1.2-2.0s> 2.0s
WER< 3%3-5%> 5%
barge-ins / minute< 33-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

  1. 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' in session.update - OpenAI handles resampling.
  2. AI repeats itself after barge-in: audio stream not reset after interrupt causes AI to continue its unfinished sentence. Fix: actively send response.cancel on interrupt.
  3. 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').
  4. WebRTC blocked by firewall: enterprise networks often block UDP. Fix: configure TURN server, fallback to WebSocket.

Next steps

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

Traditional pipeline: user stops speaking → ASR → LLM → TTS → audio out, total latency 2-5 seconds. Realtime API is fully streaming: model processes audio in and out simultaneously, can start generating while user is pausing (via speculative decoding), and can fire functions mid-conversation. Total latency is typically 500ms-1.5s. Tradeoff: per-token cost is higher, and you must use OpenAI's Realtime models (no swapping base).

Official references

Related articles

Subscribe to GPTMap Weekly

One email every Monday: curated OpenAI updates, deep dives, and best practices. No ads, unsubscribe anytime.

GPTMap EditorialPublished 2026-08-13 7 min read
Test environment (EEAT)
Last tested: 2026-08-13
Model used: gpt-5.6