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 البثّي الذي يقرر متى يُسأل. مع وجود مصنّف مرتبط، يصبح التوقّف المؤكَّد سؤالاً لا حكماً:
- سؤال واحد لكل توقّف. عندما يؤكّد Silero توقّفاً (
minSilenceDurationبعد عتبة النهاية)، يسلّم المعالج المصنّفَ صوتَ الدور حتى تلك اللحظة — من 0.5 ثانية قبل بداية الكلام التي رصدها VAD حتى القطعة الحالية، بحدّ أقصى آخر 8 ثوانٍ. - مكتمل. احتمال يساوي
threshold(0.5) أو يتجاوزه: يُطلَق.speechEndedكالمعتاد. - تعليق. دون العتبة يبقى المقطع مفتوحاً. إذا استأنف المتحدث الكلام، يستمر المقطع نفسه — دون
.speechStartedثانٍ — ويسأل التوقّف المؤكَّد التالي مجدداً بالدور الأطول. - سقف الصمت. إذا بلغ الصمت
maxSilenceDuration(2.0 ثانية، تُقاس من بداية التوقّف)، ينتهي المقطع على أي حال، فيحصل المتحدث الذي يخفت صوته تدريجياً على ردّ أيضاً.endTimeللمقطع هو موضع توقّف الكلام، لا لحظة انقضاء السقف.
يُنهي flush() المقطع المعلَّق عند نهاية البث، ويمسحه reset(). المصنّف الذي يرمي خطأً يُعدّ «مكتملاً»، لذا لا يوقف تعطّلُ النموذج المحادثةَ أبداً. يبلّغ isHoldingTurn عن حالة التعليق، وlastTurnCompletionProbability عن آخر إجابة.
النموذج
| الخاصية | القيمة |
|---|---|
| البنية | مشفّر Whisper-Tiny + تجميع بالانتباه + رأس MLP بخرج sigmoid |
| المعاملات | 8.0M |
| الإدخال | آخر 8 ثوانٍ من صوت أحادي 16 kHz (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 MB | 92.9% | 3.5 ms |
| ONNX fp32 (ONNX Runtime، CPU) | 33 MB | 92.9% | ~36 ms بخيطين، 20 ms بأربعة |
| ONNX int8 (ONNX Runtime، CPU) | 11.1 MB | 93.0% | ~36 ms بخيطين، 20 ms بأربعة |
قِيست الدقة على 1,000 مقطع من مجموعة الاختبار الأصلية؛ ويتطابق تصديرا CoreML وONNX fp32 تماماً مع نموذج fp32 الأصلي على تلك المقاطع. زمن الاستجابة هو لنافذة واحدة مدتها 8 ثوانٍ على Apple M5 Pro. استدعاء واحد لكل توقّف لا يضيف شيئاً ملحوظاً إلى التوقّف الذي انتظره VAD أصلاً.
واجهة Swift
فحص لمرة واحدة لعبارة مسجّلة:
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 | الافتراضي | الأثر |
|---|---|---|
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 والمُدرج في مدخل المصنّف، كي يصل المقطع الأول المبتور إلى النموذج. |
ما يزال 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
| الخيار | الأمر | الوصف |
|---|---|---|
--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). يربطه VoicePipeline::set_turn_completion() قبل start()؛ وبعدها لا يُنهي توقّفُ VAD دورَ المستخدم إلا عندما يبلغ الاحتمال turn_completion_threshold (0.5)، بينما يُنهي turn_completion_max_silence (2.0 ثانية) الدور المعلَّق على أي حال. تكشف واجهة C الخطّاف نفسه عبر 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 في Speech Core التسجيل بالطريقة نفسها كما على macOS:
speech download-models # fetches the int8 graph with the other ONNX models
speech turn recording.wav --threshold 0.5 --json
ملفات النموذج وعقد واجهة C وواجهة 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)