Smart Turn — การตรวจจับจบเทิร์นสนทนา
Smart Turn v3.2 ตอบคำถามเดียวหลังจาก VAD รายงานว่ามีช่วงหยุด: ผู้พูดพูดจบแล้ว หรือกำลังอยู่กลางประโยค? โมเดลฟังจากเสียงโดยตรง — ทำนองเสียง จังหวะ น้ำเสียง — ไม่ใช่จากข้อความถอดเสียง รองรับ 23 ภาษา และทำงานหนึ่งครั้งต่อช่วงหยุดบน 8 วินาทีสุดท้ายของเทิร์น น้ำหนักโมเดลคือ Smart Turn v3.2 รุ่นทางการจาก Pipecat (BSD-2-Clause) คอมไพล์เป็น CoreML สำหรับแพลตฟอร์ม Apple และส่งออกเป็น ONNX สำหรับ Speech Core
ตัวตรวจจับกิจกรรมเสียงพูดได้ยินแค่ความเงียบ คนเรามักหยุดกลางประโยคเพื่อคิด เอเจนต์ที่ใช้ VAD อย่างเดียวจึงต้องเลือกระหว่างพูดแทรก หรือรอเวลาหมดอายุที่ยาวและตายตัวหลังทุกประโยค Smart Turn ยืนยันแต่ละช่วงหยุด: ประโยคที่พูดจบได้รับคำตอบทันที ส่วนการหยุดกลางประโยคทำให้เอเจนต์ฟังต่อ
หลักการทำงาน
Smart Turn ไม่ใช่ VAD และไม่ตรวจจับเสียงพูดด้วยตัวเอง มันทำงานคู่กับ Silero VAD แบบสตรีมมิ่ง ซึ่งเป็นผู้ตัดสินว่าจะถามเมื่อใด เมื่อต่อตัวจำแนกเข้าไป ช่วงหยุดที่ยืนยันแล้วจะกลายเป็นคำถามแทนที่จะเป็นคำตัดสิน:
- ถามหนึ่งครั้งต่อช่วงหยุด เมื่อ Silero ยืนยันช่วงหยุด (
minSilenceDurationหลังเกณฑ์ offset) ตัวประมวลผลจะส่งเสียงของเทิร์นจนถึงตอนนั้นให้ตัวจำแนก — ตั้งแต่ 0.5 วินาทีก่อนจุดเริ่มพูดที่ VAD ตรวจพบ จนถึงชังก์ปัจจุบัน สูงสุด 8 วินาทีสุดท้าย - จบแล้ว ความน่าจะเป็นเท่ากับหรือสูงกว่า
threshold(0.5):.speechEndedทำงานตามปกติ - รอต่อ ต่ำกว่าเกณฑ์ เซกเมนต์ยังคงเปิดอยู่ ถ้าผู้พูดพูดต่อ เซกเมนต์เดิมจะดำเนินต่อ — ไม่มี
.speechStartedครั้งที่สอง — และช่วงหยุดที่ยืนยันครั้งถัดไปจะถามอีกครั้งด้วยเทิร์นที่ยาวขึ้น - เพดานความเงียบ ถ้าความเงียบยาวถึง
maxSilenceDuration(2.0 วินาที นับจากจุดเริ่มหยุด) เซกเมนต์จะจบอยู่ดี ผู้พูดที่เสียงค่อยๆ หายไปจึงยังได้รับคำตอบendTimeของเซกเมนต์คือจุดที่เสียงพูดหยุด ไม่ใช่จุดที่เพดานหมดเวลา
flush() ปิดเซกเมนต์ที่รอค้างไว้เมื่อสตรีมจบ และ reset() ล้างมันทิ้ง ตัวจำแนกที่โยนข้อผิดพลาดจะถูกนับว่า "จบแล้ว" โมเดลที่ล้มเหลวจึงไม่มีวันทำให้บทสนทนาค้าง isHoldingTurn รายงานสถานะการรอ และ lastTurnCompletionProbability ให้คำตอบล่าสุด
โมเดล
| คุณสมบัติ | ค่า |
|---|---|
| สถาปัตยกรรม | ตัวเข้ารหัส Whisper-Tiny + attention pooling + หัว 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 |
ไฟล์ที่ส่งออกฝัง front-end แบบ 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 ที่ 2 เธรด, 20 ms ที่ 4 เธรด |
| ONNX int8 (ONNX Runtime, CPU) | 11.1 MB | 93.0% | ~36 ms ที่ 2 เธรด, 20 ms ที่ 4 เธรด |
ความแม่นยำวัดจากคลิป 1,000 คลิปในชุดทดสอบต้นทาง; ไฟล์ส่งออก CoreML และ ONNX fp32 ให้ผลตรงกับโมเดล fp32 ต้นทางทุกประการบนคลิปเหล่านั้น ความหน่วงคือหนึ่งหน้าต่าง 8 วินาทีบน Apple M5 Pro การเรียกหนึ่งครั้งต่อช่วงหยุดไม่เพิ่มอะไรที่สังเกตได้ให้กับช่วงหยุดที่ VAD รอไปแล้ว
API 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 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.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)