Smart Turn — 발화 종료 감지
Smart Turn v3.2는 VAD가 멈춤을 보고한 뒤 한 가지 질문에 답합니다. 화자가 말을 끝냈는가, 아니면 문장 중간인가? 전사 텍스트가 아니라 음성 자체(운율, 속도, 억양)를 듣고 판단하며, 23개 언어를 지원하고, 멈춤마다 한 번씩 발화의 마지막 8초에 대해 실행됩니다. 가중치는 Pipecat의 공식 Smart Turn v3.2 릴리스(BSD-2-Clause)이며, Apple 플랫폼용으로 CoreML로 컴파일하고 Speech Core용으로 ONNX로 내보냈습니다.
음성 활동 감지기는 침묵만 듣습니다. 사람은 문장 중간에 생각하느라 멈추기 때문에, VAD만 쓰는 에이전트는 말을 끊거나, 모든 문장 뒤에 길고 고정된 타임아웃을 기다려야 합니다. Smart Turn은 각 멈춤을 확인합니다. 끝난 문장에는 즉시 답하고, 문장 중간의 멈춤에서는 에이전트가 계속 듣습니다.
동작 방식
Smart Turn은 VAD가 아니며 스스로 음성을 감지하지 않습니다. 스트리밍 Silero VAD와 짝을 이루며, 언제 물어볼지는 VAD가 결정합니다. 분류기를 붙이면 확정된 멈춤은 판정이 아니라 질문이 됩니다.
- 멈춤마다 한 번 묻기. Silero가 멈춤을 확정하면(오프셋 임계값 이후
minSilenceDuration경과), 프로세서는 지금까지의 발화 음성(VAD 시작 0.5초 전부터 현재 청크까지, 최대 마지막 8초)을 분류기에 넘깁니다. - 완료. 확률이
threshold(0.5) 이상이면 평소처럼.speechEnded가 발생합니다. - 유지. 임계값 미만이면 세그먼트가 열린 채로 남습니다. 화자가 다시 말하면 같은 세그먼트가 이어지고(두 번째
.speechStarted없음), 다음 확정된 멈춤에서 더 길어진 발화로 다시 묻습니다. - 침묵 상한. 침묵이
maxSilenceDuration(2.0초, 멈춤 시작부터 측정)에 이르면 세그먼트는 어쨌든 끝나므로, 말끝이 흐려지는 화자도 답을 받습니다. 세그먼트의endTime은 상한이 만료된 시점이 아니라 음성이 멈춘 위치입니다.
flush()는 스트림 끝에서 유지 중인 세그먼트를 정리하고 reset()은 이를 지웁니다. 분류기가 예외를 던지면 "완료"로 간주하므로 모델 장애가 대화를 멈추게 하지 않습니다. isHoldingTurn은 유지 상태를, lastTurnCompletionProbability는 가장 최근 답을 알려줍니다.
모델
| 속성 | 값 |
|---|---|
| 아키텍처 | Whisper-Tiny 인코더 + 어텐션 풀링 + sigmoid 출력의 MLP 헤드 |
| 파라미터 | 8.0M |
| 입력 | 16 kHz 모노 오디오의 마지막 8초(128,000 샘플). 짧은 발화는 앞쪽을 0으로 채우고, 다른 샘플 레이트는 리샘플링 |
| 출력 | 발화가 완료되었을 확률 [0, 1]. 기본 임계값 0.5 |
| 언어 | 23개 — Arabic, Bengali, Chinese, Danish, Dutch, English, Finnish, French, German, Hindi, Indonesian, Italian, Japanese, Korean, Marathi, Norwegian, Polish, Portuguese, Russian, Spanish, Turkish, Ukrainian, Vietnamese |
| 라이선스 | BSD-2-Clause |
내보낸 모델에는 업스트림 모델 학습에 쓰인 파형 정규화를 포함한 Whisper log-mel 프런트엔드가 내장되어 있어, 호출자는 원본 오디오를 그대로 넘깁니다. Swift나 C++에서 특징 추출은 없습니다.
성능
| 내보내기 | 크기 | 정확도 | 8초 윈도우당 지연 |
|---|---|---|---|
| CoreML(CPU + Neural Engine) | 16.8 MB | 92.9% | 3.5 ms |
| ONNX fp32(ONNX Runtime, CPU) | 33 MB | 92.9% | 2 스레드 약 36 ms, 4 스레드 20 ms |
| ONNX int8(ONNX Runtime, CPU) | 11.1 MB | 93.0% | 2 스레드 약 36 ms, 4 스레드 20 ms |
정확도는 업스트림 테스트 세트의 클립 1,000개로 측정했으며, CoreML과 fp32 ONNX 내보내기는 이 클립들에서 업스트림 fp32 모델과 정확히 일치합니다. 지연은 Apple M5 Pro에서 8초 윈도우 한 번의 시간입니다. 멈춤마다 한 번만 호출하므로, VAD가 이미 기다린 멈춤에 체감할 만한 시간이 더해지지 않습니다.
Swift API
녹음된 발화를 한 번만 판정하기:
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
}
스트리밍 VAD에 붙이면 확정된 모든 멈춤이 세그먼트가 끝나기 전에 확인됩니다:
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은 AudioCommon의 TurnCompletionProvider 프로토콜을 따르므로, 같은 형태의 다른 분류기를 대신 붙일 수 있습니다. fromPretrained(cacheDir:offlineMode:)는 다른 모델과 같은 캐시 및 오프라인 규칙을 따릅니다.
VoicePipeline
VoicePipeline은 같은 분류기를 그대로 받으므로, STT·TTS·VAD 설정을 바꾸지 않고도 음성 에이전트가 사용자의 말을 끊지 않게 됩니다. PipelineConfig의 필드 두 개와 메서드 하나로 연결하며, speech-swift v0.0.27 이상(speech-core v0.0.14 기반 SpeechCore.xcframework)이 필요합니다.
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 | 기본값 | 효과 |
|---|---|---|
turnCompletionThreshold | 0.5 | VAD 멈춤이 사용자의 발화를 끝내기 위한 완료 확률 하한. 분류기를 붙인 뒤에만 사용됩니다. |
turnCompletionMaxSilence | 2.0초 | 멈춤 시작부터 잰 침묵 초. 이를 넘기면 분류기가 거부한 발화도 어쨌든 끝납니다. 0이면 끝내지 않음. |
setTurnCompletion(_:) | — | 임의의 TurnCompletionProvider를 붙입니다. start() 전에 호출하고, nil을 넘기면 분리됩니다. 오류를 던지는 분류기는 "완료"로 간주되므로 모델 장애가 대화를 멈추지 않습니다. |
분류기를 붙이면 VAD 멈춤은 확률이 임계값에 도달할 때만 사용자의 발화를 끝냅니다. 그 미만이면 파이프라인은 계속 듣고, 다시 시작된 음성은 같은 발화로 이어지며(두 번째 speechStarted 없음), turnCompletionMaxSilence가 유지 중인 발화를 어쨌든 끝냅니다. Eager STT도 이 거부를 따르며, 분류기는 오디오 스레드에서 멈춤마다 한 번만 실행되므로 로드 후 prewarm()을 호출하세요.
튜닝
| 설정 | 기본값 | 효과 |
|---|---|---|
threshold | 0.5 | 낮을수록 발화가 더 빨리 끝나고 끼어듦이 잦아지며, 높을수록 답하기 전에 더 오래 기다립니다. 바꾸기 전에 실제 대화에서 lastTurnCompletionProbability를 관찰하세요. |
maxSilenceDuration | 2.0초 | 멈춤 시작부터 측정하는 강제 상한입니다. 거부된 멈춤이 발화를 이보다 오래 유지할 수는 없습니다. 0이면 상한이 꺼지고, 유지 중인 세그먼트는 음성 또는 flush()를 기다립니다. |
preRollDuration | 0.5초 | 분류기 입력에 포함되는 VAD 시작 이전의 오디오로, 잘린 첫 음절도 모델에 전달됩니다. |
멈춤이 언제 확정되는지, 즉 분류기에 언제 묻는지는 여전히 Silero의 minSilenceDuration이 결정합니다. 첫 실제 멈춤이 그래프 컴파일 비용을 치르지 않도록 로드 후 prewarm()을 호출하세요.
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
| 옵션 | 명령 | 설명 |
|---|---|---|
--threshold | turn | 이 확률 이상이면 발화를 완료로 간주(기본 0.5) |
--json | turn | probability, threshold, complete, latency_ms를 JSON으로 출력 |
--model, -m | turn | 같은 구조의 HuggingFace 저장소(기본 aufklarer/Smart-Turn-v3.2-CoreML) |
--model-dir | turn | smart_turn.mlmodelc와 config.json이 있는 로컬 디렉터리. 다운로드 건너뜀 |
--smart-turn | vad-stream | 세그먼트를 끝내기 전에 각 멈춤을 Smart Turn으로 확인 |
--turn-threshold | vad-stream | Smart Turn 완료 임계값(기본 0.5) |
--turn-max-silence | vad-stream | 거부된 멈춤 이후 어쨌든 세그먼트를 끝내는 침묵 초(기본 2.0) |
speech turn은 파일(모든 샘플 레이트)을 불러와 마지막 8초를 채점하고, 확률과 임계값 통과 여부, 추론 시간을 출력합니다. 자신의 녹음으로 임계값을 조정할 때 사용하세요. 전체 플래그 목록은 CLI 레퍼런스에 있습니다.
C++ / Android
Speech Core는 같은 모델을 OnnxSmartTurn으로 Linux, Windows, Android에 제공합니다(ONNX Runtime 전용, LiteRT 버전 없음). start() 전에 VoicePipeline::set_turn_completion()으로 붙이면, VAD 멈춤은 확률이 turn_completion_threshold(0.5)에 도달할 때만 사용자의 발화를 끝내고, turn_completion_max_silence(2.0초)가 유지 중인 발화를 어쨌든 끝냅니다. C API는 같은 훅을 sc_turn_completion_vtable_t + sc_pipeline_set_turn_completion()으로 노출하므로, Kotlin이나 Swift 호스트는 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);
Speech Core의 speech CLI는 macOS와 같은 방식으로 녹음을 채점합니다:
speech download-models # fetches the int8 graph with the other ONNX models
speech turn recording.wav --threshold 0.5 --json
모델 파일, C API 계약, CLI는 Speech Core 페이지와 저장소의 docs/models.md에 문서화되어 있습니다.
모델 다운로드
| 모델 | 백엔드 | 크기 | 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 |
소스
- Sources/SpeechVAD —
SmartTurnModel과StreamingVADProcessor - docs/models/smart-turn-v3.md — 모델 설명
- docs/inference/smart-turn.md — 추론과 튜닝
- speech-core docs/models.md —
OnnxSmartTurn - 업스트림: pipecat-ai/smart-turn / pipecat-ai/smart-turn-v3(Daily / Pipecat, BSD-2-Clause)