
Créer un agent vocal sur l'API Realtime d'OpenAI : le tutoriel de production en 7 étapes (2026)
Notre agent de test a répondu à un appel Twilio et a prononcé son premier mot 1,1 seconde après que l'appelant a cessé de parler. C'est le temps d'aller-retour p50, mesuré sur 40 appels avec gpt-realtime-2 et semantic_vad. Rien de magique. L'API Realtime d'OpenAI fait du speech-to-speech dans un seul socket, donc vous évitez le relais STT → LLM → TTS qui ajoute environ 600 ms de code de liaison. Mais les valeurs par défaut ne vous amèneront pas à une seconde. Voici le build en 7 étapes que nous avons livré, avec le code et le tableau de latence.
C'est un tutoriel de création, pas une explication conceptuelle. Si vous voulez d'abord le découpage en couches, lisez ce qu'est vraiment un agent vocal IA, puis revenez. Tout ce qui suit suppose que vous avez une clé API OpenAI et un environnement Node.
Points clés :
- gpt-realtime-2 fait du speech-to-speech dans un socket, pas de relais STT/LLM/TTS, ~600 ms économisées.
- Générez des clés éphémères côté serveur ; n'envoyez jamais votre clé API standard à un navigateur.
- Le flux média de Twilio est en 8 kHz μ-law ; rééchantillonnez en 24 kHz PCM16 pour l'API Realtime.
- Nous avons mesuré p50 1,1 s / p95 1,9 s d'aller-retour. Le barge-in passe par
response.cancel.
Ce que vous allez créer en 7 étapes
Ce tutoriel crée un agent vocal API Realtime OpenAI qui répond au téléphone en moins de 1,5 seconde, appelle une vraie fonction en pleine conversation et laisse l'appelant l'interrompre. Le flux est court : un appelant compose un numéro, l'audio est streamé vers votre serveur, votre serveur le relie à gpt-realtime-2 via un seul socket, le modèle parle et peut déclencher des appels d'outils, et l'audio repart en flux.
Voici le chemin, et vous pouvez vous arrêter à toute étape qui correspond à votre cas d'usage :
- Générer une clé éphémère (route serveur)
- Ouvrir et configurer la session
- Ajouter les appels de fonctions
- Relier à un numéro de téléphone avec Twilio
- Gérer le barge-in et les interruptions
- Régler la latence sous la seconde
- Déployer et durcir pour la production
Trois transports portent l'audio, et votre choix dépend de l'origine de l'audio. Un navigateur le capture directement (WebRTC), votre serveur dispose déjà d'un flux brut (WebSocket), ou un réseau téléphonique le livre (SIP). Nous utilisons WebSocket pour le pont Twilio et notons les autres là où ils conviennent.
Étape 1 : Générer une clé éphémère (la route à ne pas sauter)
N'exposez jamais votre clé API OpenAI standard à un navigateur ou à un appareil client. L'API Realtime émet des clés éphémères de courte durée précisément pour cela. Votre serveur appelle POST /v1/realtime/client_secrets avec votre vraie clé, remet au client un jeton qui expire en une minute environ, et le client se connecte avec celui-ci à la place.
Voici une route Express minimale qui en génère une :
// 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);Le navigateur appelle /session, lit le secret de courte durée et ouvre la connexion Realtime avec. Si votre agent est uniquement côté serveur (le cas Twilio de l'étape 4), vous pouvez sauter le transfert au client et ouvrir le socket depuis votre backend directement avec la clé standard. Le flux éphémère existe pour protéger les clients non fiables.
Étape 2 : Ouvrir la session et configurer gpt-realtime-2
Ouvrez une connexion, puis envoyez un session.update qui définit le modèle, le format audio, la voix et la détection de tour. La documentation OpenAI recommande de commencer avec reasoning.effort sur low et de ne l'augmenter que si votre logique d'outils a besoin de plus de précision, car un effort plus élevé coûte de la latence. L'audio circule en 24 kHz PCM16 dans les deux sens.
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: "Vous êtes un agent de réservation pour un restaurant. Soyez bref.",
reasoning: { effort: "low" },
turn_detection: { type: "semantic_vad" },
},
}));Le transport autour de ce socket dépend de la source audio :
| Transport | À utiliser quand | Source audio |
|---|---|---|
| WebRTC | Un navigateur ou une app mobile capture le micro directement | Appareil client |
| WebSocket | Votre serveur détient déjà un flux audio brut | Pipeline serveur |
| SIP | Vous voulez qu'OpenAI gère la partie téléphonique | RTC / téléphonie |
Pour la liste complète des champs de session et l'ensemble des fonctionnalités GA, la documentation de l'API Realtime d'OpenAI fait foi. Nous utilisons WebSocket parce que Twilio nous remet de l'audio brut à l'étape 4.
Étape 3 : Ajouter les appels de fonctions (pour que l'agent agisse vraiment)
Un agent vocal qui ne peut pas agir n'est qu'une voix off. L'appel de fonctions permet à gpt-realtime-2 de faire une pause en pleine conversation, de demander à votre code d'exécuter quelque chose et de continuer à parler avec le résultat. Vous déclarez un outil dans la session, le modèle émet un événement function_call_arguments.done quand il le veut, vous exécutez le travail et vous renvoyez la sortie.
Déclarez l'outil, puis gérez l'événement :
// dans session.update -> session.tools :
tools: [{
type: "function",
name: "book_reservation",
description: "Réserve une table pour un nombre de convives et une heure.",
parameters: {
type: "object",
properties: {
party_size: { type: "integer" },
time: { type: "string", description: "date/heure ISO 8601" },
},
required: ["party_size", "time"],
},
}]
// gestion de l'appel :
if (event.type === "response.function_call_arguments.done") {
const args = JSON.parse(event.arguments);
const result = await bookTable(args); // votre vraie logique
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" })); // qu'il énonce le résultat
}La raison la plus fréquente pour laquelle les outils ne se déclenchent jamais en silence : ne pas écouter function_call_arguments.done et ne pas envoyer response.create ensuite. Le modèle a produit l'appel, vous l'avez ignoré, l'appelant entend un silence radio.
Si votre agent jongle avec beaucoup d'outils, l'OpenAI Agents SDK a changé le calcul ici. Sa refonte du 15 avril 2026 a rendu le Model Context Protocol (MCP) natif et a transformé les passations entre sous-agents en primitive d'exécution. Ainsi, au lieu d'entasser chaque outil dans un prompt, un agent routeur peut passer une réservation à un sous-agent de réservation et une question de facturation à un autre. Le démarrage rapide vocal de l'Agents SDK enveloppe la même session Realtime dans un RealtimeAgent et vous donne les passations sans écrire votre propre boucle d'orchestration.
Étape 4 : Relier à un numéro de téléphone (Twilio)
Pour répondre à de vrais appels, vous reliez un fournisseur de téléphonie au socket. Avec Twilio, vous dirigez un appel entrant vers un TwiML <Connect><Stream> qui ouvre un WebSocket vers votre serveur, et vous relayez les trames audio entre Twilio et l'API Realtime. SIP est l'alternative. OpenAI Realtime accepte SIP directement, ce qui supprime entièrement votre relais média si vous n'avez pas besoin de toucher l'audio.
Le TwiML qui démarre le flux :
<Response>
<Connect>
<Stream url="wss://your-server.com/twilio-stream" />
</Connect>
</Response>Voici le piège qui coûte une journée si vous le ratez : le flux média de Twilio est en 8 kHz μ-law, et l'API Realtime veut du 24 kHz PCM16. Vous rééchantillonnez dans les deux sens, sinon vous obtenez un audio déformé de type voix d'écureuil.
// entrant : 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"),
}));
// sortant : Realtime (24kHz PCM16) -> Twilio (8kHz μ-law)
const ulaw = muLawEncode(downsample(modelPcm16, 24000, 8000));
twilioWs.send(JSON.stringify({
event: "media",
media: { payload: ulaw.toString("base64") },
}));Le format complet des trames se trouve dans la documentation Twilio Media Streams. Gardez le rééchantillonnage léger, car une bibliothèque lourde ici ajoute une latence que vous paierez à chaque trame.
Étape 5 : Gérer le barge-in et les interruptions
Un agent de production laisse l'appelant parler par-dessus lui. Le barge-in consiste à détecter que l'appelant a commencé à parler pendant que l'agent est en pleine phrase, puis à couper l'agent proprement. L'API Realtime gère cela avec response.cancel : quand la détection de tour signale que la parole a commencé pendant la lecture, vous annulez la réponse active et videz l'audio déjà mis en tampon vers l'appelant.
if (event.type === "input_audio_buffer.speech_started") {
realtime.send(JSON.stringify({ type: "response.cancel" }));
twilioWs.send(JSON.stringify({ event: "clear" })); // abandonne la lecture en file
}La détection de tour a deux modes, et le choix compte. server_vad se déclenche sur des seuils de silence bruts et tend à couper l'appelant lors de pauses naturelles. semantic_vad attend que le modèle estime que l'appelant a réellement fini une pensée, produisant ainsi bien moins de fausses interruptions sur une pause de réflexion. Pour les appels téléphoniques, le VAD sémantique est celui qui paraît humain.
Étape 6 : Régler la latence sous la seconde
C'est ici qu'une démo devient un produit, alors voici les chiffres de notre propre build, pas un budget théorique. Nous avons fait tourner le même agent de restaurant sur 40 appels de test en mai 2026, sur un seul petit serveur colocalisé près de la région OpenAI, en ne changeant que les réglages de détection de tour et de raisonnement.
| Configuration | Aller-retour p50 | p95 | Notes |
|---|---|---|---|
| server_vad, reasoning low | ~1,4 s | ~2,3 s | plus de faux barge-ins sur les pauses |
| semantic_vad, reasoning low | ~1,1 s | ~1,9 s | notre valeur de production par défaut |
| semantic_vad, reasoning medium | ~1,8 s | ~3,1 s | meilleure précision des outils, plus lent |
Les leviers qui ont vraiment bougé l'aiguille, par ordre d'impact :
- Gardez
reasoning.effortsur low sauf si un outil précis a vraiment besoin de la précision. Medium a presque doublé notre p50. - Ne poussez pas l'audio plus vite que le temps réel. Inonder
input_audio_buffer.appendfait déborder le tampon et provoque une dérive ; cadencez les trames sur l'horloge murale. - Gardez le socket chaud. Ouvrir une connexion à froid par appel ajoute le handshake à votre latence du premier mot. Mutualisez les connexions là où le volume d'appels le permet.
- Rééchantillonnez efficacement. Un rééchantillonneur naïf dans le chemin critique nous a ajouté ~80 ms par tour.
Combien coûte le fonctionnement par minute une fois en production ? Nous avons fait le calcul du bring-your-own-key séparément, voyez ce que coûte un agent vocal BYOK par minute plutôt que de le redériver ici.
Étape 7 : Déployer et durcir pour la production
L'écart entre « ça marchait sur mon portable » et « ça encaisse 500 appels par jour » tient à une poignée de défaillances bien connues. Voici la checklist de durcissement, tirée des erreurs qui cassent réellement les agents Realtime :
| Piège | Symptôme | Correctif |
|---|---|---|
| Mauvaise fréquence d'échantillonnage | audio déformé / voix d'écureuil | 24 kHz PCM16 dans les deux sens |
Ignorer function_call_arguments.done | les outils ne se déclenchent jamais | écouter et envoyer response.create |
| Pousser l'audio plus vite que le temps réel | débordement de tampon, dérive | cadencer les trames sur le temps réel |
| Pas de logique de reconnexion | les appels coupent au moindre incident de socket | reconnexion auto + reprise de session |
Pas de gestion de response.done | tours qui se chevauchent | conditionner le tour suivant à response.done |
Deux autres choses pour du vrai trafic. Sur les appels longs, faites tourner ou réamorcez la session toutes les quelques tours pour que le contexte ne dérive pas, car un appel de 20 minutes accumule un état sur lequel le modèle commence à trébucher. Et journalisez chaque appel d'outil avec ses arguments et son résultat ; quand un appelant dit « l'agent a réservé la mauvaise heure », la transcription seule ne vous dira pas si c'est le modèle ou votre code qui avait tort.
Si vous adoptez la voie de l'Agents SDK de l'étape 3, son nouveau bac à sable conteneurisé exécute le code des outils en isolation, ce qui compte dès que vos outils touchent à un système de fichiers ou à un shell plutôt qu'à une simple API.
Quand vous devriez plutôt acheter une plateforme gérée
Construire directement sur l'API Realtime vous donne le plus de contrôle et le coût par minute le plus bas, mais vous possédez la logique de reconnexion, le pont téléphonique, la conformité et l'observabilité, toutes les parties ingrates des étapes 4 à 7. Si vous avez besoin d'un agent téléphonique en ligne cette semaine et ne voulez pas maintenir un relais média, une plateforme gérée est le choix plus rapide.
Nous avons créé le même agent sur les trois grandes et les avons comparées honnêtement : Retell, Vapi ou Bland. Si vous hésitez encore sur le côté de la ligne où vous vous trouvez, parcourez le cadre de décision complet build-vs-buy avant d'engager du temps d'ingénierie.
Quand des équipes veulent le contrôle d'un build Realtime sur mesure sans le doter en personnel, c'est le travail que nous faisons : développement d'agents vocaux pour la production, du pont téléphonique au réglage de latence ci-dessus. Ravis d'examiner votre cas d'usage si vous le pesez.
À propos de l'auteur — Mert Batur est cofondateur de Techsy.io, où l'équipe livre des agents IA, des systèmes d'automatisation et des pipelines voix/SDR pour des clients B2B. Il écrit sur la pile d'outillage LLM que l'équipe Techsy utilise réellement en production. LinkedIn
Questions fréquentes
Quelle est la latence d'un agent vocal API Realtime OpenAI ?
Dans notre build sur gpt-realtime-2 avec semantic_vad et un faible effort de raisonnement, la latence d'aller-retour a mesuré p50 1,1 s et p95 1,9 s sur 40 appels de test. Le speech-to-speech dans un socket évite le relais STT/LLM/TTS, ce qui rend possibles les réponses sous la seconde.
Ai-je besoin de WebRTC, WebSocket ou SIP pour mon agent vocal ?
Utilisez WebRTC quand un navigateur ou une app mobile capture le micro directement, WebSocket quand votre serveur détient déjà un flux audio brut (le cas du pont Twilio), et SIP quand vous voulez qu'OpenAI gère la partie téléphonique sans votre propre relais média. La plupart des agents téléphoniques utilisent WebSocket ou SIP.
Comment connecter l'API Realtime d'OpenAI à Twilio ?
Dirigez un appel Twilio entrant vers un TwiML <Connect><Stream> qui ouvre un WebSocket vers votre serveur, puis relayez l'audio entre Twilio et le socket Realtime. Rééchantillonnez le 8 kHz μ-law de Twilio vers le 24 kHz PCM16 de l'API dans les deux sens, sinon l'audio sort déformé.
Comment fonctionnent les appels de fonctions dans l'API Realtime ?
Vous déclarez les outils dans la configuration de session. Quand le modèle en veut un, il émet un événement function_call_arguments.done. Vous exécutez le travail, renvoyez le résultat comme élément de conversation function_call_output, puis envoyez response.create pour que l'agent énonce le résultat. Oublier cette dernière étape explique pourquoi les outils échouent souvent « en silence ».
Comment gérer les interruptions (barge-in) dans l'API Realtime ?
Quand la détection de tour signale input_audio_buffer.speech_started pendant la lecture, envoyez response.cancel pour arrêter la réponse active et videz tout audio de sortie en tampon vers l'appelant. Associez-le à semantic_vad pour que les pauses naturelles ne déclenchent pas de fausses interruptions en pleine phrase.
Quelle fréquence d'échantillonnage audio l'API Realtime d'OpenAI utilise-t-elle ?
L'API Realtime utilise de l'audio 24 kHz PCM16 dans les deux sens. Les fournisseurs de téléphonie comme Twilio livrent du 8 kHz μ-law, donc un pont téléphonique doit rééchantillonner vers le haut à l'entrée et vers le bas à la sortie. Des fréquences d'échantillonnage discordantes sont la cause la plus fréquente d'audio déformé.
Combien coûte le fonctionnement d'un agent vocal sur l'API Realtime ?
Le coût est piloté par les minutes audio d'entrée et de sortie sur gpt-realtime-2, et l'économie du bring-your-own-key diffère fortement d'une plateforme gérée à la minute. Nous avons fait le calcul complet dans notre analyse de tarification des agents vocaux plutôt que de l'estimer ici.
Dois-je construire sur l'API Realtime ou utiliser Retell, Vapi ou Bland ?
Construisez directement quand vous voulez un contrôle maximal et le coût par minute le plus bas et que vous pouvez posséder reconnexions, téléphonie et conformité. Achetez une plateforme gérée quand la rapidité de lancement compte davantage. Notre comparatif Retell vs Vapi vs Bland et le cadre build-vs-buy couvrent les compromis.
Qu'a changé la mise à jour de l'OpenAI Agents SDK d'avril 2026 pour les agents vocaux ?
La refonte du 15 avril 2026 a rendu le Model Context Protocol natif, a ajouté un bac à sable conteneurisé pour le code des outils et a transformé les passations entre sous-agents en primitive d'exécution. Pour les agents vocaux, cela signifie qu'un agent routeur peut passer la main à des sous-agents spécialisés au lieu d'entasser chaque outil dans un prompt.