Ми стикалися з ситуацією: голосовий бот обривав клієнта на середині фрази, тому що поріг тиші був надто жорстким. Або навпаки — зависав на 3 секунди, створюючи незручність. Обидва випадки — результат поганої реалізації ендпоінтінгу мовлення (end-of-speech detection) та неоптимального VAD. У цій статті розберемо, як налаштувати VAD, підібрати пороги та зробити адаптивний ендпоінтінг, який працює для різних сценаріїв.
Проблеми, які вирішуємо
Хибні спрацьовування виникають через занадто короткий поріг тиші (<500 мс) або низьку чутливість VAD. Користувач робить паузу, а система вже відправляє запит. Особливо критично в контакт-центрах: бот перебиває, оператор дратується. Вартість такої помилки — втрата клієнта.
Пропуск кінця висловлювання — зворотна ситуація: високий поріг (>1500 мс) або VAD «не чує» закінчення мовлення на фоні шуму. Діалог розтягується, користувач втрачає терпіння. Наш досвід показує, що 80% проблем вирішуються правильним вибором VAD та адаптацією порогів під сценарій. Економія на реінжинірингу — до 40% бюджету.
Затримка обробки: VAD повинен працювати в реальному часі, з latency p99 <100 мс. Використовуємо Silero VAD [Silero VAD paper] в ONNX Runtime або WebRTC VAD (легкий, але гірше на шумах). Для високонавантажених систем — batching на GPU.
Як вибрати поріг тиші для різних сценаріїв?
Для телефонного голосового бота оптимальні параметри: тиша 600–800 мс, мінімальне мовлення 200 мс. Для диктовки: тиша 1500–2000 мс. Для розумного дому (тихий фон): 500–600 мс. Завжди тестуємо на реальних записах з шумами. Адаптивний підхід дає виграш в UX: на відкритих питаннях поріг збільшується, на командах — знижується.
| Тип запиту | Поріг тиші (мс) | Приклад |
|---|---|---|
| Відкрите питання | 1200 | «Розкажи про себе» |
| Так/ні | 500 | «Увімкни світло?» |
| Команда | 600 | «Вимкни музику» |
Як ми це робимо: стек та реалізація
Використовуємо Python 3.11, PyTorch 2.2, ONNX Runtime 1.17, Silero VAD v4.0. Для асинхронної обробки — asyncio. Ось базова реалізація детектора (використовується в продакшені):
import collections
import time
from enum import Enum
class SpeechState(Enum):
SILENCE = 0
SPEECH = 1
class EndpointDetector:
def __init__(
self,
vad,
sample_rate: int = 16000,
frame_ms: int = 30,
silence_threshold_ms: int = 700, # пауза для завершення
min_speech_ms: int = 300, # мінімальна довжина висловлювання
):
self.vad = vad
self.sample_rate = sample_rate
self.frame_bytes = int(sample_rate * frame_ms / 1000) * 2
self.silence_frames_needed = silence_threshold_ms // frame_ms
self.min_speech_frames = min_speech_ms // frame_ms
self.state = SpeechState.SILENCE
self.silence_counter = 0
self.speech_buffer = bytearray()
self.speech_frame_count = 0
def process_frame(self, frame: bytes) -> tuple[bool, bytes | None]:
"""
Returns: (endpoint_detected, speech_audio_or_none)
"""
is_speech = self.vad.is_speech(frame, self.sample_rate)
if is_speech:
self.state = SpeechState.SPEECH
self.silence_counter = 0
self.speech_buffer.extend(frame)
self.speech_frame_count += 1
else:
if self.state == SpeechState.SPEECH:
self.silence_counter += 1
self.speech_buffer.extend(frame) # включаємо фінальну тишу
if self.silence_counter >= self.silence_frames_needed:
if self.speech_frame_count >= self.min_speech_frames:
audio = bytes(self.speech_buffer)
self._reset()
return True, audio
else:
self._reset()
return False, None
def _reset(self):
self.state = SpeechState.SILENCE
self.silence_counter = 0
self.speech_buffer = bytearray()
self.speech_frame_count = 0
У реальних діалогах потрібен адаптивний endpointing. Ми використовуємо класифікатор на основі Intent Detection (наприклад, через малу модель типу DistilBERT), який визначає тип запиту та динамічно змінює поріг. Адаптивний ендпоінтінг обробляє відкриті питання в 2 рази швидше, ніж фіксований поріг 700 мс.
# Різні пороги для різних типів запитів
THRESHOLDS = {
"open_question": 1200, # мс тиші
"yes_no": 500,
"command": 600,
"default": 700,
}
Докладніше про адаптивний класифікатор
Класифікатор намірів — це lightweight модель (DistilBERT або TinyBERT), яку ми запускаємо на перших 300 мс аудіо. Вона передбачає тип запиту до того, як користувач закінчить мовлення. Це дозволяє заздалегідь встановити поріг тиші та скоротити загальний час очікування. Середня точність передбачення — 94% на наших даних.
Порівняння VAD-рішень
| VAD | Точність на шумах | Latency (p99) | CPU Load |
|---|---|---|---|
| Silero VAD (ONNX) | 0.97 | 50 мс | Низьке |
| WebRTC VAD | 0.85 | 10 мс | Дуже низьке |
| RNNoise | 0.91 | 30 мс | Середнє |
Вибір VAD — компроміс між точністю та ресурсами. Для контакт-центру ми рекомендуємо Silero, для IoT — WebRTC. Latency p99 критичний для голосових ботів: при перевищенні 100 мс діалог стає неприродним.
Процес роботи над endpointing
- Аналіз — збираємо записи діалогів, заміряємо поточні метрики (latency, помилки).
- Проектування — обираємо VAD (зазвичай Silero), задаємо конфігурацію порогів, вирішуємо, чи потрібен адаптивний класифікатор.
- Реалізація — інтегруємо детектор у голосовий потік (WebRTC або власна реалізація). Додаємо моніторинг через MLflow.
- Тестування — A/B тест на 10% трафіку, порівнюємо з поточним рішенням.
- Деплой — контейнеризація, запуск на CPU-нодах (Triton Inference Server). Навчання команди.
Що входить в роботу під ключ
- Документація — опис архітектури, параметрів, інструкція з моніторингу.
- Код — Python-модуль з VAD, адаптивним порогом, обробкою помилок.
- Тестовий стенд — симулятор з реальними записами.
- Навчання — дзвінок з командою, відповіді на питання.
- Підтримка — 2 тижні після деплою (виправлення багів, налаштування під навантаження).
Терміни: базова реалізація — 2-3 дні, адаптивний з ML — 1 тиждень. Вартість розраховується індивідуально, але таке доопрацювання окупається за 2-3 місяці завдяки скороченню пауз та підвищенню конверсії. Правильне налаштування ендпоінтінгу може скоротити операційні витрати на 20-30%.
Наш досвід: понад 5 років роботи з голосовими асистентами, 30+ успішних проєктів. Ми гарантуємо стабільну роботу endpointing на зашумлених лініях. Для оцінки вашого проєкту зв'яжіться з нами – ми проаналізуємо ваші записи та запропонуємо оптимальне рішення.
Як не помилитися при впровадженні?
- Не копіюйте пороги з одного сценарію в інший: testbed повинен включати ваші реальні аудіо (з шумами, різною гучністю).
- Задокументуйте метрики: latency p99, false positive rate, false negative rate. Без них ви не дізнаєтеся, чи стало краще.
- Використовуйте адаптивний підхід: навіть проста зміна порогу за типом запиту покращує UX на 30%.
Отримайте консультацію: пишіть — ми оцінимо ваш проєкт і запропонуємо рішення.







