
Byg en voice agent på OpenAI Realtime API: Produktionsguide i 7 trin (2026)
Vores testagent besvarede et Twilio-opkald og sagde sit første ord 1,1 sekunder efter, at opkalderen holdt op med at tale. Det er p50 round-trip, målt over 40 opkald på gpt-realtime-2 med semantic_vad. Ikke magi. OpenAI Realtime API kører speech-to-speech i én socket, så du springer STT → LLM → TTS-relæet over, der lægger cirka 600 ms lim oveni. Men standardindstillingerne bringer dig ikke under ét sekund. Det her er den 7-trins implementering, vi sendte i produktion, med koden og latenstabellen.
Det her er en byggeguide, ikke en konceptforklaring. Hvis du først vil have den lagdelte gennemgang, så læs hvad en AI voice agent egentlig er, og kom så tilbage. Alt nedenfor forudsætter, at du har en OpenAI API-nøgle og en Node-runtime.
Nøglepointer:
- gpt-realtime-2 kører speech-to-speech i én socket — intet STT/LLM/TTS-relæ, ~600 ms sparet.
- Udsted ephemeral keys på serversiden; send aldrig din standard-API-nøgle til en browser.
- Twilios mediestrøm er 8kHz μ-law; resampl til 24kHz PCM16 til Realtime API'et.
- Vi målte p50 1,1 s / p95 1,9 s round-trip. Barge-in udløses via
response.cancel.
Hvad du bygger i 7 trin
Den her guide bygger en OpenAI Realtime API voice agent, der besvarer telefonen, svarer igen på under 1,5 sekunder, kalder en rigtig funktion midt i samtalen og lader opkalderen afbryde. Flowet er kort: en opkalder ringer til et telefonnummer, lyd streames til din server, din server broer den til gpt-realtime-2 over én socket, modellen taler og kan affyre tool calls, og lyd streames tilbage.
Her er vejen, og du kan stoppe ved ethvert trin, der matcher dit use case:
- Udsted en ephemeral key (serverrute)
- Åbn og konfigurér sessionen
- Tilføj function calling
- Bro til et telefonnummer med Twilio
- Håndtér barge-in og afbrydelser
- Tun latensen til under ét sekund
- Deploy og hærd
Tre transporter bærer lyden, og dit valg afhænger af, hvor lyden kommer fra. En browser fanger den direkte (WebRTC), din server har allerede en rå strøm (WebSocket), eller et telefonnetværk leverer den (SIP). Vi bruger WebSocket til Twilio-broen og nævner de andre, hvor de passer ind.
Trin 1: Udsted en ephemeral key (ruten du ikke kan springe over)
Eksponér aldrig din standard OpenAI API-nøgle for en browser eller en klientenhed. Realtime API'et udsteder kortlivede ephemeral keys til præcis det her. Din server kalder POST /v1/realtime/client_secrets med din rigtige nøgle, giver klienten en token, der udløber om cirka et minut, og klienten forbinder med den i stedet.
Her er en minimal Express-rute, der udsteder en:
// server.js
import express from "express";
const app = express();
app.get("/session", async (req, res) => {
const r = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: { type: "realtime", model: "gpt-realtime-2" },
}),
});
const data = await r.json();
res.json({ client_secret: data.value, expires_at: data.expires_at });
});
app.listen(3000);Browseren henter /session, læser den kortlivede secret og åbner Realtime-forbindelsen med den. Hvis din agent kun kører på serveren (Twilio-tilfældet i trin 4), kan du springe klientoverdragelsen over og åbne socketten direkte fra din backend med standardnøglen. Ephemeral-flowet findes for at beskytte ikke-tillidsvækkende klienter.
Trin 2: Åbn sessionen og konfigurér gpt-realtime-2
Åbn en forbindelse, og send derefter en session.update, der sætter modellen, lydformatet, stemmen og turdetekteringen. OpenAI-dokumentationen anbefaler at starte med reasoning.effort sat til low og kun hæve den, hvis din tool-logik har brug for mere præcision, da højere effort koster dig latens. Lyd kører som 24kHz PCM16 i begge retninger.
ws.send(JSON.stringify({
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2",
output_modalities: ["audio"],
audio: {
input: { format: "pcm16", sample_rate: 24000 },
output: { format: "pcm16", sample_rate: 24000, voice: "marin" },
},
instructions: "You are a reservations agent for a restaurant. Be brief.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));Hvilken transport du pakker den socket ind i, afhænger af lydkilden:
| Transport | Brug når | Lydkilde |
|---|---|---|
| WebRTC | Browser eller mobilapp fanger mikrofonen direkte | Klientenhed |
| WebSocket | Din server allerede har en rå lydstrøm | Server-pipeline |
| SIP | Du vil have OpenAI til at håndtere telefonbenet | PSTN / telefoni |
For den fulde liste over sessionsfelter og GA-funktionaliteten er OpenAI Realtime API-dokumentationen den autoritative kilde. Vi bruger WebSocket, fordi Twilio giver os rå lyd i trin 4.
Trin 3: Tilføj function calling (så agenten faktisk kan gøre noget)
En voice agent, der ikke kan handle, er bare en speak. Function calling lader gpt-realtime-2 holde pause midt i samtalen, bede din kode om at køre noget og fortsætte med at tale med resultatet. Du erklærer et tool i sessionen, modellen udsender en function_call_arguments.done-event, når den vil have det, du kører arbejdet, og du sender outputtet tilbage.
Erklær toolelet, og håndtér derefter eventen:
// in session.update -> session.tools:
tools: [{
type: "function",
name: "book_reservation",
description: "Book a table for a given party size and time.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "ISO 8601 datetime" },
},
required: ["party_size", "time"],
},
}]
// handling the call:
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // your real logic
ws.send(JSON.stringify({
type: "conversation.item.create",
item: {
type: "function_call_output",
call_id: event.call_id,
output: JSON.stringify(result),
},
}));
ws.send(JSON.stringify({ type: "response.create" })); // let it speak the result
}Den mest almindelige grund til, at tools aldrig affyres i stilhed: at du ikke lytter efter function_call_arguments.done og ikke sender response.create bagefter. Modellen producerede kaldet, du ignorerede det, og opkalderen hører kun tavshed.
Hvis din agent jonglerer med mange tools, har OpenAI Agents SDK ændret regnestykket her. Dens overhaling fra 15. april 2026 gjorde Model Context Protocol (MCP) til første klasses borger og gjorde sub-agent-handoffs til en runtime-primitiv. Så i stedet for at proppe alle tools ind i én prompt kan en router-agent videregive en booking til en reservations-sub-agent og et faktureringsspørgsmål til en anden. Agents SDK voice quickstart pakker den samme Realtime-session ind i en RealtimeAgent og giver dig handoffs uden at skrive din egen orkestreringsløkke.
Trin 4: Bro den til et telefonnummer (Twilio)
For at besvare rigtige opkald broer du en telefonudbyder ind i socketten. Med Twilio peger du et indgående opkald mod en TwiML <Connect><Stream>, der åbner en WebSocket til din server, og du relæer lydframes mellem Twilio og Realtime API'et. SIP er alternativet — OpenAI Realtime accepterer SIP direkte, hvilket fjerner dit medierelæ helt, hvis du ikke behøver at røre lyden.
TwiML'en, der starter strømmen:
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>Her er fælden, der æder en dag, hvis du overser den: Twilios mediestrøm er 8kHz μ-law, og Realtime API'et vil have 24kHz PCM16. Du resampler i begge retninger, ellers får du forvrænget, chipmunk-lyd.
// inbound: Twilio (8kHz μ-law base64) -> Realtime (24kHz PCM16)
const pcm16 = upsample(muLawDecode(Buffer.from(msg.media.payload, "base64")), 8000, 24000);
realtime.send(JSON.stringify({
type: "input_audio_buffer.append",
audio: pcm16.toString("base64"),
}));
// outbound: Realtime (24kHz PCM16) -> Twilio (8kHz μ-law)
const ulaw = muLawEncode(downsample(modelPcm16, 24000, 8000));
twilioWs.send(JSON.stringify({
event: "media",
media: { payload: ulaw.toString("base64") },
}));Det fulde frame-format findes i Twilio Media Streams-dokumentationen. Hold resamplingen billig, for et tungt bibliotek her tilføjer latens, du betaler for på hver frame.
Trin 5: Håndtér barge-in og afbrydelser
En produktionsagent lader opkalderen tale hen over den. Barge-in betyder at detektere, at opkalderen begyndte at tale, mens agenten er midt i en sætning, og derefter afbryde agenten pænt. Realtime API'et håndterer det her med response.cancel: når turdetekteringen rapporterer, at tale startede under afspilning, annullerer du det aktive svar og tømmer den lyd, du allerede har bufferet mod opkalderen.
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // drop queued playback
}Turdetektering har to tilstande, og valget betyder noget. server_vad udløses på rå stilhedsgrænser og har tendens til at afbryde opkalderen ved naturlige pauser. semantic_vad venter, indtil modellen mener, at opkalderen faktisk har afsluttet en tanke, så den producerer langt færre falske afbrydelser ved en tænkepause. Til telefonopkald er semantisk VAD den, der føles menneskelig.
Trin 6: Tun latensen til under ét sekund
Det er her, en demo bliver til et produkt, så her er tallene fra vores egen implementering, ikke et teoretisk budget. Vi kørte den samme restaurantagent over 40 testopkald i maj 2026 på én lille server colokeret nær OpenAI-regionen og skiftede kun turdetekterings- og reasoning-indstillingerne.
| Konfiguration | p50 round-trip | p95 | Noter |
|---|---|---|---|
| server_vad, reasoning low | ~1,4 s | ~2,3 s | flere falske barge-ins ved pauser |
| semantic_vad, reasoning low | ~1,1 s | ~1,9 s | vores produktionsstandard |
| semantic_vad, reasoning medium | ~1,8 s | ~3,1 s | bedre tool-præcision, langsommere |
De håndtag, der faktisk flyttede noget, i rækkefølge efter effekt:
- Hold
reasoning.effortlav, medmindre et bestemt tool virkelig har brug for præcisionen. Medium fordoblede næsten vores p50. - Skub ikke lyd hurtigere end realtid. At oversvømme
input_audio_buffer.appendoverløber bufferen og forårsager drift; takt frames til vægur-tid. - Hold socketten varm. Koldåbning af en forbindelse per opkald lægger håndtrykket oven i din første-ords-latens. Pool forbindelser, hvor opkaldsvolumen tillader det.
- Resample effektivt. En naiv resampler i den varme sti tilføjede ~80 ms per tur for os.
Hvad koster det at køre det her per minut, når det er live? Vi har regnet bring-your-own-key-stykket separat — se hvad en BYOK voice agent koster per minut i stedet for at genudlede det her.
Trin 7: Deploy og hærd til produktion
Kløften mellem "det virkede på min laptop" og "det overlever 500 opkald om dagen" er en håndfuld velkendte fejl. Her er hærdnings-tjeklisten, trukket fra de fejl, der faktisk ødelægger Realtime-agenter:
| Faldgrube | Symptom | Løsning |
|---|---|---|
| Forkert sample rate | forvrænget / chipmunk-lyd | 24kHz PCM16 begge veje |
Ignorerer function_call_arguments.done | tools affyres aldrig | lyt og send response.create |
| Skubber lyd hurtigere end realtid | buffer-overløb, drift | takt frames til realtid |
| Ingen reconnect-logik | opkald dropper ved et socket-udfald | auto-reconnect + genoptag sessionen |
Ingen response.done-håndtering | overlappende ture | gate næste tur på response.done |
To ting mere til rigtig trafik. På lange opkald skal du rotere eller geninitialisere sessionen hver flere tur, så konteksten ikke driver, for et 20-minutters opkald akkumulerer tilstand, modellen begynder at snuble over. Og log hvert tool call med dets argumenter og resultat; når en opkalder siger "agenten bookede det forkerte tidspunkt", fortæller transskriptionen alene dig ikke, om det var modellen eller din kode, der var galt på den.
Hvis du vælger Agents SDK-ruten fra trin 3, kører dens nye container-sandbox tool-kode i isolation, hvilket betyder noget, når dine tools rører et filsystem eller en shell i stedet for bare et API.
Hvornår du i stedet bør købe en managed platform
At bygge direkte på Realtime API'et giver dig mest kontrol og de laveste per-minut-omkostninger, men du ejer reconnect-logikken, telefonibroen, compliance og observability — alle de uglamorøse dele af trin 4 til 7. Hvis du har brug for en telefonagent live i denne uge og ikke vil vedligeholde et medierelæ, er en managed platform det hurtigere valg.
Vi byggede den samme agent på de tre store og sammenlignede dem ærligt: Retell, Vapi eller Bland. Hvis du stadig beslutter, hvilken side af linjen du er på, så gennemgå den fulde build-vs-buy-beslutningsramme, før du forpligter ingeniørtid.
Når teams vil have kontrollen fra en custom Realtime-implementering uden at bemande den, er det det arbejde, vi laver: produktionsudvikling af voice agents, fra telefonibroen til latens-tuning ovenfor. Vi ser gerne på dit use case, hvis du overvejer det.
Om forfatteren — Mert Batur Gurbuz er medstifter af Techsy.io, hvor teamet leverer AI-agenter, automationssystemer og voice/SDR-pipelines til B2B-klienter. Han studerer på University of Birmingham og skriver om den LLM-tooling-stack, Techsy-teamet faktisk bruger i produktion. LinkedIn
Ofte stillede spørgsmål
Hvad er latensen for en OpenAI Realtime API voice agent?
I vores implementering på gpt-realtime-2 med semantic_vad og lav reasoning effort målte vi en round-trip-latens på p50 1,1 s og p95 1,9 s over 40 testopkald. Speech-to-speech i én socket undgår STT/LLM/TTS-relæet, hvilket er det, der overhovedet gør svar under ét sekund mulige.
Har jeg brug for WebRTC, WebSocket eller SIP til min voice agent?
Brug WebRTC, når en browser eller mobilapp fanger mikrofonen direkte, WebSocket, når din server allerede har en rå lydstrøm (Twilio-bro-tilfældet), og SIP, når du vil have OpenAI til at håndtere telefonbenet uden dit eget medierelæ. De fleste telefonagenter bruger WebSocket eller SIP.
Hvordan forbinder jeg OpenAI Realtime API'et til Twilio?
Peg et indgående Twilio-opkald mod en TwiML <Connect><Stream>, der åbner en WebSocket til din server, og relæ derefter lyd mellem Twilio og Realtime-socketten. Resampl Twilios 8kHz μ-law til API'ets 24kHz PCM16 i begge retninger, ellers kommer lyden ud forvrænget.
Hvordan fungerer function calling i Realtime API'et?
Du erklærer tools i session-konfigurationen. Når modellen vil have et, udsender den en function_call_arguments.done-event. Du kører arbejdet, sender resultatet tilbage som et function_call_output-samtaleelement og sender derefter response.create, så agenten siger resultatet. At glemme det sidste trin er grunden til, at tools ofte fejler "i stilhed".
Hvordan håndterer du afbrydelser (barge-in) i Realtime API'et?
Når turdetekteringen rapporterer input_audio_buffer.speech_started under afspilning, sender du response.cancel for at stoppe det aktive svar og rydde eventuel køet outputlyd mod opkalderen. Kombiner det med semantic_vad, så naturlige pauser ikke udløser falske afbrydelser midt i en sætning.
Hvilken lyd-sample rate bruger OpenAI Realtime API'et?
Realtime API'et bruger 24kHz PCM16-lyd i begge retninger. Telefonudbydere som Twilio leverer 8kHz μ-law, så en telefonbro skal resample op på vej ind og ned på vej ud. Forkerte sample rates er den klart mest almindelige årsag til forvrænget lyd.
Hvad koster det at køre en voice agent på Realtime API'et?
Omkostningerne drives af lyd-input- og output-minutter på gpt-realtime-2, og bring-your-own-key-økonomien adskiller sig markant fra en managed per-minut-platform. Vi har regnet det fulde stykke ud i vores voice agent-prisgennemgang i stedet for at estimere det her.
Bør jeg bygge på Realtime API'et eller bruge Retell, Vapi eller Bland?
Byg direkte, når du vil have maksimal kontrol og de laveste per-minut-omkostninger og kan eje reconnects, telefoni og compliance. Køb en managed platform, når tid-til-lancering betyder mere. Vores Retell vs Vapi vs Bland-sammenligning og build-vs-buy-ramme dækker afvejningerne.
Hvad ændrede OpenAI Agents SDK-opdateringen fra april 2026 for voice agents?
Overhalingen fra 15. april 2026 gjorde Model Context Protocol til første klasses borger, tilføjede en container-sandbox til tool-kode og gjorde sub-agent-handoffs til en runtime-primitiv. For voice agents betyder det, at en router-agent kan videregive til specialiserede sub-agenter i stedet for at proppe alle tools ind i én prompt.