Voice Agents — OpenAI Realtime API
Phase 4. The breakout AI engineering niche in 2026.
Why voice in 2026
- OpenAI Realtime API matured (Cedar/Marin voices, low latency, speech-to-speech)
- ElevenLabs Flash v2.5 hits ~75ms TTS latency
- LiveKit + WebRTC = production-grade transport (jitter buffer, packet loss recovery, echo cancellation)
- Phone agents replacing call-center workflows is happening NOW in FR startups
- TJM premium : €150-275/hour OR fixed-price €20-50k/project
- Most accessible high-value niche for a TS/Python dev
Note de cadrage. Ce fichier couvre la stack voix de référence : OpenAI Realtime (speech-to-speech) et le pipeline STT → LLM → TTS. Le code et les identifiants restent dans l'écosystème de chaque provider. Quand on cite un LLM dans le pipeline alternatif, on utilise les faits canoniques 2026 (voir la section pricing). On ne réécrit PAS le code OpenAI Realtime en SDK Anthropic — ce serait un contresens technique.
Le modèle mental du staff engineer
Avant le code, il faut une carte mentale claire. Un agent vocal n'est pas « un chatbot avec un micro ». C'est un système temps-réel souple où la latence perçue, l'interruptibilité et la robustesse réseau dominent la qualité du LLM. Trois axiomes :
La latence est le produit. En texte, 2s de réponse passe inaperçu. En voix, au-delà de ~800ms de silence après que l'utilisateur a fini de parler, la conversation « casse » — l'humain croit que l'agent n'a pas compris et répète. Le budget de latence end-to-end (fin de parole utilisateur → premier phonème audible) est votre métrique nord. Visez p50 < 800ms, p95 < 1.5s.
Le tour de parole (turn-taking) est un problème de systèmes, pas de ML. Savoir QUAND l'utilisateur a fini de parler (endpointing), gérer les interruptions (barge-in), ne pas couper l'utilisateur qui réfléchit — c'est de la détection d'activité vocale (VAD) + de la machine à états, pas du prompt engineering.
Le speech-to-speech (S2S) et le pipeline STT→LLM→TTS sont deux architectures, pas deux implémentations. Elles ont des courbes coût/latence/contrôle radicalement différentes. Choisir, c'est arbitrer. La section Architecture détaille cet arbitrage.
Le budget de latence, décomposé
Fin de parole utilisateur
│
├─ Endpointing (VAD décide "il a fini") ~ 200-500 ms ← tunable, dominant
├─ Transport audio → backend (WebRTC) ~ 20-50 ms
├─ STT (si pipeline) / rien (si S2S) ~ 100-300 ms (pipeline only)
├─ Time-to-first-token du LLM ~ 200-600 ms ← dominé par le modèle + thinking
├─ TTS time-to-first-byte (si pipeline) ~ 75-200 ms (pipeline only)
└─ Transport audio → utilisateur (WebRTC) ~ 20-50 ms
▼
Premier phonème audibleLe staff engineer lit ce tableau et sait immédiatement où agir : l'endpointing et le TTFT du LLM sont les deux leviers dominants. Réduire la latence STT de 50ms quand votre endpointing attend 500ms de silence, c'est polir une roue alors que le moteur fume.
Architecture
User microphone
↓ (WebRTC : jitter buffer, AEC, packet-loss concealment)
LiveKit room (transport)
↓
Backend agent (machine à états — voir plus bas)
├── OpenAI Realtime API (speech-to-speech)
│ OR
├── STT (Deepgram/Whisper) → LLM → TTS (ElevenLabs/OpenAI)
└── Tool use (databases, APIs, MCP servers)
↓
User speakerS2S vs pipeline — le tableau d'arbitrage
| Critère | Speech-to-speech (Realtime) | Pipeline STT→LLM→TTS |
|---|---|---|
| Latence | La plus basse (un seul hop, pas de re-synthèse) | Plus haute, mais maîtrisable composant par composant |
| Coût/min | Élevé (~3$/10min, voir pricing) | Bas (~0.50$/min) |
| Contrôle du texte | Faible — on n'a pas toujours le transcript exact avant l'audio | Total — on voit/loggue/filtre le texte du LLM |
| Prosodie / émotion | Excellente (le modèle « parle » nativement) | Dépend du TTS ; bonne avec ElevenLabs |
| Choix du LLM | Verrouillé au modèle du provider Realtime | Libre — n'importe quel LLM |
| Modération / compliance | Difficile (audio sort directement) | Facile (gate sur le texte avant TTS) |
| Function calling | Supporté, mais ajoute de la latence dans la boucle | Supporté, dans la boucle LLM classique |
| Observabilité | Plus opaque (audio bout-en-bout) | Chaque étape est instrumentable |
| Débogage | Difficile (« pourquoi a-t-il dit ça ? ») | Reproductible (le transcript est là) |
Comment un staff engineer choisit :
- S2S quand la latence et le naturel priment, le LLM imposé convient, et le volume est faible/moyen (démo, conciergerie premium, assistant personnel).
- Pipeline quand le coût compte à l'échelle, qu'il faut un LLM précis (le vôtre, fine-tuné, ou un modèle moins cher), de la modération réglementaire (santé, finance), ou une observabilité forte (centre d'appels audité).
- Hybride existe : S2S pour le small-talk, bascule vers pipeline + LLM spécialisé quand la conversation devient transactionnelle. Rare, mais c'est la réponse « senior » en entretien.
OpenAI Realtime API — les bases
- Speech-to-speech : pas de STT/TTS séparés. Le LLM entend l'audio, répond en audio.
- Modèle :
gpt-realtime(ou son successeur — vérifier l'ID courant dans la doc OpenAI). - Voix : alloy, echo, fable, onyx, nova, shimmer + plus récentes (Cedar, Marin).
- Latence : 200-500ms typique pour le TTFT audio.
- Interruption : l'utilisateur peut parler par-dessus ; le modèle s'arrête proprement (barge-in via VAD).
Connexion
WebSocket OU WebRTC.
- WebRTC = latence plus basse, gestion native du réseau dégradé (jitter, perte de paquets), echo cancellation côté client. Recommandé pour le navigateur et le mobile, via LiveKit qui encapsule tout ça.
- WebSocket = plus simple à câbler côté serveur, mais c'est À VOUS de gérer le buffering audio, le découpage en frames, et vous payez la latence d'un transport non optimisé pour l'audio. Acceptable pour un backend-to-backend (téléphonie via SIP trunk → votre serveur → OpenAI), pas pour un client final sur réseau mobile.
Règle senior : navigateur/mobile → WebRTC (LiveKit). Backend/téléphonie → WebSocket peut suffire, mais mesurez la gigue.
Pricing reality check
OpenAI Realtime est cher par minute. Budget (à vérifier — les prix audio bougent vite) :
- ~$0.06/min audio input + $0.24/min audio output (approx, vérifier le tarif courant)
- Appel de 10 min ≈ ~$3
- À 10 000 appels/mois de 10 min → ~30 000$/mois rien qu'en Realtime. C'est le genre de chiffre qui tue un business model si on ne l'a pas modélisé AVANT de vendre.
Comment un staff engineer modélise un coût voix. Ne jamais raisonner « au feeling ». On décompose en coût par minute, par composant, puis on multiplie par le volume. Le piège classique : oublier que la voix est bidirectionnelle et asymétrique (l'audio input et output ne coûtent pas pareil), et que le LLM facture en tokens, pas en minutes — il faut un facteur de conversion (≈ 150 mots/min de parole ≈ ~200 tokens/min en français, à mesurer sur vos vrais transcripts avec un compteur de tokens, jamais à l'estimation).
Alternative : pipeline STT → LLM → TTS
Moins cher, plus de contrôle :
- STT : Deepgram Nova-3 (~$0.0043/min) ou Whisper local (gratuit mais GPU à opérer)
- LLM : au choix. Si vous voulez du Claude dans la boucle, la cible 2026 est Claude Haiku 4.5 (
claude-haiku-4-5, 1$/5$ par M tok input/output) pour la latence/coût en conversation, ou Claude Sonnet 4.6 (claude-sonnet-4-6, 3$/15$) quand la qualité de raisonnement prime. Le flagship Claude Opus 4.8 (claude-opus-4-8, 5$/25$) est généralement trop lent/cher pour le chemin temps-réel d'un agent vocal — réservez-le à des tâches asynchrones hors boucle. - TTS : ElevenLabs Flash v2.5 ($0.30/1000 chars, ~$0.40/min, ~75ms TTFB)
Total ~$0.50/min au lieu de ~$3/min. Mais plus de latence à orchestrer, et trois fournisseurs à monitorer au lieu d'un.
Mettre un LLM Anthropic dans la boucle (le chemin temps-réel correct)
Le réflexe senior pour le LLM du pipeline, en voix, tient en une phrase : AsyncAnthropic, streaming obligatoire, thinking désactivé par défaut, prompt système caché. Détaillons chaque choix — chacun coûte ou gagne de la latence perçue.
AsyncAnthropic: un agent vocal est un serveur qui gère N appels concurrents. Le client sync bloque l'event loop ; vous voulezasynciode bout en bout (un appel lent ne doit pas geler les autres conversations).- Streaming obligatoire : vous attaquez le TTS dès la première frontière de phrase (voir « streaming chaîné » plus haut). Sans streaming, vous attendez la réponse complète du LLM avant le moindre phonème — c'est le pire bug de latence en voix.
- Modèle :
claude-haiku-4-5(1$/5$ par M tok) pour la latence/coût conversationnels,claude-sonnet-4-6(3$/15$) quand le raisonnement prime. Le flagshipclaude-opus-4-8(5$/25$) est trop lent/cher pour la boucle temps-réel — réservez-le à l'asynchrone hors boucle (résumé d'appel, post-traitement, agent de supervision). - Thinking désactivé par défaut : le thinking ajoute de la latence (le modèle « réfléchit » avant de répondre). Pour du small-talk vocal, c'est mortel. Activez
thinking: {type: "adaptive"}uniquement sur les tours transactionnels qui le justifient (raisonnement multi-étapes, arbitrage). Le budget de tokensbudget_tokensest retiré sur 4.7/4.8 (HTTP 400) — si vous devez plafonner la profondeur, c'estoutput_config.effort(lowpour la voix), pas un budget. cache_controlsur le préfixe stable (system prompt + définitions de tools) : à chaque tour vocal vous renvoyez tout l'historique. Sans cache, vous re-payez le contexte plein toute la conversation. Le cache lit le préfixe à ~0.1× — sur un appel de 20 tours, c'est un facteur de coût ET de latence (TTFT plus bas car moins de contexte à traiter).
import os
from anthropic import AsyncAnthropic
client = AsyncAnthropic() # lit ANTHROPIC_API_KEY ; max_retries=2 par défaut
SYSTEM = [{
"type": "text",
"text": "Tu es un agent vocal de prise de RDV. Réponds en une à deux phrases "
"courtes, jamais de listes, ton naturel et oral.",
"cache_control": {"type": "ephemeral"}, # préfixe stable → caché, lu à ~0.1×
}]
async def stream_reply(history, tts):
"""history = liste de messages user/assistant ; tts = client TTS streaming."""
buffer = ""
async with client.messages.stream(
model="claude-haiku-4-5", # latence/coût pour la voix
max_tokens=300, # réponses vocales courtes
system=SYSTEM, # caché via cache_control
messages=history,
# thinking volontairement absent → désactivé (latence minimale pour le small-talk)
) as stream:
async for text in stream.text_stream:
buffer += text
if ends_sentence(buffer): # frontière de phrase → attaque le TTS tout de suite
await tts.synthesize_streaming(buffer)
buffer = ""
if buffer.strip():
await tts.synthesize_streaming(buffer)
final = await stream.get_final_message()
log.info("turn_cost", usage=final.usage) # loggez resp.usage pour le coût/tour
return finalPoints senior souvent ratés :
resp.usageà chaque tour :input_tokens,cache_read_input_tokens,output_tokens. Sans ça, pas de coût/appel live — vous le découvrez sur la facture (voir Observabilité).- Exceptions typées + timeout : enveloppez l'appel avec un timeout par tour et attrapez
RateLimitError/OverloadedError/APITimeoutError(le SDK retry déjà 429/5xx avec backoff viamax_retries). En voix, un appel LLM qui pend = silence mort → jouez un filler ou dégradez gracieusement (« je n'ai pas bien saisi, vous pouvez répéter ? ») plutôt que d'attendre. - Tools parallèles : si le modèle demande plusieurs tools dans le même tour, exécutez-les avec
asyncio.gather, jamais en série (voir Function calling). thinkingréactivé sélectivement : un routeur léger en amont peut classer le tour (small-talk vs transactionnel) et n'allumerthinking: {type: "adaptive"}que sur le second. C'est la réponse « architecte » : on ne paie la latence du raisonnement que là où elle achète quelque chose.
Streaming bout-en-bout : le détail qui fait ou défait la latence
Dans un pipeline, la naïveté tue. Le piège classique : attendre la fin de la réponse du LLM avant d'appeler le TTS. Vous additionnez alors latence LLM complète + latence TTS complète.
La version senior chaîne les flux :
- STT émet un transcript partiel/final dès que l'utilisateur a fini (endpointing).
- Le LLM stream ses tokens. Dès qu'une frontière de phrase est détectée (
.,?,!, ou ~10 mots), on envoie ce segment au TTS — sans attendre la suite. - Le TTS stream l'audio de ce segment pendant que le LLM génère le suivant.
- On joue l'audio segment par segment.
Résultat : le premier phonème sort après TTFT_LLM + TTFB_TTS du premier segment, pas après la réponse entière. C'est souvent un facteur 3-5x sur la latence perçue.
# Pseudo-code de la boucle de streaming chaîné (pipeline)
async def respond(transcript: str, tts, llm_stream):
buffer = ""
async for token in llm_stream(transcript):
buffer += token
if ends_sentence(buffer): # frontière de phrase
await tts.synthesize_streaming(buffer) # attaque le TTS tout de suite
buffer = ""
if buffer.strip():
await tts.synthesize_streaming(buffer)Function calling en Realtime
Identique au texte — les tools peuvent être appelés pendant la conversation :
session.update({
"tools": [
{"type": "function", "name": "book_appointment", "parameters": {...}},
{"type": "function", "name": "check_availability", "parameters": {...}},
],
"tool_choice": "auto"
})Quand le LLM appelle un tool, vous exécutez, renvoyez le résultat, la conversation continue.
Le problème caché : le tool call est un trou de latence. Pendant l'exécution du tool (requête DB, appel API externe), l'agent est silencieux. 800ms de silence = l'utilisateur croit que ça a planté. Patterns senior :
- Filler audio : dès le tool call, jouer un « un instant, je vérifie ça… » pré-enregistré ou synthétisé. Ça achète 1-2s de tolérance.
- Tools parallèles : si le modèle demande
check_availabilityETget_customer_record, lancez-les en parallèle (asyncio.gather), pas en série. - Timeout dur sur chaque tool (ex. 3s) avec dégradation gracieuse : « je n'arrive pas à accéder à l'agenda là, je vous rappelle ? » plutôt qu'un silence infini.
- Tools idempotents et réversibles côté voix : un utilisateur peut interrompre en plein milieu d'un
book_appointment. Concevez pour que l'interruption n'ait pas réservé à moitié.
VAD (Voice Activity Detection) et endpointing
La VAD détecte quand l'utilisateur commence/arrête de parler. L'endpointing (décider que le tour est fini) est la décision la plus sensible de tout l'agent.
- Server-side VAD : intégré à l'API Realtime. Simple, mais le silence transite par le réseau avant la décision.
- Client-side VAD : tourne sur l'appareil de l'utilisateur, latence plus basse, mais à vous de l'implémenter (LiveKit fournit des composants).
Deux paramètres dominent :
threshold: sensibilité au son. Trop bas → bruit de fond déclenche des faux tours. Trop haut → on rate les utilisateurs qui parlent doucement.silence_duration_ms: combien de silence avant de conclure « il a fini ». C'est l'arbitrage central. Trop court (200ms) → on coupe l'utilisateur qui fait une pause pour réfléchir. Trop long (1000ms) → la conversation traîne, l'agent paraît lent.
Le piège des faux endpoints. Un humain qui dit « mon numéro de commande c'est… [pause de réflexion] …4 5 7 » sera coupé par un endpointing à 300ms. Un agent vocal de qualité utilise un endpointing sémantique/adaptatif : plus long après un chiffre ou une hésitation (« euh »), plus court après une phrase grammaticalement complète. C'est ce qui sépare une démo d'un produit.
Failure modes : ce qui casse en production (et pas en démo)
| Mode de défaillance | Symptôme | Mitigation |
|---|---|---|
| Pics de latence | Réseau, warm-up modèle, tool calls lents | Loguer chaque étape avec timestamps ; alerter sur p95 |
| Bruit de fond | VAD se déclenche tout seul, faux tours | Tester en conditions réelles (rue, open-space), pas en bureau silencieux ; AEC côté client |
| Multi-locuteurs | Deux personnes parlent, l'agent mélange | Détecter et soit gérer poliment, soit échouer explicitement (« je n'entends qu'une personne à la fois ») |
| Conversations longues | La fenêtre de contexte se remplit, l'agent « oublie » le début | Tronquer/résumer avec soin ; garder le system prompt et les N derniers tours |
| Qualité audio | 16kHz vs 24kHz change la précision STT | Fixer le sample rate côté capture ; tester la chaîne complète |
| Déconnexions | Réseau mobile coupe en plein appel | Logique de reconnexion + préservation d'état (où en était la conversation ?) |
| Barge-in raté | L'agent continue de parler quand l'utilisateur l'interrompt | VAD doit pouvoir annuler la génération en cours (cancel du stream LLM + flush du buffer TTS) |
| Hallucination de tool | Le modèle « confirme » une réservation qu'il n'a pas faite | Ne jamais faire confiance au texte du modèle pour un effet de bord ; vérifier le retour réel du tool |
| Coût qui explose | Facture provider 10x le budget | Cap de durée d'appel, monitoring du coût par appel en temps réel, alerte budgétaire |
La machine à états : structurer l'agent
Un agent vocal sérieux n'est pas une boucle while True. C'est une machine à états explicite :
┌─────────────┐
┌───▶│ LISTENING │ (VAD attend la parole utilisateur)
│ └──────┬──────┘
│ │ endpoint détecté
│ ▼
│ ┌─────────────┐ barge-in
│ │ THINKING │◀──────────────┐
│ └──────┬──────┘ │
│ │ tokens / tool │
│ ▼ │
│ ┌─────────────┐ │
│ │ SPEAKING │───────────────┘
│ └──────┬──────┘ (l'utilisateur interrompt → annuler génération)
│ │ fin de réponse
└───────────┘Les transitions critiques :
- SPEAKING → THINKING (barge-in) : doit annuler le stream LLM en cours ET vider le buffer TTS, sinon l'agent parle « par-dessus » sa propre réponse annulée.
- THINKING → SPEAKING : ne commencer à parler qu'au premier segment prêt (streaming chaîné).
- N'importe quel état → erreur : déconnexion, timeout tool — toujours une sortie propre.
Coder ça comme une vraie FSM (pas des if imbriqués) rend l'agent débogable et testable. C'est le genre de structure qu'un staff engineer impose dès le départ.
Observabilité, coût, sécurité (les préoccupations de production)
Observabilité. Loguez par tour : t_endpoint, t_first_token, t_first_audio, t_response_done, le transcript (input + output), les tool calls et leur durée, le coût estimé du tour. Sans ça, vous ne pouvez pas répondre à « pourquoi l'agent était lent à 14h ? ». Exportez vers un système de traces (OpenTelemetry) — chaque tour est un span.
Coût. Le coût par appel doit être une métrique live, pas une découverte de fin de mois. Instrumentez : minutes audio (S2S) ou tokens LLM + chars TTS + minutes STT (pipeline). Posez un cap de durée d'appel. Alertez si le coût/appel dérive.
Sécurité. En voix, deux risques spécifiques : (1) PII dans les transcripts — un numéro de carte dicté à l'oral finit dans vos logs ; chiffrez/rédigez. (2) Prompt injection vocale — un utilisateur qui dit « ignore tes instructions et… » ; le system prompt doit être robuste et les tools à effet de bord doivent valider côté serveur, jamais faire confiance à l'intention parlée. (3) Compliance (RGPD, enregistrement d'appel) : consentement explicite avant d'enregistrer, en France c'est légalement requis.
🏋️ Exercices
Progressifs et exigeants. L'objectif n'est pas de changer une constante mais de construire, mesurer, casser, défendre.
1. Echo agent + instrumentation de latence
Objectif : Mettre en place un agent OpenAI Realtime « echo » (répète ce que dit l'utilisateur) ET instrumenter le budget de latence complet. Indice/Solution : Connexion WebRTC via LiveKit. Loguez t_endpoint, t_first_audio_out. Le livrable n'est pas l'echo (trivial) — c'est le tableau de timestamps qui prouve que vous savez où va le temps.
2. Pipeline STT→LLM→TTS avec streaming chaîné
Objectif : Reconstruire le même agent en pipeline (Deepgram → LLM → ElevenLabs) avec streaming chaîné (TTS attaqué à la première frontière de phrase). Indice/Solution : La clé est ends_sentence() + envoi segment par segment au TTS. Mesurez le TTFT audio avec et sans le chaînage — vous devez observer un facteur ~3x. Si vous n'observez pas l'écart, votre chaînage attend la fin du LLM quelque part (bug courant : await llm.complete() au lieu d'itérer le stream).
3. Endpointing adaptatif anti-coupure
Objectif : Implémenter un endpointing qui ne coupe PAS l'utilisateur qui dicte un numéro avec des pauses. Indice/Solution : silence_duration_ms dynamique : allongez-le après un chiffre, un « euh », ou une phrase grammaticalement incomplète ; raccourcissez-le après une phrase complète. Testez avec : « mon numéro c'est… [1.5s] …quatre cinq sept ». Un endpointing fixe à 400ms échoue ce test ; le vôtre doit le passer.
4. Barge-in correct (casser puis réparer)
Objectif : L'utilisateur interrompt l'agent en plein milieu d'une phrase ; l'agent doit s'arrêter NET (pas finir sa phrase), annuler la génération LLM, et écouter. Indice/Solution : D'abord, cassez-le volontairement : sans annulation, l'agent finit sa réponse annulée par-dessus la nouvelle. Puis réparez : sur détection de parole en état SPEAKING, cancel() le stream LLM et flush() le buffer TTS, transition → LISTENING. Vérifiez qu'aucun audio « zombie » ne sort après l'interruption.
5. Tool call sans trou de latence (production-grade)
Objectif : Ajouter un tool check_availability qui prend 2s, SANS que l'utilisateur entende 2s de silence. Indice/Solution : Filler audio (« je vérifie ça… ») joué dès le tool call, tool avec timeout de 3s + dégradation gracieuse, et si plusieurs tools : asyncio.gather. Cassez-le : déclenchez l'interruption pendant le tool call et vérifiez qu'on ne réserve pas à moitié (idempotence).
6. Défendre le chiffre (architecture + business)
Objectif : On vous demande un agent de prise de RDV, 50 000 appels/mois, 4 min moyens. Choisir S2S ou pipeline, et défendre le coût mensuel chiffré. Indice/Solution : Posez la table de coût/minute composant par composant, puis multipliez par 50 000 × 4 = 200 000 min/mois.
- S2S (Realtime) : ~$0.30/min audio moyen (mix input/output) →
200 000 × 0.30 ≈ 60 000$/mois. - Pipeline : STT Deepgram ~$0.0043/min + TTS ElevenLabs ~$0.40/min + LLM Haiku 4.5. Le LLM : à ~200 tokens/min en sortie et un contexte qui grossit, comptez l'output (
5$/M) + l'input caché (~0.1×grâce àcache_control) — soit grossièrement ~$0.01–0.03/min selon la longueur de contexte. Total ≈ ~$0.42–0.45/min →200 000 × 0.43 ≈ 86 000$/mois… plus cher que le S2S ici, car le TTS premium domine. Recalculez avec un TTS moins cher (OpenAI TTS, ou ElevenLabs Flash sur volume négocié) : le pipeline repasse devant. - Le vrai enseignement : le composant dominant du pipeline n'est PAS le LLM — c'est le TTS. Optimiser le LLM quand le TTS pèse 0.40$/min, c'est polir la roue pendant que le moteur fume (même réflexe que le budget de latence). Défendez votre arbitrage avec la table chiffrée, le composant dominant identifié, et le levier d'optimisation nommé — pas avec « le pipeline est moins cher en général ».
🎤 En entretien
Q : Comment réduiriez-vous la latence perçue d'un agent vocal ? R : Je cible les deux leviers dominants — l'endpointing (silence_duration_ms adaptatif) et le TTFT du LLM (streaming + premier segment au TTS dès la première frontière de phrase) ; réduire la latence STT/transport est secondaire tant que ces deux-là ne sont pas optimisés.
Q : S2S ou pipeline STT→LLM→TTS — comment choisissez-vous ? R : Arbitrage latence/coût/contrôle : S2S pour le naturel et la latence basse à faible volume avec LLM imposé ; pipeline quand le coût à l'échelle, un LLM précis, la modération réglementaire ou l'observabilité priment — le pipeline rend chaque étape instrumentable et le texte filtrable avant l'audio.
Q : Comment gérez-vous un utilisateur qui interrompt l'agent (barge-in) ? R : Machine à états avec transition SPEAKING → LISTENING sur détection VAD, qui annule le stream LLM en cours ET flush le buffer TTS — sinon l'agent parle par-dessus sa réponse annulée. C'est un problème de systèmes (cancellation propre), pas de modèle.
Q : Quel est le risque sécurité spécifique à la voix qu'un agent texte n'a pas ? R : La PII dictée à l'oral (numéro de carte, sécurité sociale) atterrit dans les transcripts/logs — il faut rédaction/chiffrement — plus le consentement légal à l'enregistrement (RGPD/Code de procédure pénale en France) ; et la prompt injection vocale, donc les tools à effet de bord valident côté serveur, jamais sur l'intention parlée.
Q : Comment chiffrez-vous le coût d'un agent vocal en pipeline, et quel composant domine ? R : On décompose en coût/minute par composant (STT + LLM + TTS), on convertit le LLM tokens→minutes en mesurant les vrais transcripts (jamais à l'estimation), puis on multiplie par le volume. Le composant dominant est presque toujours le TTS (~0.40$/min en premium), pas le LLM — donc on cache le préfixe système (cache_control, lecture à ~0.1×) et on choisit un modèle voix bon marché (Haiku 4.5), mais le vrai levier de coût est le TTS, pas le LLM.
Q : Pourquoi désactiver le thinking du LLM par défaut sur le chemin vocal, et quand le réactiver ? R : Le thinking ajoute de la latence avant le premier token, ce qui casse le naturel en small-talk. On le laisse désactivé par défaut et on l'active sélectivement (thinking: {type: "adaptive"}) sur les tours transactionnels via un routeur léger en amont — on ne paie la latence du raisonnement que là où elle achète de la qualité. Sur 4.7/4.8, budget_tokens est retiré (HTTP 400) ; le plafond de profondeur, c'est output_config.effort.
Resources
- OpenAI Realtime API docs : platform.openai.com/docs/guides/realtime
- OpenAI Realtime examples : github.com/openai/openai-realtime-console
- LiveKit Agents : docs.livekit.io/agents
- Deepgram STT : developers.deepgram.com
- ElevenLabs TTS : elevenlabs.io/docs
- Production guide : Forasoft — Realtime API 2026 guide