Smart Turn — 発話終了検出

Smart Turn v3.2 は、VAD がポーズを報告したあとに一つの問いに答えます。話者は話し終えたのか、それとも文の途中なのか。書き起こしではなく音声そのもの——韻律、テンポ、イントネーション——を聞き取り、23 言語に対応し、ポーズごとに一度だけ、その発話の最後の 8 秒に対して実行されます。重みは Pipecat 公式の Smart Turn v3.2 リリース(BSD-2-Clause)で、Apple プラットフォーム向けに CoreML へコンパイルし、Speech Core 向けに ONNX へエクスポートしています。

VAD だけでは足りない理由

音声区間検出器は無音しか聞き取れません。人は文の途中で考えるために間を置くので、VAD だけのエージェントは相手を遮るか、すべての文のあとに長い固定タイムアウトを待つかのどちらかになります。Smart Turn は各ポーズを確認します。言い終えた文には即座に返答し、文の途中のポーズではエージェントが聞き続けます。

仕組み

Smart Turn は VAD ではなく、単体で音声を検出することもありません。ストリーミングの Silero VAD と組み合わせ、いつ尋ねるかは VAD が決めます。分類器を接続すると、確定したポーズは判決ではなく問いになります。

  1. ポーズごとに一度尋ねる。Silero がポーズを確定すると(オフセット閾値を下回ってから minSilenceDuration 経過)、プロセッサはその時点までの発話音声——VAD オンセットの 0.5 秒前から現在のチャンクまで、最大で最後の 8 秒——を分類器に渡します。
  2. 完了。確率が threshold(0.5)以上なら、通常どおり .speechEnded が発火します。
  3. 保留。閾値未満ならセグメントは開いたままです。話者が話を再開すれば同じセグメントが続き——2 回目の .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 の log-mel フロントエンドが埋め込まれているため、呼び出し側は生の音声をそのまま渡します。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 秒ウィンドウ 1 回分です。ポーズごとに 1 回の呼び出しなので、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()

SmartTurnModelAudioCommonTurnCompletionProvider プロトコルに準拠しているため、同じ形の分類器なら他のものを代わりに接続できます。fromPretrained(cacheDir:offlineMode:) は他のモデルと同じキャッシュ/オフラインの規則に従います。

VoicePipeline

VoicePipeline は同じ分類器をそのまま受け付けるため、STT・TTS・VAD の設定を変えずに音声エージェントがユーザーを遮らなくなります。PipelineConfig の 2 つのフィールドと 1 つのメソッドで接続でき、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 のポーズは確率がしきい値に達したときだけユーザーの発話を終了します。それ未満ではパイプラインは聞き続け、再開した音声は同じ発話として続き(2 回目の speechStarted は発火しません)、turnCompletionMaxSilence が保留中の発話をいずれにせよ終了させます。Eager STT もこの却下に従います。分類器はオーディオスレッド上でポーズごとに 1 回だけ実行されるので、読み込み後に 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)
--jsonturnprobabilitythresholdcompletelatency_ms を JSON で出力
--model, -mturn同じレイアウトの HuggingFace リポジトリ(デフォルト aufklarer/Smart-Turn-v3.2-CoreML
--model-dirturnsmart_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() で接続すると、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.2CoreML16.8 MBaufklarer/Smart-Turn-v3.2-CoreML
Smart-Turn-v3.2ONNX(fp32 + int8)33 MB / 11.1 MBsoniqo/Smart-Turn-v3.2-ONNX

ソース