Smart Turn — 话轮结束检测
Smart Turn v3.2 在 VAD 报告停顿后回答一个问题:说话人已经说完了,还是话说到一半?它直接听音频本身——韵律、语速、语调——而不是转写文本,覆盖 23 种语言,每次停顿只运行一次,输入是这一轮话语的最后 8 秒。权重来自 Pipecat 官方发布的 Smart Turn v3.2(BSD-2-Clause),已编译为 CoreML 供 Apple 平台使用,并导出为 ONNX 供 Speech Core 使用。
语音活动检测器只能听到静音。人们会在句子中途停下来思考,所以只依赖 VAD 的代理要么打断用户,要么在每句话之后都要等待一段很长的固定超时。Smart Turn 会确认每一次停顿:说完的句子立即得到回复,句中停顿则让代理继续倾听。
工作原理
Smart Turn 不是 VAD,自身也不检测语音。它与流式 Silero VAD 配合使用,由 VAD 决定何时提问。接入分类器后,一次已确认的停顿不再是判决,而是一个问题:
- 每次停顿问一次。当 Silero 确认停顿(越过 offset 阈值后再持续
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, 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 对数梅尔前端,包括上游模型训练时使用的波形归一化,因此调用方直接传入原始音频——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 | 以 JSON 输出 probability、threshold、complete 和 latency_ms |
--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() 接入;此后只有当概率达到 turn_completion_threshold(0.5)时,VAD 停顿才会结束用户的话轮,而 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)