Smart Turn — Détection de fin de tour

Smart Turn v3.2 répond à une seule question une fois que le VAD signale une pause : la personne a-t-elle fini de parler, ou est-elle en milieu de phrase ? Il écoute l'audio lui-même — prosodie, débit, intonation — plutôt qu'une transcription, couvre 23 langues et s'exécute une fois par pause sur les 8 dernières secondes du tour. Les poids sont la version officielle Smart Turn v3.2 de Pipecat (BSD-2-Clause), compilée en CoreML pour les plateformes Apple et exportée en ONNX pour Speech Core.

Pourquoi un VAD seul ne suffit pas

Un détecteur d'activité vocale n'entend que le silence. Les gens marquent des pauses en milieu de phrase pour réfléchir ; un agent basé uniquement sur le VAD les coupe donc, ou attend un long délai fixe après chaque phrase. Smart Turn confirme chaque pause : une phrase terminée reçoit une réponse immédiate, une pause en milieu de phrase laisse l'agent écouter.

Fonctionnement

Smart Turn n'est pas un VAD et ne détecte pas la parole par lui-même. Il est associé au Silero VAD en streaming, qui décide quand poser la question. Avec un classifieur attaché, une pause confirmée devient une question plutôt qu'un verdict :

  1. Une question par pause. Quand Silero confirme une pause (minSilenceDuration après le seuil d'offset), le processeur transmet au classifieur l'audio du tour jusqu'ici — de 0,5 s avant le début détecté par le VAD jusqu'au chunk courant, limité aux 8 dernières secondes.
  2. Terminé. Probabilité supérieure ou égale à threshold (0,5) : .speechEnded est émis comme d'habitude.
  3. Maintien. Sous le seuil, le segment reste ouvert. Si la personne reprend la parole, le même segment continue — sans second .speechStarted — et la prochaine pause confirmée repose la question avec le tour allongé.
  4. Plafond de silence. Si le silence atteint maxSilenceDuration (2,0 s, mesurées depuis le début de la pause), le segment se termine quand même, de sorte qu'une personne qui laisse sa phrase en suspens obtient tout de même une réponse. Le endTime du segment correspond à l'arrêt de la parole, pas à l'expiration du plafond.

flush() clôt un segment maintenu en fin de flux et reset() l'efface. Un classifieur qui lève une erreur compte comme « terminé », si bien qu'un modèle défaillant ne bloque jamais la conversation. isHoldingTurn indique l'état de maintien et lastTurnCompletionProbability la dernière réponse.

Modèle

PropriétéValeur
ArchitectureEncodeur Whisper-Tiny + attention pooling + tête MLP à sortie sigmoïde
Paramètres8,0M
Entrée8 dernières secondes d'audio mono à 16 kHz (128 000 échantillons) ; les tours plus courts sont complétés par des zéros au début, les autres fréquences d'échantillonnage sont rééchantillonnées
SortieProbabilité dans [0, 1] que le tour soit terminé ; seuil par défaut 0,5
Langues23 — Arabic, Bengali, Chinese, Danish, Dutch, English, Finnish, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Marathi, Norwegian, Polish, Portuguese, Russian, Spanish, Turkish, Ukrainian, Vietnamese
LicenceBSD-2-Clause

Les exports intègrent le front-end log-mel de Whisper, y compris la normalisation de la forme d'onde utilisée à l'entraînement du modèle d'origine ; on passe donc de l'audio brut — aucune extraction de caractéristiques en Swift ni en C++.

Performance

ExportTaillePrécisionLatence par fenêtre de 8 s
CoreML (CPU + Neural Engine)16,8 Mo92,9 %3,5 ms
ONNX fp32 (ONNX Runtime, CPU)33 Mo92,9 %~36 ms avec 2 threads, 20 ms avec 4
ONNX int8 (ONNX Runtime, CPU)11,1 Mo93,0 %~36 ms avec 2 threads, 20 ms avec 4

La précision est mesurée sur 1 000 extraits du jeu de test d'origine ; les exports CoreML et ONNX fp32 coïncident exactement avec le modèle fp32 d'origine sur ces extraits. La latence correspond à une fenêtre de 8 secondes sur un Apple M5 Pro. Un appel par pause n'ajoute rien de perceptible à la pause que le VAD a déjà attendue.

API Swift

Vérification ponctuelle d'un énoncé enregistré :

import SpeechVAD

let turn = try await SmartTurnModel.fromPretrained()
try turn.prewarm()   // compile the graph before the first real pause

let probability = try turn.turnCompleteProbability(
    audio: utterance, sampleRate: 16_000)
if probability >= SmartTurnModel.defaultThreshold {
    // finished turn — reply now
}

Attaché au VAD en streaming, chaque pause confirmée est vérifiée avant la fin d'un segment :

let vad = try await SileroVADModel.fromPretrained(engine: .coreml)
let turn = try await SmartTurnModel.fromPretrained()

let processor = StreamingVADProcessor(
    model: vad,
    config: .sileroDefault,
    turnCompletion: turn,
    turnCompletionConfig: TurnCompletionConfig(threshold: 0.5, maxSilenceDuration: 2.0))

for chunk in microphoneChunks {            // Float32 @ 16 kHz
    for event in processor.process(samples: chunk) {
        switch event {
        case .speechStarted(let time): print("speech at \(time)s")
        case .speechEnded(let segment): print("turn \(segment.startTime)–\(segment.endTime)s")
        }
    }
}
let remaining = processor.flush()

SmartTurnModel se conforme au protocole TurnCompletionProvider de AudioCommon ; tout autre classifieur de même forme peut donc être attaché à la place. fromPretrained(cacheDir:offlineMode:) suit les mêmes règles de cache et de mode hors ligne que les autres modèles.

VoicePipeline

VoicePipeline accepte le même classifieur : un agent vocal cesse ainsi de couper la parole sans rien changer à sa configuration STT, TTS ou VAD. Deux champs de PipelineConfig et une méthode suffisent ; ils requièrent speech-swift v0.0.27 ou ultérieur (SpeechCore.xcframework issu de speech-core v0.0.14).

import SpeechCore
import SpeechVAD

let smartTurn = try await SmartTurnModel.fromPretrained()
try smartTurn.prewarm()

var config = PipelineConfig()
config.turnCompletionThreshold = 0.5   // pause ends the turn when p >= this
config.turnCompletionMaxSilence = 2.0  // seconds of silence that end a held turn anyway; 0 = never

let pipeline = VoicePipeline(stt: stt, tts: tts, vad: vad, config: config, onEvent: { print($0) })
pipeline.setTurnCompletion(smartTurn)   // before start(); nil detaches
pipeline.start()
APIDéfautEffet
turnCompletionThreshold0,5Probabilité de fin à partir de laquelle une pause du VAD termine le tour de l'utilisateur. Utilisée seulement une fois un classifieur attaché.
turnCompletionMaxSilence2,0 sSecondes de silence, mesurées depuis le début de la pause, après lesquelles un tour refusé par le classifieur se termine quand même. 0 = jamais.
setTurnCompletion(_:)Attache n'importe quel TurnCompletionProvider. À appeler avant start() ; passez nil pour le détacher. Un classifieur qui lève une erreur compte comme « terminé », si bien qu'un modèle défaillant ne bloque jamais la conversation.

Avec un classifieur attaché, une pause du VAD ne termine le tour de l'utilisateur que si la probabilité atteint le seuil ; en dessous, le pipeline continue d'écouter, la parole qui reprend poursuit le même tour (pas de second speechStarted) et turnCompletionMaxSilence termine quand même le tour maintenu. Le STT anticipé (eager STT) respecte ce veto, et le classifieur s'exécute une fois par pause sur le thread audio : appelez donc prewarm() après le chargement.

Réglage

ParamètreDéfautEffet
threshold0,5Des valeurs plus basses terminent les tours plus tôt et interrompent plus souvent ; des valeurs plus hautes attendent plus longtemps avant de répondre. Observez lastTurnCompletionProbability sur de vraies conversations avant de le modifier.
maxSilenceDuration2,0 sPlafond strict, mesuré depuis le début de la pause. Une pause refusée ne peut jamais maintenir le tour plus longtemps. 0 désactive le plafond : un segment maintenu attend alors la parole ou flush().
preRollDuration0,5 sAudio précédant le début détecté par le VAD, inclus dans l'entrée du classifieur pour qu'une première syllabe tronquée atteigne tout de même le modèle.

Le minSilenceDuration de Silero décide toujours quand une pause est confirmée, donc quand le classifieur est interrogé. Appelez prewarm() après le chargement pour que la première vraie pause ne paie pas la compilation du graphe.

Utilisation CLI

# Probability, complete/incomplete verdict and inference time for one utterance
speech turn utterance.wav

# Custom threshold, JSON output
speech turn utterance.wav --threshold 0.7 --json

# Local bundle, no download
speech turn utterance.wav --model-dir ~/smart-turn-coreml

# Streaming VAD with Smart Turn confirming each pause; mid-sentence pauses merge
speech vad-stream call.wav --smart-turn
speech vad-stream call.wav --smart-turn --turn-threshold 0.6 --turn-max-silence 1.5
OptionCommandeDescription
--thresholdturnProbabilité à partir de laquelle le tour compte comme terminé (défaut 0,5)
--jsonturnÉmet probability, threshold, complete et latency_ms en JSON
--model, -mturnDépôt HuggingFace de même structure (défaut aufklarer/Smart-Turn-v3.2-CoreML)
--model-dirturnRépertoire local contenant smart_turn.mlmodelc et config.json ; saute le téléchargement
--smart-turnvad-streamConfirme chaque pause avec Smart Turn avant de terminer un segment
--turn-thresholdvad-streamSeuil de complétion Smart Turn (défaut 0,5)
--turn-max-silencevad-streamSecondes de silence après une pause refusée qui terminent le segment quand même (défaut 2,0)

speech turn charge le fichier (toute fréquence d'échantillonnage), note les 8 dernières secondes et affiche la probabilité, le franchissement ou non du seuil et le temps d'inférence. Utilisez-le pour régler le seuil sur vos propres enregistrements. La liste complète des options figure dans la référence CLI.

C++ / Android

Speech Core fournit le même modèle sous le nom OnnxSmartTurn pour Linux, Windows et Android (ONNX Runtime uniquement ; pas de variante LiteRT). VoicePipeline::set_turn_completion() l'attache avant start() ; une pause du VAD ne termine alors le tour de l'utilisateur que si la probabilité atteint turn_completion_threshold (0,5), et turn_completion_max_silence (2,0 s) termine quand même un tour maintenu. L'API C expose le même point d'entrée via sc_turn_completion_vtable_t + sc_pipeline_set_turn_completion(), si bien qu'un hôte Kotlin ou Swift peut brancher son propre classifieur comme il le fait pour le VAD.

#include <speech_core/models/onnx_smart_turn.h>

speech_core::OnnxSmartTurn model("/models/smart-turn-v3.2-int8.onnx");

// Audio of the turn so far at any rate (resampled to 16 kHz internally).
float p = model.turn_complete_probability(turn.data(), turn.size(), rate);

// Or let the pipeline ask it on every VAD pause (before start()).
pipeline.set_turn_completion(&model);

La CLI speech de Speech Core note un enregistrement comme sous macOS :

speech download-models        # fetches the int8 graph with the other ONNX models
speech turn recording.wav --threshold 0.5 --json

Les fichiers du modèle, le contrat de l'API C et la CLI sont documentés sur la page Speech Core et dans le docs/models.md du dépôt.

Téléchargements du modèle

ModèleBackendTailleHuggingFace
Smart-Turn-v3.2CoreML16,8 Moaufklarer/Smart-Turn-v3.2-CoreML
Smart-Turn-v3.2ONNX (fp32 + int8)33 Mo / 11,1 Mosoniqo/Smart-Turn-v3.2-ONNX

Sources