
Bygg en röstagent på OpenAI Realtime API: produktionshandledningen i 7 steg (2026)
Vår testagent besvarade ett Twilio-samtal och sa sitt första ord 1,1 sekund efter att uppringaren slutat tala. Det är p50-rundresan, mätt över 40 samtal med gpt-realtime-2 och semantic_vad. Ingen magi. OpenAI Realtime API gör tal-till-tal i en enda socket, så du hoppar över STT → LLM → TTS-stafetten som lägger på ungefär 600 ms limkod. Men standardvärdena tar dig inte till en sekund. Det här är bygget i 7 steg som vi släppte, med koden och latenstabellen.
Det här är en bygghandledning, inte en konceptförklaring. Vill du ha den lagerindelade nedbrytningen först, läs vad en AI-röstagent faktiskt är och kom sedan tillbaka. Allt nedan förutsätter att du har en OpenAI API-nyckel och en Node-körmiljö.
Viktigaste punkterna:
- gpt-realtime-2 gör tal-till-tal i en socket, ingen STT/LLM/TTS-stafett, ~600 ms sparade.
- Skapa tillfälliga nycklar på serversidan; skicka aldrig din standard-API-nyckel till en webbläsare.
- Twilios mediaström är 8 kHz μ-law; omsampla till 24 kHz PCM16 för Realtime API.
- Vi mätte p50 1,1 s / p95 1,9 s rundresa. Avbrott sker via
response.cancel.
Vad du bygger i 7 steg
Den här handledningen bygger en telefonsvarande OpenAI Realtime API-röstagent som svarar inom 1,5 sekund, anropar en riktig funktion mitt i samtalet och låter uppringaren avbryta. Flödet är kort: en uppringare ringer ett nummer, ljud streamas till din server, din server bryggar det via en enda socket till gpt-realtime-2, modellen talar och kan utlösa verktygsanrop, och ljud streamas tillbaka.
Här är vägen, och du kan stanna vid valfritt steg som passar ditt användningsfall:
- Skapa en tillfällig nyckel (serverväg)
- Öppna och konfigurera sessionen
- Lägg till funktionsanrop
- Brygga till ett telefonnummer med Twilio
- Hantera avbrott och inbrytningar
- Trimma latensen till under en sekund
- Distribuera och härda för produktion
Tre transporter bär ljudet, och ditt val beror på var ljudet kommer ifrån. En webbläsare fångar det direkt (WebRTC), din server har redan en rå ström (WebSocket), eller ett telefonnät levererar det (SIP). Vi använder WebSocket för Twilio-bryggan och nämner de andra där de passar.
Steg 1: Skapa en tillfällig nyckel (vägen du inte får hoppa över)
Exponera aldrig din standard-OpenAI-API-nyckel för en webbläsare eller klientenhet. Realtime API utfärdar kortlivade tillfälliga nycklar just för detta. Din server anropar POST /v1/realtime/client_secrets med din riktiga nyckel, ger klienten en token som löper ut på ungefär en minut, och klienten ansluter med den istället.
Här är en minimal Express-väg som skapar 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);Webbläsaren hämtar /session, läser den kortlivade hemligheten och öppnar Realtime-anslutningen med den. Om din agent endast är på serversidan (Twilio-fallet i steg 4) kan du hoppa över klientöverlämningen och öppna socketen från din backend direkt med standardnyckeln. Det tillfälliga flödet finns för att skydda icke betrodda klienter.
Steg 2: Öppna sessionen och konfigurera gpt-realtime-2
Öppna en anslutning och skicka sedan en session.update som anger modell, ljudformat, röst och turdetektering. OpenAI-dokumentationen rekommenderar att börja med reasoning.effort på low och bara höja det om din verktygslogik behöver mer noggrannhet, eftersom högre ansträngning kostar dig latens. Ljud körs som 24 kHz PCM16 i båda riktningarna.
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: "Du är en bokningsagent för en restaurang. Var kortfattad.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));Vilken transport du lindar runt socketen beror på ljudkällan:
| Transport | Använd när | Ljudkälla |
|---|---|---|
| WebRTC | En webbläsare eller mobilapp fångar mikrofonen direkt | Klientenhet |
| WebSocket | Din server har redan en rå ljudström | Serverpipeline |
| SIP | Du vill att OpenAI hanterar telefondelen | PSTN / telefoni |
För den fullständiga fältlistan för sessionen och GA-funktionsuppsättningen är OpenAI Realtime API-dokumentationen källan till sanning. Vi använder WebSocket eftersom Twilio ger oss rått ljud i steg 4.
Steg 3: Lägg till funktionsanrop (så att agenten faktiskt kan göra något)
En röstagent som inte kan agera är en speakerröst. Funktionsanrop låter gpt-realtime-2 pausa mitt i samtalet, be din kod köra något och fortsätta tala med resultatet. Du deklarerar ett verktyg i sessionen, modellen sänder en function_call_arguments.done-händelse när den vill ha det, du kör arbetet och skickar tillbaka utdata.
Deklarera verktyget och hantera sedan händelsen:
// i session.update -> session.tools:
tools: [{
type: "function",
name: "book_reservation",
description: "Bokar ett bord för ett antal gäster och en tid.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "ISO 8601 datum/tid" },
},
required: ["party_size", "time"],
},
}]
// hantering av anropet:
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // din riktiga logik
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" })); // låt den säga resultatet
}Den vanligaste anledningen till att verktyg tyst aldrig utlöses: att inte lyssna på function_call_arguments.done och inte skicka response.create efteråt. Modellen producerade anropet, du ignorerade det, uppringaren hör radiotystnad.
Om din agent jonglerar många verktyg ändrade OpenAI Agents SDK kalkylen här. Dess översyn den 15 april 2026 gjorde Model Context Protocol (MCP) förstaklassigt och förvandlade överlämningar mellan underagenter till en körtidsprimitiv. Så istället för att proppa varje verktyg i en prompt kan en routeragent lämna över en bokning till en bokningsunderagent och en faktureringsfråga till en annan. Agents SDK-snabbstarten för röst lindar samma Realtime-session i en RealtimeAgent och ger dig överlämningar utan att du skriver din egen orkestreringsslinga.
Steg 4: Brygga till ett telefonnummer (Twilio)
För att besvara riktiga samtal bryggar du en telefonileverantör in i socketen. Med Twilio riktar du ett inkommande samtal till en TwiML <Connect><Stream> som öppnar en WebSocket till din server, och du reläar ljudramar mellan Twilio och Realtime API. SIP är alternativet. OpenAI Realtime accepterar SIP direkt, vilket tar bort din mediarelä helt om du inte behöver röra ljudet.
TwiML:en som startar strömmen:
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>Här är fällan som äter en dag om du missar den: Twilios mediaström är 8 kHz μ-law, och Realtime API vill ha 24 kHz PCM16. Du omsamplar i båda riktningarna, annars får du förvrängt ekorrljud.
// inkommande: 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"),
}));
// utgående: 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 fullständiga ramformatet finns i Twilio Media Streams-dokumentationen. Håll omsamplingen billig, eftersom ett tungt bibliotek här lägger till latens som du betalar för i varje ram.
Steg 5: Hantera avbrott och inbrytningar
En produktionsagent låter uppringaren prata över den. Avbrott (barge-in) innebär att upptäcka att uppringaren började tala medan agenten är mitt i en mening, och sedan avbryta agenten rent. Realtime API hanterar detta med response.cancel: när turdetekteringen rapporterar att tal började under uppspelningen avbryter du det aktiva svaret och tömmer det redan buffrade ljudet mot uppringaren.
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // släng köad uppspelning
}Turdetektering har två lägen, och valet spelar roll. server_vad utlöses på råa tystnadströsklar och tenderar att avbryta uppringaren vid naturliga pauser. semantic_vad väntar tills modellen tror att uppringaren faktiskt avslutat en tanke, och producerar därmed långt färre falska avbrott vid en tankepaus. För telefonsamtal är semantisk VAD den som känns mänsklig.
Steg 6: Trimma latensen till under en sekund
Här blir en demo en produkt, så här är siffrorna från vårt eget bygge, inte en teoretisk budget. Vi körde samma restaurangagent över 40 testsamtal i maj 2026, på en enda liten server samlokaliserad nära OpenAI-regionen, och bytte bara turdetekterings- och resonemangsinställningarna.
| Konfiguration | p50 rundresa | p95 | Anteckningar |
|---|---|---|---|
| server_vad, reasoning low | ~1,4 s | ~2,3 s | fler falska avbrott vid pauser |
| semantic_vad, reasoning low | ~1,1 s | ~1,9 s | vårt produktionsstandardvärde |
| semantic_vad, reasoning medium | ~1,8 s | ~3,1 s | bättre verktygsnoggrannhet, långsammare |
Spakarna som verkligen flyttade nålen, i ordning efter påverkan:
- Håll
reasoning.effortpå low om inte ett specifikt verktyg verkligen behöver noggrannheten. Medium nästan fördubblade vår p50. - Tryck inte ljud snabbare än realtid. Att översvämma
input_audio_buffer.appendfår bufferten att svämma över och orsakar drift; takta ramar mot väggklockan. - Håll socketen varm. Att kallöppna en anslutning per samtal lägger till handskakningen till din latens för första ordet. Poola anslutningar där samtalsvolymen tillåter.
- Omsampla effektivt. En naiv omsamplare i den heta vägen lade till ~80 ms per tur för oss.
Vad kostar det att köra per minut när det väl är live? Vi gjorde ta-med-din-egen-nyckel-kalkylen separat, se vad en BYOK-röstagent kostar per minut istället för att härleda den igen här.
Steg 7: Distribuera och härda för produktion
Glappet mellan "det funkade på min laptop" och "det överlever 500 samtal om dagen" är en handfull välkända fel. Här är härdningschecklistan, dragen från misstagen som faktiskt knäcker Realtime-agenter:
| Fälla | Symptom | Åtgärd |
|---|---|---|
| Fel samplingsfrekvens | förvrängt / ekorrljud | 24 kHz PCM16 åt båda hållen |
Ignorera function_call_arguments.done | verktyg utlöses aldrig | lyssna och skicka response.create |
| Trycka ljud snabbare än realtid | buffertöverskridning, drift | takta ramar mot realtid |
| Ingen återanslutningslogik | samtal faller vid en socketstörning | autoåteranslutning + återuppta session |
Ingen response.done-hantering | överlappande turer | grinda nästa tur på response.done |
Två saker till för riktig trafik. Vid långa samtal, rotera eller såda om sessionen var några turer så att kontexten inte driver, eftersom ett 20-minuterssamtal samlar tillstånd som modellen börjar snubbla över. Och logga varje verktygsanrop med dess argument och resultat; när en uppringare säger "agenten bokade fel tid" berättar transkriptet ensamt inte om det var modellen eller din kod som hade fel.
Om du väljer Agents SDK-vägen från steg 3 kör dess nya containersandlåda verktygskod isolerat, vilket spelar roll så fort dina verktyg rör ett filsystem eller skal istället för bara ett API.
När du bör köpa en hanterad plattform istället
Att bygga direkt på Realtime API ger dig mest kontroll och lägst kostnad per minut, men du äger återanslutningslogiken, telefonbryggan, efterlevnaden och observerbarheten, alla de oglamorösa delarna av steg 4 till 7. Om du behöver en telefonagent live den här veckan och inte vill underhålla en mediarelä är en hanterad plattform det snabbare valet.
Vi byggde samma agent på de tre stora och jämförde dem ärligt: Retell, Vapi eller Bland. Om du fortfarande bestämmer dig för vilken sida av linjen du är på, gå igenom det fullständiga beslutsramverket bygga-eller-köpa innan du binder ingenjörstid.
När team vill ha kontrollen över ett skräddarsytt Realtime-bygge utan att bemanna det är det jobbet vi gör: röstagentutveckling för produktion, från telefonbryggan till latenstrimmningen ovan. Vi tittar gärna på ditt användningsfall om du väger det.
Om författaren — Mert Batur är medgrundare av Techsy.io, där teamet levererar AI-agenter, automatiseringssystem och röst-/SDR-pipelines för B2B-kunder. Han skriver om den LLM-verktygsstack som Techsy-teamet faktiskt använder i produktion. LinkedIn
Vanliga frågor
Vad är latensen för en OpenAI Realtime API-röstagent?
I vårt bygge på gpt-realtime-2 med semantic_vad och låg resonemangsansträngning mätte rundreselatensen p50 1,1 s och p95 1,9 s över 40 testsamtal. Tal-till-tal i en socket undviker STT/LLM/TTS-stafetten, vilket är det som gör svar under en sekund möjliga överhuvudtaget.
Behöver jag WebRTC, WebSocket eller SIP för min röstagent?
Använd WebRTC när en webbläsare eller mobilapp fångar mikrofonen direkt, WebSocket när din server redan har en rå ljudström (Twilio-bryggfallet), och SIP när du vill att OpenAI hanterar telefondelen utan din egen mediarelä. De flesta telefonagenter använder WebSocket eller SIP.
Hur ansluter jag OpenAI Realtime API till Twilio?
Rikta ett inkommande Twilio-samtal till en TwiML <Connect><Stream> som öppnar en WebSocket till din server, och relä sedan ljud mellan Twilio och Realtime-socketen. Omsampla Twilios 8 kHz μ-law till API:ets 24 kHz PCM16 åt båda hållen, annars kommer ljudet ut förvrängt.
Hur fungerar funktionsanrop i Realtime API?
Du deklarerar verktyg i sessionskonfigurationen. När modellen vill ha ett sänder den en function_call_arguments.done-händelse. Du kör arbetet, skickar tillbaka resultatet som ett function_call_output-konversationsobjekt och skickar sedan response.create så att agenten säger resultatet. Att glömma det sista steget är varför verktyg ofta misslyckas "tyst".
Hur hanterar man avbrott (barge-in) i Realtime API?
När turdetekteringen rapporterar input_audio_buffer.speech_started under uppspelning, skicka response.cancel för att stoppa det aktiva svaret och töm allt buffrat utgående ljud mot uppringaren. Para ihop det med semantic_vad så att naturliga pauser inte utlöser falska avbrott mitt i en mening.
Vilken ljudsamplingsfrekvens använder OpenAI Realtime API?
Realtime API använder 24 kHz PCM16-ljud i båda riktningarna. Telefonileverantörer som Twilio levererar 8 kHz μ-law, så en telefonbrygga måste omsampla uppåt vid ingången och nedåt vid utgången. Icke matchande samplingsfrekvenser är den vanligaste orsaken till förvrängt ljud.
Hur mycket kostar det att köra en röstagent på Realtime API?
Kostnaden drivs av ljud-in- och ljud-ut-minuter på gpt-realtime-2, och ekonomin för ta-med-din-egen-nyckel skiljer sig kraftigt från en hanterad per-minut-plattform. Vi gjorde hela kalkylen i vår prisanalys för röstagenter istället för att uppskatta den här.
Ska jag bygga på Realtime API eller använda Retell, Vapi eller Bland?
Bygg direkt när du vill ha maximal kontroll och lägst kostnad per minut och kan äga återanslutningar, telefoni och efterlevnad. Köp en hanterad plattform när snabbhet till lansering betyder mer. Vår Retell vs Vapi vs Bland-jämförelse och bygga-eller-köpa-ramverket täcker avvägningarna.
Vad ändrade OpenAI Agents SDK-uppdateringen i april 2026 för röstagenter?
Översynen den 15 april 2026 gjorde Model Context Protocol förstaklassigt, lade till en containersandlåda för verktygskod och förvandlade överlämningar mellan underagenter till en körtidsprimitiv. För röstagenter betyder det att en routeragent kan lämna över till specialiserade underagenter istället för att proppa varje verktyg i en prompt.