Interruptions et gestion du tour de parole
Sur cette page
Mise à jour du 2 novembre 2026 — modèles audio. Plusieurs modèles de ce cours sont dépréciés :
tts-1,tts-1-hdetgpt-4o-mini-ttss’arrêtent le 6 janvier 2027 (remplaçant indiqué :gpt-realtime-2.1-mini, que le guide TTS d’OpenAI déclare non pris en charge par/v1/audio/speech: la migration de la synthèse vocale passe par l’API Realtime) ;gpt-realtime,gpt-4o-realtimeet les anciens modèles audio le 20 janvier 2027 (gpt-realtime-2.1,gpt-realtime-2.1-mini,gpt-audio-1.5) ;whisper-1,gpt-4o-transcribeet/v1/audio/translationsle 26 février 2027 (gpt-transcribe,gpt-live-transcribe,v1/realtime/translations). Pour la voix full-duplex, GPT-Live 1 est disponible dans l’API depuis le 10 septembre 2026.
Corrigé le 3 novembre 2026. Les
session.updateplaçaientturn_detectionà la racine de la session, forme de l’ancienne bêta : avec l’API Realtime actuelle, le réglage se trouve danssession.audio.input.turn_detection, et la session porte"type": "realtime". La gestion d’interruption envoyaitresponse.cancelalors qu’avec le VAD le serveur annule lui-même la réponse, et utilisait une variable jamais définie. La leçon présente aussisemantic_vad,create_responseetinterrupt_response, et distingue WebSocket et WebRTC. Formes d’événements vérifiées contre les types du SDKopenai3.42.1. Sources : Voice activity detection · Realtime conversations.
Interruptions et gestion du tour de parole
Dans une conversation naturelle, les interlocuteurs s’interrompent, parlent en même temps, se coupent la parole. Un agent vocal qui ne gère pas ces situations sera perçu comme robotique et frustrant. La Realtime API propose deux mécanismes pour gérer le tour de parole : la détection vocale automatique (VAD) et le contrôle manuel.
Voice Activity Detection (VAD)
Le VAD est activé par défaut dans une session de conversation vocale, en mode server_vad : le serveur analyse l’audio entrant en continu, découpe sur les silences et signale le début et la fin de la parole de l’utilisateur. Il se règle dans session.audio.input.turn_detection :
await ws.send(json.dumps({
"type": "session.update",
"session": {
"type": "realtime",
"audio": {
"input": {
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 300,
"silence_duration_ms": 500,
"create_response": True, # répondre dès la fin du tour
"interrupt_response": True, # couper l'agent si l'utilisateur parle
}
}
},
},
}))
Paramètres du VAD
| Paramètre | Description | Impact |
|---|---|---|
| threshold | Seuil de détection (0.0 à 1.0) | Haut = moins sensible, bas = plus sensible |
| prefix_padding_ms | Audio conservé avant la détection | Évite de couper le début des mots |
| silence_duration_ms | Durée de silence pour fin de tour | Court = réactif, long = patient |
Un silence_duration_ms trop court déclenchera la réponse pendant les pauses naturelles de l’utilisateur ; trop long, il donnera l’impression que l’agent ne réagit pas. OpenAI ne publie pas de valeur par langue : partez de l’exemple de la documentation (500 ms) et ajustez sur des enregistrements de vos utilisateurs. Un threshold plus élevé exige un son plus fort pour déclencher, ce qui aide en environnement bruyant.
Le VAD sémantique
Le second mode, semantic_vad, ne se fie pas au silence mais aux mots : un classifieur estime si l’utilisateur a fini sa phrase. Une phrase qui reste en suspens laisse plus de temps qu’une affirmation nette, et le modèle coupe moins souvent la parole. Son réglage eagerness vaut low (laisser l’utilisateur prendre son temps), medium, high (répondre au plus tôt) ou auto, la valeur par défaut, équivalente à medium :
await ws.send(json.dumps({
"type": "session.update",
"session": {
"type": "realtime",
"audio": {"input": {"turn_detection": {"type": "semantic_vad", "eagerness": "low"}}},
},
}))
Gérer les interruptions
Quand l’utilisateur parle pendant que l’agent répond, le serveur émet input_audio_buffer.speech_started. Avec le VAD et interrupt_response activé, c’est le serveur qui annule la réponse en cours : il envoie ensuite response.done avec le statut cancelled. Il reste deux choses à faire de votre côté, sur une connexion WebSocket où c’est vous qui jouez l’audio :
- Arrêter la lecture tout de suite et jeter l’audio en attente, en notant combien de millisecondes l’utilisateur a réellement entendues
- Tronquer l’élément de l’agent à cette durée, avec
conversation.item.truncate
N’attendez pas response.done pour faire l’un ou l’autre.
async def gerer_interruption(ws, event: dict, lecteur, item_id: str | None, ms_jouees: int) -> None:
"""Gère une prise de parole de l'utilisateur pendant que l'agent parle (WebSocket)."""
if event["type"] != "input_audio_buffer.speech_started":
return
# 1. Arrêter la lecture et vider la file d'audio en attente
lecteur.stop_stream()
# 2. Retirer de l'historique la partie que l'utilisateur n'a pas entendue
if item_id:
await ws.send(json.dumps({
"type": "conversation.item.truncate",
"item_id": item_id,
"content_index": 0,
"audio_end_ms": ms_jouees,
}))
# 3. Rouvrir la lecture pour la prochaine réponse
lecteur.start_stream()
ms_jouees se calcule à partir de ce que vous avez réellement envoyé au haut-parleur : en PCM 16 bits mono à 24 kHz, 48 000 octets font une seconde. En WebRTC ou en SIP, le serveur gère lui-même la mémoire tampon de sortie, sait ce qui a été joué et tronque automatiquement : vous n’avez rien à envoyer.
Pourquoi tronquer la réponse ?
Sans troncature, l’historique de conversation contiendrait la réponse complète du modèle, alors que l’utilisateur n’en a entendu qu’une partie. Si l’utilisateur demande « c’était quoi, la dernière chose ? », le modèle croirait l’avoir dit. En tronquant, vous alignez l’historique avec ce que l’utilisateur a réellement entendu. La documentation précise une limite : la troncature coupe l’audio et retire la transcription de la partie non jouée, mais ne fournit pas de transcription tronquée au mot près.
Mode de contrôle manuel
Pour certains cas d’usage (menus IVR, dictée, quiz), le VAD automatique n’est pas adapté. Le mode manuel vous donne un contrôle total :
# Désactiver le VAD
await ws.send(json.dumps({
"type": "session.update",
"session": {"type": "realtime", "audio": {"input": {"turn_detection": None}}},
}))
# Bouton enfoncé : si l'agent parle encore, annuler sa réponse et tronquer ce qui n'a pas été joué
await ws.send(json.dumps({"type": "response.cancel"}))
# Bouton relâché : l'audio a été envoyé par input_audio_buffer.append, on valide le tour...
await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
# ...puis on déclenche la réponse
await ws.send(json.dumps({"type": "response.create"}))
Sans VAD, plus rien n’est automatique : c’est ici que response.cancel a sa place, quand une réponse est en cours au moment où l’utilisateur reprend la main. Entre deux tours, input_audio_buffer.clear vide le tampon d’entrée.
Une voie intermédiaire existe : garder le VAD, qui continue de découper les tours et d’émettre ses événements, mais passer create_response et interrupt_response à False. Aucune réponse ne part alors sans votre response.create, ce qui laisse le temps de vérifier ou d’enrichir l’entrée (modération, recherche documentaire), au prix d’un peu de latence.
Cas d’usage du mode manuel
- Menu IVR : attendre un DTMF ou un mot-clé spécifique avant de répondre
- Dictée : accumuler de longs segments audio sans interruption
- Confirmation : attendre un « oui » ou « non » explicite
- Push-to-talk : l’utilisateur appuie sur un bouton pour parler
Architecture de gestion d’état
Pour une gestion robuste du tour de parole, maintenez un automate d’état :
from enum import Enum
class ConversationState(Enum):
IDLE = "idle" # En attente
USER_SPEAKING = "user_speaking" # L'utilisateur parle
PROCESSING = "processing" # Le modèle traite
AGENT_SPEAKING = "agent_speaking" # L'agent répond
INTERRUPTED = "interrupted" # Interruption en cours
class TurnManager:
def __init__(self):
self.state = ConversationState.IDLE
self.current_response_item_id = None
self.played_audio_ms = 0
def on_event(self, event_type: str):
if event_type == "input_audio_buffer.speech_started":
if self.state == ConversationState.AGENT_SPEAKING:
self.state = ConversationState.INTERRUPTED
else:
self.state = ConversationState.USER_SPEAKING
elif event_type == "input_audio_buffer.speech_stopped":
self.state = ConversationState.PROCESSING
elif event_type == "response.output_audio.delta":
self.state = ConversationState.AGENT_SPEAKING
elif event_type == "response.done":
self.state = ConversationState.IDLE
Points clés à retenir
- Le VAD (
server_vadpar défaut) se règle danssession.audio.input.turn_detection semantic_vaddécoupe selon le sens des mots ;eagernessrègle sa patience- Avec le VAD, le serveur annule la réponse interrompue ; en WebSocket, arrêtez la lecture et tronquez avec
conversation.item.truncate(automatique en WebRTC et SIP) - Le mode manuel (
turn_detectionàNone) convient au push-to-talk, aux menus IVR et à la dictée - Un automate d’état explicite évite les comportements incohérents dans les cas limites