Smart Turn — Detecção de fim de turno
O Smart Turn v3.2 responde a uma única pergunta depois que o VAD relata uma pausa: a pessoa terminou de falar ou está no meio da frase? Ele ouve o áudio em si — prosódia, ritmo, entonação — em vez de uma transcrição, cobre 23 idiomas e roda uma vez por pausa sobre os últimos 8 segundos do turno. Os pesos são a versão oficial Smart Turn v3.2 da Pipecat (BSD-2-Clause), compilada para CoreML nas plataformas Apple e exportada para ONNX no Speech Core.
Um detector de atividade de voz só ouve silêncio. As pessoas param no meio da frase para pensar, então um agente baseado só em VAD ou as interrompe, ou espera um tempo fixo e longo depois de cada frase. O Smart Turn confirma cada pausa: uma frase terminada recebe resposta imediata; uma pausa no meio da frase mantém o agente ouvindo.
Como funciona
O Smart Turn não é um VAD e não detecta fala por conta própria. Ele é combinado com o Silero VAD em streaming, que decide quando perguntar. Com um classificador conectado, uma pausa confirmada vira uma pergunta em vez de um veredito:
- Perguntar uma vez por pausa. Quando o Silero confirma uma pausa (
minSilenceDurationapós o limiar de offset), o processador entrega ao classificador o áudio do turno até ali — de 0,5 s antes do início detectado pelo VAD até o chunk atual, limitado aos últimos 8 segundos. - Completo. Probabilidade igual ou superior a
threshold(0,5):.speechEndedé emitido como sempre. - Retenção. Abaixo do limiar, o segmento continua aberto. Se a pessoa voltar a falar, o mesmo segmento continua — sem um segundo
.speechStarted— e a próxima pausa confirmada pergunta de novo com o turno mais longo. - Teto de silêncio. Se o silêncio chegar a
maxSilenceDuration(2,0 s, medidos a partir do início da pausa), o segmento termina mesmo assim, de modo que quem deixa a frase no ar também recebe resposta. OendTimedo segmento é onde a fala parou, não quando o teto expirou.
flush() encerra um segmento retido no fim do stream e reset() o descarta. Um classificador que lança erro conta como "completo", então um modelo com falha nunca trava a conversa. isHoldingTurn informa o estado de retenção e lastTurnCompletionProbability a resposta mais recente.
Modelo
| Propriedade | Valor |
|---|---|
| Arquitetura | Encoder Whisper-Tiny + attention pooling + cabeça MLP com saída sigmoide |
| Parâmetros | 8,0M |
| Entrada | Últimos 8 s de áudio mono a 16 kHz (128.000 amostras); turnos mais curtos são preenchidos com zeros no início e outras taxas de amostragem são reamostradas |
| Saída | Probabilidade em [0, 1] de o turno estar completo; limiar padrão 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 |
| Licença | BSD-2-Clause |
As exportações incorporam o front-end log-mel do Whisper, incluindo a normalização da forma de onda com que o modelo original foi treinado, então basta passar áudio bruto — não há extração de características em Swift nem em C++.
Desempenho
| Exportação | Tamanho | Precisão | Latência por janela 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 com 2 threads, 20 ms com 4 |
| ONNX int8 (ONNX Runtime, CPU) | 11,1 MB | 93,0% | ~36 ms com 2 threads, 20 ms com 4 |
A precisão é medida em 1.000 clipes do conjunto de teste original; as exportações CoreML e ONNX fp32 coincidem exatamente com o modelo fp32 original nesses clipes. A latência corresponde a uma janela de 8 segundos em um Apple M5 Pro. Uma chamada por pausa não acrescenta nada perceptível à pausa que o VAD já esperou.
API Swift
Verificação única de uma fala gravada:
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 ao VAD em streaming, cada pausa confirmada é verificada antes de encerrar um 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 adota o protocolo TurnCompletionProvider do AudioCommon, então qualquer outro classificador com a mesma forma pode ser conectado no lugar. fromPretrained(cacheDir:offlineMode:) segue as mesmas regras de cache e modo offline dos outros modelos.
VoicePipeline
O VoicePipeline aceita o mesmo classificador, de modo que um agente de voz para de interromper as pessoas sem mudar sua configuração de STT, TTS ou VAD. Dois campos do PipelineConfig e um método fazem a ligação; eles exigem speech-swift v0.0.27 ou mais recente (SpeechCore.xcframework do 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 | Padrão | Efeito |
|---|---|---|
turnCompletionThreshold | 0,5 | Probabilidade de conclusão a partir da qual uma pausa do VAD encerra o turno do usuário. Só é usada depois que um classificador é conectado. |
turnCompletionMaxSilence | 2,0 s | Segundos de silêncio, medidos desde o início da pausa, após os quais um turno vetado pelo classificador é encerrado mesmo assim. 0 = nunca. |
setTurnCompletion(_:) | — | Conecta qualquer TurnCompletionProvider. Chame antes de start(); passe nil para desconectar. Um classificador que lança erro conta como concluído, então um modelo com falha nunca trava a conversa. |
Com um classificador conectado, uma pausa do VAD só encerra o turno do usuário quando a probabilidade atinge o limiar; abaixo dele o pipeline continua ouvindo, a fala que retoma continua o mesmo turno (sem um segundo speechStarted) e turnCompletionMaxSilence encerra o turno retido mesmo assim. O STT antecipado (eager STT) respeita o veto, e o classificador roda uma vez por pausa na thread de áudio, então chame prewarm() após o carregamento.
Ajuste
| Configuração | Padrão | Efeito |
|---|---|---|
threshold | 0,5 | Valores mais baixos encerram turnos mais cedo e interrompem mais; valores mais altos esperam mais antes de responder. Observe lastTurnCompletionProbability em conversas reais antes de alterá-lo. |
maxSilenceDuration | 2,0 s | Teto rígido, medido a partir do início da pausa. Uma pausa vetada nunca pode reter o turno por mais tempo. 0 desativa o teto: um segmento retido passa a esperar por fala ou por flush(). |
preRollDuration | 0,5 s | Áudio anterior ao início detectado pelo VAD incluído na entrada do classificador, para que uma primeira sílaba cortada ainda chegue ao modelo. |
O minSilenceDuration do Silero continua decidindo quando uma pausa é confirmada e, portanto, quando o classificador é consultado. Chame prewarm() após carregar, para que a primeira pausa real não pague pela compilação do grafo.
Uso no 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
| Opção | Comando | Descrição |
|---|---|---|
--threshold | turn | Probabilidade a partir da qual o turno conta como completo (padrão 0,5) |
--json | turn | Emite probability, threshold, complete e latency_ms como JSON |
--model, -m | turn | Repositório do HuggingFace com a mesma estrutura (padrão aufklarer/Smart-Turn-v3.2-CoreML) |
--model-dir | turn | Diretório local com smart_turn.mlmodelc e config.json; pula o download |
--smart-turn | vad-stream | Confirma cada pausa com o Smart Turn antes de encerrar um segmento |
--turn-threshold | vad-stream | Limiar de conclusão do Smart Turn (padrão 0,5) |
--turn-max-silence | vad-stream | Segundos de silêncio após uma pausa vetada que encerram o segmento mesmo assim (padrão 2,0) |
speech turn carrega o arquivo (qualquer taxa de amostragem), pontua os últimos 8 segundos e imprime a probabilidade, se ela supera o limiar e o tempo de inferência. Use-o para ajustar o limiar com suas próprias gravações. A lista completa de opções está na referência do CLI.
C++ / Android
O Speech Core inclui o mesmo modelo como OnnxSmartTurn para Linux, Windows e Android (apenas ONNX Runtime; não há variante LiteRT). VoicePipeline::set_turn_completion() o conecta antes de start(); a partir daí uma pausa do VAD só encerra o turno do usuário quando a probabilidade atinge turn_completion_threshold (0,5), e turn_completion_max_silence (2,0 s) encerra um turno retido mesmo assim. A API C expõe o mesmo gancho como sc_turn_completion_vtable_t + sc_pipeline_set_turn_completion(), então hosts em Kotlin ou Swift podem conectar seu próprio classificador da mesma forma que o 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);
A CLI speech do Speech Core pontua uma gravação do mesmo jeito que no macOS:
speech download-models # fetches the int8 graph with the other ONNX models
speech turn recording.wav --threshold 0.5 --json
Os arquivos do modelo, o contrato da API C e a CLI estão documentados na página do Speech Core e no docs/models.md do repositório.
Downloads do modelo
| Modelo | Backend | Tamanho | 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 fonte
- Sources/SpeechVAD —
SmartTurnModeleStreamingVADProcessor - docs/models/smart-turn-v3.md — notas do modelo
- docs/inference/smart-turn.md — inferência e ajuste
- speech-core docs/models.md —
OnnxSmartTurn - Origem: pipecat-ai/smart-turn / pipecat-ai/smart-turn-v3 (Daily / Pipecat, BSD-2-Clause)