Aller au contenu principal
Leçon 5 sur 18 · API Temps Réel et Agents Vocaux

Interruptions et gestion du tour de parole

Leçon 5 sur 18 Mis à jour le 3 novembre 2026 Développeur
Sur cette page
  1. Interruptions et gestion du tour de parole
  2. Voice Activity Detection (VAD)
  3. Gérer les interruptions
  4. Mode de contrôle manuel
  5. Architecture de gestion d’état
  6. Points clés à retenir

Mise à jour du 2 novembre 2026 — modèles audio. Plusieurs modèles de ce cours sont dépréciés : tts-1, tts-1-hd et gpt-4o-mini-tts s’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-realtime et 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-transcribe et /v1/audio/translations le 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.update plaçaient turn_detection à la racine de la session, forme de l’ancienne bêta : avec l’API Realtime actuelle, le réglage se trouve dans session.audio.input.turn_detection, et la session porte "type": "realtime". La gestion d’interruption envoyait response.cancel alors 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 aussi semantic_vad, create_response et interrupt_response, et distingue WebSocket et WebRTC. Formes d’événements vérifiées contre les types du SDK openai 3.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 :

  1. Arrêter la lecture tout de suite et jeter l’audio en attente, en notant combien de millisecondes l’utilisateur a réellement entendues
  2. 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_vad par défaut) se règle dans session.audio.input.turn_detection
  • semantic_vad découpe selon le sens des mots ; eagerness rè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