Smart Turn — 话轮结束检测

Smart Turn v3.2 在 VAD 报告停顿后回答一个问题:说话人已经说完了,还是话说到一半?它直接听音频本身——韵律、语速、语调——而不是转写文本,覆盖 23 种语言,每次停顿只运行一次,输入是这一轮话语的最后 8 秒。权重来自 Pipecat 官方发布的 Smart Turn v3.2(BSD-2-Clause),已编译为 CoreML 供 Apple 平台使用,并导出为 ONNX 供 Speech Core 使用。

为什么仅靠 VAD 不够

语音活动检测器只能听到静音。人们会在句子中途停下来思考,所以只依赖 VAD 的代理要么打断用户,要么在每句话之后都要等待一段很长的固定超时。Smart Turn 会确认每一次停顿:说完的句子立即得到回复,句中停顿则让代理继续倾听。

工作原理

Smart Turn 不是 VAD,自身也不检测语音。它与流式 Silero VAD 配合使用,由 VAD 决定何时提问。接入分类器后,一次已确认的停顿不再是判决,而是一个问题:

  1. 每次停顿问一次。当 Silero 确认停顿(越过 offset 阈值后再持续 minSilenceDuration),处理器把这一轮到目前为止的音频交给分类器——从 VAD 起始点之前 0.5 秒到当前块,最多取最后 8 秒。
  2. 已完成。概率达到或超过 threshold(0.5):像往常一样触发 .speechEnded
  3. 保持。低于阈值时片段保持打开。如果说话人继续说话,同一片段继续——不会再触发第二次 .speechStarted——下一次确认的停顿会用更长的话语再问一次。
  4. 静音上限。如果静音达到 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 MB92.9%3.5 ms
ONNX fp32(ONNX Runtime,CPU)33 MB92.9%2 线程约 36 ms,4 线程 20 ms
ONNX int8(ONNX Runtime,CPU)11.1 MB93.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默认值作用
turnCompletionThreshold0.5VAD 停顿结束用户话轮所需的完成概率下限。仅在接入分类器后生效。
turnCompletionMaxSilence2.0 秒从停顿开始计算的静音秒数,超过后被分类器否决的话轮也会结束。0 表示永不。
setTurnCompletion(_:)接入任意 TurnCompletionProvider。须在 start() 之前调用;传入 nil 即可解除。抛出错误的分类器按“已完成”处理,因此模型故障不会卡住对话。

接入分类器后,只有当概率达到阈值时,VAD 停顿才会结束用户的话轮;低于阈值时管线继续聆听,恢复的语音延续同一话轮(不会再次触发 speechStarted),而 turnCompletionMaxSilence 会强制结束被保持的话轮。Eager STT 同样遵守这一否决;分类器在音频线程上每次停顿只运行一次,因此加载后请调用 prewarm()

调参

设置默认值作用
threshold0.5越低,话轮结束得越早、打断也越频繁;越高,回复前等待越久。调整之前,先在真实对话中观察 lastTurnCompletionProbability
maxSilenceDuration2.0 秒硬性上限,从停顿开始计时。被否决的停顿最多只能把话轮保持这么久。0 表示禁用上限:被保持的片段会一直等到再次说话或调用 flush()
preRollDuration0.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
选项命令说明
--thresholdturn达到或超过此概率即视为话轮已完成(默认 0.5)
--jsonturn以 JSON 输出 probabilitythresholdcompletelatency_ms
--model, -mturn布局相同的 HuggingFace 仓库(默认 aufklarer/Smart-Turn-v3.2-CoreML
--model-dirturn包含 smart_turn.mlmodelcconfig.json 的本地目录;跳过下载
--smart-turnvad-stream在结束片段之前用 Smart Turn 确认每一次停顿
--turn-thresholdvad-streamSmart Turn 完成阈值(默认 0.5)
--turn-max-silencevad-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.2CoreML16.8 MBaufklarer/Smart-Turn-v3.2-CoreML
Smart-Turn-v3.2ONNX(fp32 + int8)33 MB / 11.1 MBsoniqo/Smart-Turn-v3.2-ONNX

源代码