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, который решает, когда задать вопрос. С подключённым классификатором подтверждённая пауза становится вопросом, а не вердиктом:

  1. Один запрос на паузу. Когда Silero подтверждает паузу (minSilenceDuration после порога offset), процессор передаёт классификатору звук реплики на этот момент — от 0,5 с до начала речи по VAD и до текущего чанка, не более последних 8 секунд.
  2. Завершено. Вероятность не ниже threshold (0,5): .speechEnded срабатывает как обычно.
  3. Удержание. Ниже порога сегмент остаётся открытым. Если говорящий продолжает, тот же сегмент продолжается — без второго .speechStarted, — а следующая подтверждённая пауза снова задаёт вопрос уже с более длинной репликой.
  4. Предел тишины. Если тишина достигает maxSilenceDuration (2,0 с, считая от начала паузы), сегмент всё равно завершается, так что говорящий, который замолкает на полуслове, тоже получает ответ. endTime сегмента — момент, когда прекратилась речь, а не когда истёк предел.

flush() завершает удерживаемый сегмент в конце потока, reset() сбрасывает его. Классификатор, выбросивший ошибку, считается ответившим «завершено», поэтому сбой модели никогда не останавливает разговор. isHoldingTurn сообщает о состоянии удержания, lastTurnCompletionProbability — последний ответ.

Модель

СвойствоЗначение
АрхитектураЭнкодер Whisper-Tiny + attention pooling + MLP-голова с сигмоидным выходом
Параметры8,0M
ВходПоследние 8 с моно-аудио 16 кГц (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

В экспорты встроен log-mel фронтенд Whisper, включая нормализацию сигнала, на которой обучалась исходная модель, поэтому на вход подаётся сырой звук — извлечения признаков в Swift или C++ нет.

Производительность

ЭкспортРазмерТочностьЗадержка на окно 8 с
CoreML (CPU + Neural Engine)16,8 МБ92,9 %3,5 мс
ONNX fp32 (ONNX Runtime, CPU)33 МБ92,9 %~36 мс при 2 потоках, 20 мс при 4
ONNX int8 (ONNX Runtime, CPU)11,1 МБ93,0 %~36 мс при 2 потоках, 20 мс при 4

Точность измерена на 1 000 клипах из исходного тестового набора; экспорты CoreML и ONNX fp32 на этих клипах в точности совпадают с исходной fp32-моделью. Задержка — одно окно 8 секунд на Apple M5 Pro. Один вызов на паузу не добавляет ничего заметного к паузе, которую 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 реализует протокол TurnCompletionProvider из AudioCommon, поэтому вместо него можно подключить любой другой классификатор той же формы. fromPretrained(cacheDir:offlineMode:) следует тем же правилам кэша и офлайн-режима, что и остальные модели.

VoicePipeline

VoicePipeline принимает тот же классификатор, поэтому голосовой агент перестаёт перебивать людей без изменений в настройке STT, TTS или VAD. Подключение — два поля PipelineConfig и один метод; требуется speech-swift v0.0.27 или новее (SpeechCore.xcframework из 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По умолчаниюЭффект
turnCompletionThreshold0,5Вероятность завершения, начиная с которой пауза VAD завершает реплику пользователя. Используется только после подключения классификатора.
turnCompletionMaxSilence2,0 сСекунды тишины, отсчитываемые от начала паузы, после которых реплика, отклонённая классификатором, всё равно завершается. 0 = никогда.
setTurnCompletion(_:)Подключает любой TurnCompletionProvider. Вызывайте до start(); передайте nil, чтобы отключить. Классификатор, выбросивший ошибку, считается ответившим «завершено», поэтому сбой модели никогда не останавливает разговор.

С подключённым классификатором пауза VAD завершает реплику пользователя, только если вероятность достигает порога; ниже него конвейер продолжает слушать, возобновившаяся речь продолжает ту же реплику (без второго speechStarted), а turnCompletionMaxSilence всё равно завершает удерживаемую реплику. Eager STT подчиняется этому вето, а классификатор выполняется один раз на паузу в аудиопотоке, поэтому после загрузки вызовите prewarm().

Настройка

ПараметрПо умолчаниюЭффект
threshold0,5Меньшие значения завершают реплики раньше и чаще перебивают; большие — дольше ждут перед ответом. Прежде чем менять, понаблюдайте за lastTurnCompletionProbability в реальных разговорах.
maxSilenceDuration2,0 сЖёсткий предел, считая от начала паузы. Отклонённая пауза никогда не удержит реплику дольше. 0 отключает предел: удерживаемый сегмент тогда ждёт речи или flush().
preRollDuration0,5 сЗвук до начала речи по VAD, включаемый во вход классификатора, чтобы обрезанный первый слог всё же дошёл до модели.

minSilenceDuration у Silero по-прежнему решает, когда пауза считается подтверждённой, а значит, когда опрашивать классификатор. После загрузки вызовите 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Выводит probability, threshold, complete и latency_ms в формате JSON
--model, -mturnРепозиторий HuggingFace с той же структурой (по умолчанию aufklarer/Smart-Turn-v3.2-CoreML)
--model-dirturnЛокальный каталог с smart_turn.mlmodelc и config.json; пропускает загрузку
--smart-turnvad-streamПодтверждать каждую паузу через Smart Turn перед завершением сегмента
--turn-thresholdvad-streamПорог завершения Smart Turn (по умолчанию 0,5)
--turn-max-silencevad-streamСекунды тишины после отклонённой паузы, по истечении которых сегмент завершается в любом случае (по умолчанию 2,0)

speech turn загружает файл (любая частота дискретизации), оценивает последние 8 секунд и выводит вероятность, пройден ли порог, и время инференса. Используйте его, чтобы подобрать порог на собственных записях. Полный список флагов — в справочнике CLI.

C++ / Android

Speech Core поставляет ту же модель как OnnxSmartTurn для Linux, Windows и Android (только ONNX Runtime; варианта LiteRT нет). VoicePipeline::set_turn_completion() подключает её до start(); после этого пауза 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);

CLI speech из Speech Core оценивает запись так же, как на 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 МБaufklarer/Smart-Turn-v3.2-CoreML
Smart-Turn-v3.2ONNX (fp32 + int8)33 МБ / 11,1 МБsoniqo/Smart-Turn-v3.2-ONNX

Исходники