Interruptions et gestion du tour de parole
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 mode server_vad est le mécanisme par défaut. Le serveur analyse l’audio entrant en continu et détecte automatiquement le début et la fin de la parole de l’utilisateur.
await ws.send(json.dumps({
"type": "session.update",
"session": {
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"prefix_padding_ms": 300,
"silence_duration_ms": 500
}
}
}))
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 (200 ms) déclenchera la réponse pendant les pauses naturelles de l’utilisateur. Trop long (2000 ms) donnera l’impression que l’agent ne réagit pas. La valeur de 500 à 700 ms est un bon compromis pour le français.
Gérer les interruptions
Quand l’utilisateur parle pendant que l’agent répond, le serveur émet input_audio_buffer.speech_started. C’est le signal que l’utilisateur interrompt. Vous devez alors :
- Arrêter la lecture audio de la réponse en cours
- Tronquer la réponse dans l’historique de conversation
async def handle_interruption(ws, event, player, current_item_id):
"""Gère l'interruption de l'utilisateur."""
if event["type"] == "input_audio_buffer.speech_started":
print("Interruption détectée !")
# 1. Arrêter la lecture audio immédiatement
player.stop_stream()
# 2. Annuler la réponse en cours
await ws.send(json.dumps({
"type": "response.cancel"
}))
# 3. Tronquer l'élément de conversation
if current_item_id:
await ws.send(json.dumps({
"type": "conversation.item.truncate",
"item_id": current_item_id,
"content_index": 0,
"audio_end_ms": int(played_duration_ms)
}))
# 4. Redémarrer la lecture pour la prochaine réponse
player.start_stream()
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. Le modèle penserait avoir tout dit et ne répéterait pas l’information. En tronquant, vous alignez l’historique avec ce que l’utilisateur a réellement entendu.
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": {
"turn_detection": None
}
}))
# Quand vous décidez que l'utilisateur a fini de parler :
await ws.send(json.dumps({
"type": "input_audio_buffer.commit"
}))
# Puis déclencher la réponse :
await ws.send(json.dumps({
"type": "response.create"
}))
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.audio.delta":
self.state = ConversationState.AGENT_SPEAKING
elif event_type == "response.done":
self.state = ConversationState.IDLE
Points clés à retenir
- Le VAD (
server_vad) détecte automatiquement début et fin de parole avec des seuils configurables silence_duration_msentre 500 et 700 ms convient au français- Lors d’une interruption, annulez la réponse ET tronquez l’historique pour la cohérence
- Le mode manuel (
turn_detection: null) convient aux menus IVR et à la dictée - Un automate d’état explicite évite les comportements incohérents dans les cas limites