Smart Turn — Detección de fin de turno
Smart Turn v3.2 responde a una sola pregunta después de que el VAD detecta una pausa: ¿la persona terminó de hablar o está a mitad de frase? Escucha el audio en sí — prosodia, ritmo, entonación — en lugar de una transcripción, cubre 23 idiomas y se ejecuta una vez por pausa sobre los últimos 8 segundos del turno. Los pesos son la versión oficial Smart Turn v3.2 de Pipecat (BSD-2-Clause), compilada a CoreML para plataformas Apple y exportada a ONNX para Speech Core.
Un detector de actividad de voz solo oye silencio. La gente hace pausas a mitad de frase para pensar, así que un agente basado únicamente en VAD o bien interrumpe, o bien espera un tiempo fijo y largo después de cada frase. Smart Turn confirma cada pausa: una frase terminada recibe respuesta inmediata; una pausa a mitad de frase mantiene al agente escuchando.
Cómo funciona
Smart Turn no es un VAD y no detecta voz por sí solo. Se combina con el Silero VAD en streaming, que decide cuándo preguntar. Con un clasificador conectado, una pausa confirmada se convierte en una pregunta en lugar de un veredicto:
- Preguntar una vez por pausa. Cuando Silero confirma una pausa (
minSilenceDurationtras el umbral de offset), el procesador entrega al clasificador el audio del turno hasta ese momento — desde 0,5 s antes del inicio detectado por el VAD hasta el chunk actual, limitado a los últimos 8 segundos. - Completo. Probabilidad igual o superior a
threshold(0,5):.speechEndedse emite como siempre. - Retención. Por debajo del umbral, el segmento sigue abierto. Si la persona retoma la palabra, continúa el mismo segmento — sin un segundo
.speechStarted— y la siguiente pausa confirmada vuelve a preguntar con el turno más largo. - Tope de silencio. Si el silencio alcanza
maxSilenceDuration(2,0 s, medidos desde el inicio de la pausa), el segmento termina de todos modos, de modo que quien se queda a medias también recibe respuesta. ElendTimedel segmento es donde se detuvo la voz, no cuando venció el tope.
flush() cierra un segmento retenido al final del stream y reset() lo descarta. Un clasificador que lanza un error cuenta como «completo», así que un modelo que falla nunca bloquea la conversación. isHoldingTurn informa del estado de retención y lastTurnCompletionProbability de la última respuesta.
Modelo
| Propiedad | Valor |
|---|---|
| Arquitectura | Encoder Whisper-Tiny + attention pooling + cabeza MLP con salida sigmoide |
| Parámetros | 8,0M |
| Entrada | Últimos 8 s de audio mono a 16 kHz (128.000 muestras); los turnos más cortos se rellenan con ceros al principio y otras frecuencias de muestreo se remuestrean |
| Salida | Probabilidad en [0, 1] de que el turno esté completo; umbral por defecto 0,5 |
| Idiomas | 23 — Arabic, Bengali, Chinese, Danish, Dutch, English, Finnish, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Marathi, Norwegian, Polish, Portuguese, Russian, Spanish, Turkish, Ukrainian, Vietnamese |
| Licencia | BSD-2-Clause |
Las exportaciones incorporan el front-end log-mel de Whisper, incluida la normalización de la forma de onda con la que se entrenó el modelo original, así que se pasa audio sin procesar — no hay extracción de características en Swift ni en C++.
Rendimiento
| Exportación | Tamaño | Precisión | Latencia por ventana de 8 s |
|---|---|---|---|
| CoreML (CPU + Neural Engine) | 16,8 MB | 92,9% | 3,5 ms |
| ONNX fp32 (ONNX Runtime, CPU) | 33 MB | 92,9% | ~36 ms con 2 hilos, 20 ms con 4 |
| ONNX int8 (ONNX Runtime, CPU) | 11,1 MB | 93,0% | ~36 ms con 2 hilos, 20 ms con 4 |
La precisión se mide sobre 1.000 clips del conjunto de prueba original; las exportaciones CoreML y ONNX fp32 coinciden exactamente con el modelo fp32 original en esos clips. La latencia corresponde a una ventana de 8 segundos en un Apple M5 Pro. Una llamada por pausa no añade nada perceptible a la pausa que el VAD ya esperó.
API Swift
Comprobación puntual sobre una locución grabada:
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
}
Conectado al VAD en streaming, de modo que cada pausa confirmada se verifica antes de cerrar un segmento:
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 cumple el protocolo TurnCompletionProvider de AudioCommon, así que se puede conectar en su lugar cualquier otro clasificador con la misma forma. fromPretrained(cacheDir:offlineMode:) sigue las mismas reglas de caché y modo sin conexión que el resto de modelos.
VoicePipeline
VoicePipeline acepta el mismo clasificador, de modo que un agente de voz deja de interrumpir a la gente sin cambiar su configuración de STT, TTS o VAD. Dos campos de PipelineConfig y un método lo integran; requieren speech-swift v0.0.27 o posterior (SpeechCore.xcframework 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()
| API | Por defecto | Efecto |
|---|---|---|
turnCompletionThreshold | 0,5 | Probabilidad de finalización a partir de la cual una pausa del VAD termina el turno del usuario. Solo se usa una vez conectado un clasificador. |
turnCompletionMaxSilence | 2,0 s | Segundos de silencio, medidos desde el inicio de la pausa, tras los cuales un turno vetado por el clasificador termina igualmente. 0 = nunca. |
setTurnCompletion(_:) | — | Conecta cualquier TurnCompletionProvider. Llámalo antes de start(); pasa nil para desconectarlo. Un clasificador que lanza un error cuenta como completo, así que un modelo que falla nunca bloquea la conversación. |
Con un clasificador conectado, una pausa del VAD solo termina el turno del usuario cuando la probabilidad alcanza el umbral; por debajo, la canalización sigue escuchando, el habla que se reanuda continúa el mismo turno (sin un segundo speechStarted) y turnCompletionMaxSilence cierra igualmente el turno retenido. El STT anticipado (eager STT) respeta el veto, y el clasificador se ejecuta una vez por pausa en el hilo de audio, así que llama a prewarm() tras cargarlo.
Ajuste
| Ajuste | Por defecto | Efecto |
|---|---|---|
threshold | 0,5 | Valores más bajos terminan los turnos antes e interrumpen más; valores más altos esperan más antes de responder. Observa lastTurnCompletionProbability en conversaciones reales antes de moverlo. |
maxSilenceDuration | 2,0 s | Tope duro, medido desde el inicio de la pausa. Una pausa vetada nunca puede retener el turno más tiempo que esto. 0 desactiva el tope: un segmento retenido espera entonces a que haya voz o a flush(). |
preRollDuration | 0,5 s | Audio anterior al inicio detectado por el VAD que se incluye en la entrada del clasificador, para que una primera sílaba recortada llegue igualmente al modelo. |
El minSilenceDuration de Silero sigue decidiendo cuándo se confirma una pausa y, por tanto, cuándo se consulta al clasificador. Llama a prewarm() después de cargar, para que la primera pausa real no pague la compilación del grafo.
Uso del 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
| Opción | Comando | Descripción |
|---|---|---|
--threshold | turn | Probabilidad a partir de la cual el turno cuenta como completo (por defecto 0,5) |
--json | turn | Emite probability, threshold, complete y latency_ms como JSON |
--model, -m | turn | Repositorio de HuggingFace con la misma estructura (por defecto aufklarer/Smart-Turn-v3.2-CoreML) |
--model-dir | turn | Directorio local con smart_turn.mlmodelc y config.json; omite la descarga |
--smart-turn | vad-stream | Confirma cada pausa con Smart Turn antes de cerrar un segmento |
--turn-threshold | vad-stream | Umbral de completitud de Smart Turn (por defecto 0,5) |
--turn-max-silence | vad-stream | Segundos de silencio tras una pausa vetada que cierran el segmento de todos modos (por defecto 2,0) |
speech turn carga el archivo (cualquier frecuencia de muestreo), puntúa los últimos 8 segundos e imprime la probabilidad, si supera el umbral y el tiempo de inferencia. Úsalo para ajustar el umbral con tus propias grabaciones. La lista completa de opciones está en la referencia del CLI.
C++ / Android
Speech Core incluye el mismo modelo como OnnxSmartTurn para Linux, Windows y Android (solo ONNX Runtime; no hay variante LiteRT). VoicePipeline::set_turn_completion() lo conecta antes de start(); a partir de ahí una pausa del VAD solo termina el turno del usuario cuando la probabilidad alcanza turn_completion_threshold (0,5), y turn_completion_max_silence (2,0 s) cierra igualmente un turno retenido. La API C expone el mismo gancho como sc_turn_completion_vtable_t + sc_pipeline_set_turn_completion(), de modo que un host en Kotlin o Swift puede conectar su propio clasificador igual que el 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);
El CLI speech de Speech Core puntúa una grabación igual que en macOS:
speech download-models # fetches the int8 graph with the other ONNX models
speech turn recording.wav --threshold 0.5 --json
Los archivos del modelo, el contrato de la API C y el CLI están documentados en la página de Speech Core y en el docs/models.md del repositorio.
Descargas del modelo
| Modelo | Backend | Tamaño | HuggingFace |
|---|---|---|---|
| Smart-Turn-v3.2 | CoreML | 16,8 MB | aufklarer/Smart-Turn-v3.2-CoreML |
| Smart-Turn-v3.2 | ONNX (fp32 + int8) | 33 MB / 11,1 MB | soniqo/Smart-Turn-v3.2-ONNX |
Código fuente
- Sources/SpeechVAD —
SmartTurnModelyStreamingVADProcessor - docs/models/smart-turn-v3.md — notas del modelo
- docs/inference/smart-turn.md — inferencia y ajuste
- speech-core docs/models.md —
OnnxSmartTurn - Origen: pipecat-ai/smart-turn / pipecat-ai/smart-turn-v3 (Daily / Pipecat, BSD-2-Clause)