
Costruire un agente vocale sull'API Realtime di OpenAI: il tutorial di produzione in 7 passi (2026)
Il nostro agente di test ha risposto a una chiamata Twilio e ha pronunciato la prima parola 1,1 secondi dopo che il chiamante aveva smesso di parlare. È il tempo di andata e ritorno p50, misurato su 40 chiamate con gpt-realtime-2 e semantic_vad. Niente magia. L'API Realtime di OpenAI fa speech-to-speech in un solo socket, quindi salti il relè STT → LLM → TTS che aggiunge circa 600 ms di codice di collegamento. Ma i valori predefiniti non ti porteranno al secondo. Questo è il build in 7 passi che abbiamo rilasciato, con il codice e la tabella delle latenze.
Questo è un tutorial di costruzione, non una spiegazione concettuale. Se vuoi prima la scomposizione a livelli, leggi cos'è davvero un agente vocale IA, poi torna qui. Tutto ciò che segue presuppone che tu abbia una chiave API OpenAI e un runtime Node.
Punti chiave:
- gpt-realtime-2 fa speech-to-speech in un socket, niente relè STT/LLM/TTS, ~600 ms risparmiati.
- Genera le chiavi effimere lato server; non inviare mai la chiave API standard a un browser.
- Lo stream multimediale di Twilio è a 8 kHz μ-law; ricampiona a 24 kHz PCM16 per l'API Realtime.
- Abbiamo misurato p50 1,1 s / p95 1,9 s di andata e ritorno. Il barge-in passa per
response.cancel.
Cosa costruirai in 7 passi
Questo tutorial costruisce un agente vocale con l'API Realtime di OpenAI che risponde al telefono in meno di 1,5 secondi, chiama una funzione reale a metà conversazione e lascia che il chiamante lo interrompa. Il flusso è breve: un chiamante compone un numero, l'audio viene trasmesso al tuo server, il tuo server lo collega a gpt-realtime-2 su un solo socket, il modello parla e può attivare chiamate di strumento, e l'audio torna indietro.
Ecco il percorso, e puoi fermarti a qualsiasi passo adatto al tuo caso d'uso:
- Generare una chiave effimera (route del server)
- Aprire e configurare la sessione
- Aggiungere le chiamate di funzione
- Collegare a un numero di telefono con Twilio
- Gestire il barge-in e le interruzioni
- Regolare la latenza sotto il secondo
- Distribuire e irrobustire per la produzione
Tre trasporti portano l'audio, e la tua scelta dipende da dove arriva l'audio. Un browser lo cattura direttamente (WebRTC), il tuo server ha già uno stream grezzo (WebSocket), o una rete telefonica lo consegna (SIP). Useremo WebSocket per il ponte Twilio e segnaliamo gli altri dove si adattano.
Passo 1: Generare una chiave effimera (la route che non puoi saltare)
Non esporre mai la tua chiave API OpenAI standard a un browser o a un dispositivo client. L'API Realtime emette chiavi effimere a breve durata proprio per questo. Il tuo server chiama POST /v1/realtime/client_secrets con la tua chiave vera, consegna al client un token che scade in circa un minuto, e il client si connette con quello.
Ecco una route Express minimale che ne genera una:
// 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);Il browser chiama /session, legge il segreto a breve durata e apre la connessione Realtime con esso. Se il tuo agente è solo lato server (il caso Twilio del passo 4), puoi saltare il passaggio al client e aprire il socket dal tuo backend direttamente con la chiave standard. Il flusso effimero esiste per proteggere i client non fidati.
Passo 2: Aprire la sessione e configurare gpt-realtime-2
Apri una connessione, poi invia un session.update che imposta modello, formato audio, voce e rilevamento del turno. La documentazione OpenAI consiglia di partire con reasoning.effort su low e di alzarlo solo se la logica dei tuoi strumenti richiede più precisione, perché uno sforzo più alto ti costa latenza. L'audio scorre come 24 kHz PCM16 in entrambe le direzioni.
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: "Sei un agente per le prenotazioni di un ristorante. Sii breve.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));Il trasporto con cui avvolgi quel socket dipende dalla sorgente audio:
| Trasporto | Usare quando | Sorgente audio |
|---|---|---|
| WebRTC | Un browser o un'app mobile cattura il microfono direttamente | Dispositivo client |
| WebSocket | Il tuo server detiene già uno stream audio grezzo | Pipeline del server |
| SIP | Vuoi che OpenAI gestisca la tratta telefonica | RTC / telefonia |
Per l'elenco completo dei campi della sessione e l'insieme di funzionalità GA, la documentazione dell'API Realtime di OpenAI è la fonte autorevole. Usiamo WebSocket perché Twilio ci consegna audio grezzo al passo 4.
Passo 3: Aggiungere le chiamate di funzione (perché l'agente faccia davvero qualcosa)
Un agente vocale che non può agire è una voce fuori campo. Le chiamate di funzione permettono a gpt-realtime-2 di fermarsi a metà conversazione, chiedere al tuo codice di eseguire qualcosa e continuare a parlare con il risultato. Dichiari uno strumento nella sessione, il modello emette un evento function_call_arguments.done quando lo vuole, tu esegui il lavoro e rimandi indietro l'output.
Dichiara lo strumento, poi gestisci l'evento:
// in session.update -> session.tools:
tools: [{
type: "function",
name: "book_reservation",
description: "Prenota un tavolo per un numero di persone e un orario.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "data/ora ISO 8601" },
},
required: ["party_size", "time"],
},
}]
// gestione della chiamata:
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // la tua logica reale
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" })); // fagli dire il risultato
}Il motivo più comune per cui gli strumenti non si attivano mai in silenzio: non ascoltare function_call_arguments.done e non inviare response.create dopo. Il modello ha prodotto la chiamata, tu l'hai ignorata, il chiamante sente silenzio totale.
Se il tuo agente gestisce molti strumenti, l'OpenAI Agents SDK ha cambiato i conti qui. La sua revisione del 15 aprile 2026 ha reso nativo il Model Context Protocol (MCP) e ha trasformato i passaggi di consegne tra sotto-agenti in una primitiva di runtime. Così, invece di stipare ogni strumento in un prompt, un agente router può passare una prenotazione a un sotto-agente delle prenotazioni e una domanda di fatturazione a un altro. La guida rapida vocale dell'Agents SDK avvolge la stessa sessione Realtime in un RealtimeAgent e ti dà i passaggi di consegne senza scrivere il tuo ciclo di orchestrazione.
Passo 4: Collegarlo a un numero di telefono (Twilio)
Per rispondere a chiamate reali, colleghi un provider di telefonia al socket. Con Twilio, indirizzi una chiamata in arrivo a un TwiML <Connect><Stream> che apre un WebSocket verso il tuo server, e inoltri i frame audio tra Twilio e l'API Realtime. SIP è l'alternativa. OpenAI Realtime accetta SIP direttamente, il che rimuove del tutto il tuo relay multimediale se non devi toccare l'audio.
Il TwiML che avvia lo stream:
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>Ecco l'insidia che ti costa una giornata se la perdi: lo stream multimediale di Twilio è 8 kHz μ-law, e l'API Realtime vuole 24 kHz PCM16. Ricampioni in entrambe le direzioni, oppure ottieni audio distorto da scoiattolo.
// in entrata: 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"),
}));
// in uscita: Realtime (24kHz PCM16) -> Twilio (8kHz μ-law)
const ulaw = muLawEncode(downsample(modelPcm16, 24000, 8000));
twilioWs.send(JSON.stringify({
event: "media",
media: { payload: ulaw.toString("base64") },
}));Il formato completo dei frame è nella documentazione di Twilio Media Streams. Mantieni il ricampionamento leggero, perché una libreria pesante qui aggiunge latenza che pagherai a ogni frame.
Passo 5: Gestire il barge-in e le interruzioni
Un agente di produzione lascia che il chiamante parli sopra di lui. Il barge-in consiste nel rilevare che il chiamante ha iniziato a parlare mentre l'agente è a metà frase, poi tagliare l'agente in modo pulito. L'API Realtime lo gestisce con response.cancel: quando il rilevamento del turno segnala che il parlato è iniziato durante la riproduzione, annulli la risposta attiva e svuoti l'audio già nel buffer verso il chiamante.
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // scarta la riproduzione in coda
}Il rilevamento del turno ha due modalità, e la scelta conta. server_vad si attiva su soglie di silenzio grezze e tende a tagliare il chiamante nelle pause naturali. semantic_vad aspetta finché il modello pensa che il chiamante abbia davvero finito un pensiero, producendo così molte meno interruzioni false su una pausa di riflessione. Per le chiamate telefoniche, il VAD semantico è quello che sembra umano.
Passo 6: Regolare la latenza sotto il secondo
Qui una demo diventa un prodotto, quindi ecco i numeri del nostro build, non un budget teorico. Abbiamo fatto girare lo stesso agente di ristorante su 40 chiamate di test a maggio 2026, su un singolo piccolo server colocato vicino alla regione OpenAI, cambiando solo le impostazioni di rilevamento del turno e di ragionamento.
| Configurazione | Andata e ritorno p50 | p95 | Note |
|---|---|---|---|
| server_vad, reasoning low | ~1,4 s | ~2,3 s | più barge-in falsi nelle pause |
| semantic_vad, reasoning low | ~1,1 s | ~1,9 s | il nostro valore di produzione predefinito |
| semantic_vad, reasoning medium | ~1,8 s | ~3,1 s | migliore precisione degli strumenti, più lento |
Le leve che hanno davvero spostato l'ago, in ordine di impatto:
- Tieni
reasoning.effortsu low a meno che uno strumento preciso abbia davvero bisogno della precisione. Medium ha quasi raddoppiato il nostro p50. - Non spingere l'audio più veloce del tempo reale. Inondare
input_audio_buffer.appendfa traboccare il buffer e causa deriva; cadenza i frame all'orologio. - Tieni il socket caldo. Aprire a freddo una connessione per chiamata aggiunge l'handshake alla tua latenza della prima parola. Raggruppa le connessioni dove il volume di chiamate lo consente.
- Ricampiona in modo efficiente. Un ricampionatore ingenuo nel percorso critico ci ha aggiunto ~80 ms per turno.
Quanto costa farlo girare al minuto una volta in produzione? Abbiamo fatto i conti del bring-your-own-key separatamente, vedi quanto costa al minuto un agente vocale BYOK invece di riderivarli qui.
Passo 7: Distribuire e irrobustire per la produzione
Il divario tra «funzionava sul mio portatile» e «regge 500 chiamate al giorno» è una manciata di guasti ben noti. Ecco la checklist di irrobustimento, tratta dagli errori che rompono davvero gli agenti Realtime:
| Insidia | Sintomo | Correzione |
|---|---|---|
| Frequenza di campionamento errata | audio distorto / da scoiattolo | 24 kHz PCM16 in entrambe le direzioni |
Ignorare function_call_arguments.done | gli strumenti non si attivano mai | ascoltare e inviare response.create |
| Spingere l'audio più veloce del tempo reale | overflow del buffer, deriva | cadenzare i frame al tempo reale |
| Nessuna logica di riconnessione | le chiamate cadono a un singhiozzo del socket | riconnessione automatica + ripresa sessione |
Nessuna gestione di response.done | turni sovrapposti | vincolare il turno successivo a response.done |
Altre due cose per il traffico reale. Sulle chiamate lunghe, ruota o riseminala la sessione ogni pochi turni perché il contesto non vada alla deriva, dato che una chiamata di 20 minuti accumula stato su cui il modello inizia a inciampare. E registra ogni chiamata di strumento con i suoi argomenti e il risultato; quando un chiamante dice «l'agente ha prenotato l'orario sbagliato», la trascrizione da sola non ti dirà se aveva torto il modello o il tuo codice.
Se adotti la via dell'Agents SDK del passo 3, la sua nuova sandbox in container esegue il codice degli strumenti in isolamento, cosa che conta non appena i tuoi strumenti toccano un filesystem o una shell invece di una semplice API.
Quando dovresti invece acquistare una piattaforma gestita
Costruire direttamente sull'API Realtime ti dà il massimo controllo e il costo al minuto più basso, ma sei tu a possedere la logica di riconnessione, il ponte telefonico, la conformità e l'osservabilità, tutte le parti poco affascinanti dei passi da 4 a 7. Se ti serve un agente telefonico online questa settimana e non vuoi mantenere un relay multimediale, una piattaforma gestita è la scelta più veloce.
Abbiamo costruito lo stesso agente sulle tre grandi e le abbiamo confrontate onestamente: Retell, Vapi o Bland. Se stai ancora decidendo da che parte della linea ti trovi, percorri il quadro decisionale completo build-vs-buy prima di impegnare tempo di ingegneria.
Quando i team vogliono il controllo di un build Realtime su misura senza dotarlo di personale, è il lavoro che facciamo: sviluppo di agenti vocali per la produzione, dal ponte telefonico alla regolazione della latenza vista sopra. Felici di esaminare il tuo caso d'uso se lo stai valutando.
Sull'autore — Mert Batur è cofondatore di Techsy.io, dove il team realizza agenti IA, sistemi di automazione e pipeline voce/SDR per clienti B2B. Scrive dello stack di tooling LLM che il team Techsy usa davvero in produzione. LinkedIn
Domande frequenti
Qual è la latenza di un agente vocale con l'API Realtime di OpenAI?
Nel nostro build su gpt-realtime-2 con semantic_vad e sforzo di ragionamento basso, la latenza di andata e ritorno ha misurato p50 1,1 s e p95 1,9 s su 40 chiamate di test. Lo speech-to-speech in un socket evita il relè STT/LLM/TTS, ed è ciò che rende possibili le risposte sotto il secondo.
Mi servono WebRTC, WebSocket o SIP per il mio agente vocale?
Usa WebRTC quando un browser o un'app mobile cattura il microfono direttamente, WebSocket quando il tuo server detiene già uno stream audio grezzo (il caso del ponte Twilio), e SIP quando vuoi che OpenAI gestisca la tratta telefonica senza il tuo relay multimediale. La maggior parte degli agenti telefonici usa WebSocket o SIP.
Come collego l'API Realtime di OpenAI a Twilio?
Indirizza una chiamata Twilio in arrivo a un TwiML <Connect><Stream> che apre un WebSocket verso il tuo server, poi inoltra l'audio tra Twilio e il socket Realtime. Ricampiona l'8 kHz μ-law di Twilio al 24 kHz PCM16 dell'API in entrambe le direzioni, altrimenti l'audio esce distorto.
Come funzionano le chiamate di funzione nell'API Realtime?
Dichiari gli strumenti nella configurazione della sessione. Quando il modello ne vuole uno, emette un evento function_call_arguments.done. Esegui il lavoro, rimandi indietro il risultato come elemento di conversazione function_call_output, poi invii response.create perché l'agente dica il risultato. Dimenticare quest'ultimo passo è il motivo per cui gli strumenti spesso falliscono «in silenzio».
Come si gestiscono le interruzioni (barge-in) nell'API Realtime?
Quando il rilevamento del turno segnala input_audio_buffer.speech_started durante la riproduzione, invia response.cancel per fermare la risposta attiva e svuota qualsiasi audio in uscita nel buffer verso il chiamante. Abbinalo a semantic_vad perché le pause naturali non attivino interruzioni false a metà frase.
Quale frequenza di campionamento audio usa l'API Realtime di OpenAI?
L'API Realtime usa audio 24 kHz PCM16 in entrambe le direzioni. I provider di telefonia come Twilio consegnano 8 kHz μ-law, quindi un ponte telefonico deve ricampionare verso l'alto in ingresso e verso il basso in uscita. Frequenze di campionamento non corrispondenti sono la causa più comune di audio distorto.
Quanto costa far girare un agente vocale sull'API Realtime?
Il costo è guidato dai minuti audio di input e output su gpt-realtime-2, e l'economia del bring-your-own-key differisce nettamente da una piattaforma gestita al minuto. Abbiamo fatto i conti completi nella nostra analisi dei prezzi degli agenti vocali invece di stimarli qui.
Devo costruire sull'API Realtime o usare Retell, Vapi o Bland?
Costruisci direttamente quando vuoi il massimo controllo e il costo al minuto più basso e puoi possedere riconnessioni, telefonia e conformità. Acquista una piattaforma gestita quando conta di più la rapidità di lancio. Il nostro confronto Retell vs Vapi vs Bland e il quadro build-vs-buy coprono i compromessi.
Cosa ha cambiato l'aggiornamento dell'OpenAI Agents SDK di aprile 2026 per gli agenti vocali?
La revisione del 15 aprile 2026 ha reso nativo il Model Context Protocol, ha aggiunto una sandbox in container per il codice degli strumenti e ha trasformato i passaggi di consegne tra sotto-agenti in una primitiva di runtime. Per gli agenti vocali, significa che un agente router può passare la mano a sotto-agenti specializzati invece di stipare ogni strumento in un prompt.