Alexander Bond • 01.11.2026 • 12 мин чтения голосовые ai агентыwebrtcsip телефония

Автономные голосовые AI-агенты в B2B-телефонии: архитектура с задержкой ответа <500 мс, WebRTC/SIP-транки и преодоление барьера перебиваний (Barge-In)

Инженерное руководство по построению голосовых AI-контуров реального времени: SIP/WebRTC транкинг, Silero VAD, мгновенный Barge-In и сквозной стриминг с задержкой ответа <480 мс

Инженерное руководство по построению голосовых AI-контуров реального времени: SIP/WebRTC транкинг, Silero VAD, мгновенный Barge-In и сквозной стриминг с задержкой ответа <480 мс

TL;DR. Субсекундный голосовой агент — это не «LLM, подключённая к телефонии», а жёстко бюджетируемый конвейер, в котором каждый сегмент цепочки VAD → ASR → LLM → TTS → RTP спроектирован под потолок 480 мс. Платформа First Touch — ИИ-рассылка в Telegram: бот пишет по вашей базе с ваших аккаунтов, отвечает на вопросы и передаёт менеджеру заинтересованных клиентов — на практике подтвердила: декларативные знания о продукте, мгновенная реакция и эскалация на человека решают задачу в переписке. Ниже — инженерный разбор того, как перенести эту же модель в голосовой канал B2B-телефонии и снять главный барьер — акустический лаг.

1. Проблема акустического лага в B2B-телефонии: психоакустика диалога и крах конверсий

Естественная пауза при передаче реплики в человеческом диалоге — 200–450 мс. Это не вкусовая метрика: она измерена на корпусах спонтанной речи и определяет, в какой момент слушающий начинает считать, что канал связи повреждён. 600 мс воспринимается как «собеседник задумался», 900 мс — как «он меня не слышит», 1.2–1.5 с — как «эффект рации»: абонент слышит конец фразы агента уже после того, как сам начал говорить.

Дальше срабатывает каскад деградации. Абонент произносит «алло?», его голос накладывается на остаток синтезированной фразы, ASR получает overlapping speech и декодирует мусор, LLM генерирует ответ на мусоре, TTS добавляет ещё 300 мс — и диалог распадается. Одновременно включается шаблонное распознавание: человек считывает скриптового бота именно по неравномерным, «слишком ровным» паузам, а не по тембру. Итог в продакшене: падение конверсии в назначенную встречу на 72% относительно субсекундного контура, пик сбросов в первые 15 секунд разговора и рост доли агрессивных перебиваний, которые физически невозможно обработать без аппаратного Barge-In.

Вывод жёсткий: задержка ответа — не UX-метрика, а прямой параметр конверсии B2B-звонка. Её нужно проектировать как бюджет, а не «оптимизировать потом».

2. Архитектура субсекундного голосового контура: от SIP-транка до аудио-фрейма

Телеком-слой строится на трёх уровнях. Транкинг: SIP-транк от оператора → SBC → шлюз (FreeSWITCH с mod_audio_fork, Asterisk с AudioSocket, либо LiveKit SIP Ingress + Audio Egress). Медиа-плоскость: G.711 µ-law 8 кГц из PSTN и Opus 48 кГц из WebRTC приводятся к единому сырому формату — PCM 16 кГц, 16-bit, mono. Ресемплинг делается один раз, на границе (soxr/libsamplerate), потому что каскадный ресемплинг внутри пайплайна добавляет 5–15 мс и вносит фазовые искажения в VAD. Кадрирование: 20 мс фрейм = 320 сэмплов = 640 байт — базовая единица учёта латентности; всё, что крупнее 40 мс, ломает бюджет Barge-In.

Далее — двунаправленный поток фреймов в микросервис агента: WebSocket (проще, но TCP head-of-line при потере пакета) или gRPC bidirectional streaming поверх HTTP/2 с явным backpressure. Каждая сессия — отдельная asyncio-корутина с uvloop, никаких блокирующих вызовов в цикле; TTS/ASR/LLM — внешние пулы через пул соединений с keep-alive. Джиттер-буфер выставляется в 20–60 мс и адаптивно расширяется только при реальных потерях: статический буфер в 100 мс «съедает» четверть бюджета TTFA ещё до старта.

3. Детекция перебиваний (Barge-In) и потоковый VAD на сыром аудио

VAD работает не на файлах, а на скользящем окне: Silero VAD (ONNX, 512 сэмплов ≈ 32 мс), hop 20 мс, выход — вероятность речи. Для телефонного канала обязательна гистерезисная логика: порог активации 0.55–0.6, порог деактивации ниже на 0.1, hangover 200–300 мс, чтобы не рвать фразу на межсловных паузах. Порог калибруется на записях реального канала (компрессия, шум офиса, гарнитура менеджера), а не на чистом студийном сэмпле.

Ключевой элемент — акустическое эхоподавление. Без AEC (WebRTC AEC3 + residual echo suppressor) агент слышит собственный TTS в линии и запускает бесконечный self-interrupt. После AEC остаётся остаточное эхо на уровне −25…−35 дБ — его добивает дополнительный gate по корреляции с опорным сигналом воспроизведения.

Алгоритм прерывания: (1) во время состояния SPEAKING VAD даёт voiced=True два окна подряд; (2) вызывается barge_in_canceller: выставляется флаг flush, отменяются asyncio-таски воспроизведения и генерации LLM, очередь аудио дренируется (Audio Buffer Flusher), вызывается tts.reset() по WebSocket; (3) агент немедленно переходит в LISTENING и продолжает накапливать партиал ASR. Целевое время — <40 мс от детекции до тишины в линии; это измеряемая метрика barge_in_ms, и она должна быть в дашборде рядом с TTFA. Антипаттерн: дождаться конца фразы абонента перед реакцией — это уже не Barge-In, а вежливая очередь с задержкой в 2 секунды.

4. Сквозной стриминг (Cascaded & Speculative Pipeline): декомпозиция TTFA <480 мс

TTFA = T_VAD + T_ASR + T_LLM_TTFT + T_TTS_TTFA + T_NET
  • **T_VAD, 20–40 мс** — детекция окончания фразы: hangover 240 мс реализуется как задержанное решение, но частичный ASR-гипотез стартует раньше, поэтому в бюджет попадает только финальное подтверждение.
  • **T_ASR, 120–150 мс** — sherpa-onnx (Zipformer/Conformer streaming) или Whisper Streaming с инкрементальным декодированием и финализацией по последнему сегменту. Полное пережатие 3-секундного буфера недопустимо — только инкремент.
  • **T_LLM_TTFT, 90–120 мс** — vLLM/SGLang с prefix caching, температурой 0.3 и жёстким лимитом первого предложения в 12–18 токенов. Спекулятивное декодирование (draft 0.5B + верификация Qwen-2.5-7B/32B) даёт 1.8–2.4× на TTFT.
  • **T_TTS_TTFA, 100–140 мс** — потоковый синтез первого смыслового чанка: Kokoro-82M, FastPitch или ElevenLabs Flash через WebSocket. Синтез запускается по достижении первой клаузы (точка/запятая/двоеточие), а не по полному ответу.
  • **T_NET, 30–40 мс** — RTP/WebRTC джиттер, буферизация и сетевой хоп до оператора.
  • Сумма: 360–490 мс — порог абсолютной естественности. Правило бюджетирования: деградация при перегрузке допускается только в LLM-сегменте (укоротить первую фразу), но никогда в TTS и сети. Иначе вы получаете агента, который начинает отвечать быстро, но звучит рвано.

    5. Машиночитаемый манифест знаний: интеграция протокола llms.txt в голосовые агенты

    Голосовое ядро страдает от фонетических галлюцинаций ASR сильнее, чем текстовый чат: названия SaaS, аббревиатуры (SLA, MRR, DPIA), юридические конструкции и бренды декодируются «на слух» и превращаются в бессмысленные токены. Решение — открытая спецификация llms.txt как единый машиночитаемый манифест знаний продукта.

    Три конкретных применения в контуре: (1) источник факта — секции манифеста инжектятся в system prompt и попадают в prefix cache vLLM, снижая TTFT и исключая ответы «из головы»; (2) Biasing Lexicon — из манифеста автоматически генерируется список hotwords и фонетических вариантов, который подмешивается в декодер (boost score в sherpa-onnx, initial_prompt/hotwords в Whisper), из-за чего WER по продуктовым терминам падает в разы; (3) границы компетенции — если факта нет в манифесте, агент не изобретает его, а фиксирует квалификационный вопрос и эскалирует на менеджера. Это же превращает обновление базы знаний в деплой одного текстового файла без пересборки моделей.

    6. Боевой продакшен-кейс платформы First Touch: 22 000+ звонков верификации

    В контуре First Touch субсекундный движок обкатан на 22 000+ B2B-звонках квалификации и подтверждения встреч в сложных SaaS и Enterprise-сделках, где цена ошибки — сорванная встреча с CTO. Фактическая средняя задержка ответа — 460 мс: абонент не отличает агента от живого эксперта и практически не использует фразу «вы бот?».

    Метрики после перевода трафика на субсекундный контур: рост доходимости (Show-Up Rate) до запланированных встреч с 14% до 41%; снижение стоимости одного валидированного контакта в 6.2 раза против классического операторского колл-центра; нулевой процент зависаний при перебиваниях — Barge-In отрабатывает за <40 мс, и абонент ни разу не слышит «хвост» прерванной фразы. Технически критичными оказались три вещи: калибровка VAD на записях конкретного оператора, hotwords из спецификации llms.txt и запрет на любое полное пережатие аудиобуфера.

    7. Production-grade реализация: класс SubsecondVoiceAgentEngine на Python

    import asyncio, time, uuid
    from enum import Enum
    from typing import Optional
    from pydantic import BaseModel, Field
    
    
    class AudioFrame(BaseModel):
        session_id: str
        seq: int
        pcm: bytes                     # 16 kHz / 16-bit / mono / 20 ms = 640 B
        ts_ms: float = Field(default_factory=lambda: time.monotonic() * 1000)
    
    
    class AgentState(str, Enum):
        LISTENING = 'listening'
        THINKING = 'thinking'
        SPEAKING = 'speaking'
        BARGE_IN = 'barge_in'
    
    
    class AgentDialogueState(BaseModel):
        session_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
        state: AgentState = AgentState.LISTENING
        partial_asr: str = ''
        llm_buffer: str = ''
        turn_index: int = 0
    
    
    class VoiceSessionMetrics(BaseModel):
        frames_in: int = 0
        barge_in_count: int = 0
        barge_in_ms: float = 0.0
        ttfa_ms: list = Field(default_factory=list)
    
        def ttfa_p50(self) -> float:
            return sorted(self.ttfa_ms)[len(self.ttfa_ms) // 2] if self.ttfa_ms else 0.0
    
    
    class SubsecondVoiceAgentEngine:
        FRAME_MS, NET_MS, BUDGET_MS = 20, 35, 480
    
        def __init__(self, vad, asr, llm, tts, lexicon,
                     vad_threshold: float = 0.55, min_speech_ms: int = 120):
            self.vad, self.asr, self.llm, self.tts, self.lexicon = vad, asr, llm, tts, lexicon
            self.vad_threshold, self.min_speech_ms = vad_threshold, min_speech_ms
            self.dialogue = AgentDialogueState()
            self.metrics = VoiceSessionMetrics()
            self._speech_ms = 0
            self._out_q: asyncio.Queue = asyncio.Queue()
            self._gen_task: Optional[asyncio.Task] = None
            self._play_task: Optional[asyncio.Task] = None
            self._flush = asyncio.Event()
    
        async def feed(self, frame: AudioFrame) -> None:
            self.metrics.frames_in += 1
            prob = await self.vad(frame.pcm)                  # Silero ONNX, окно 32 мс
            voiced = prob >= self.vad_threshold
            if voiced and self.dialogue.state is AgentState.SPEAKING:
                await self.barge_in_canceller()
            if voiced:
                self._speech_ms += self.FRAME_MS
                if self.dialogue.state is AgentState.LISTENING:
                    self.dialogue.partial_asr += await self.asr.push(frame.pcm)
            else:
                if self._speech_ms >= self.min_speech_ms:
                    await self._finalize_turn(frame.ts_ms)
                self._speech_ms = 0
    
        async def barge_in_canceller(self) -> None:
            t0 = time.monotonic()
            self.dialogue.state = AgentState.BARGE_IN
            self._flush.set()
            for task in (self._play_task, self._gen_task):
                if task and not task.done():
                    task.cancel()
            while not self._out_q.empty():
                self._out_q.get_nowait()                      # Audio Buffer Flusher
            await self.tts.reset()
            self.dialogue.partial_asr, self.dialogue.llm_buffer = '', ''
            self.metrics.barge_in_count += 1
            self.metrics.barge_in_ms = (time.monotonic() - t0) * 1000
            self.dialogue.state = AgentState.LISTENING
            self._flush.clear()
    
        async def _finalize_turn(self, ts_ms: float) -> None:
            t_vad = time.monotonic() * 1000 - ts_ms
            text = await self.asr.finalize(hotwords=self.lexicon.hotwords)
            if text.strip():
                self._gen_task = asyncio.create_task(self._respond(text, t_vad))
    
        async def _respond(self, user_text: str, t_vad_ms: float) -> None:
            self.dialogue.state, first = AgentState.THINKING, True
            t0 = time.monotonic()
            try:
                async for tok in self.llm.stream(user_text, system=self.lexicon.system_prompt):
                    if self._flush.is_set():
                        return
                    self.dialogue.llm_buffer += tok
                    if first or tok in '.!?;':
                        sentence = self.dialogue.llm_buffer.strip()
                        self.dialogue.llm_buffer = ''
                        t_llm = (time.monotonic() - t0) * 1000
                        self._play_task = asyncio.create_task(self._speak(sentence))
                        self.metrics.ttfa_ms.append(
                            t_vad_ms + t_llm + self.tts.last_ttfa_ms + self.NET_MS)
                        first = False
            finally:
                if self.dialogue.state is AgentState.THINKING:
                    self.dialogue.state = AgentState.LISTENING
    
        async def _speak(self, text: str) -> None:
            self.dialogue.state = AgentState.SPEAKING
            async for pcm in self.tts.stream(text):
                if self._flush.is_set():
                    return
                await self._out_q.put(pcm)

    Контракты vad/asr/llm/tts подключаются как адаптеры (Silero ONNX, sherpa-onnx, vLLM OpenAI-совместимый, Kokoro/ElevenLabs WebSocket). Ключевое: _out_q дренируется синхронно внутри отмены, а tts.reset() закрывает серверный буфер синтеза — иначе хвост фразы доигрывает в линию.

    8. Дорожная карта внедрения субсекундных голосовых агентов за 4 спринта (30 дней)

    Спринт 1 (дни 1–7). Развёртывание SIP/WebRTC шлюза (LiveKit или Asterisk/FreeSWITCH), настройка RTP-трансляции в PCM 16 кГц, сбор 3–5 часов записей реальных звонков и калибровка Silero VAD с AEC3 на телефонных шумах. Выход: стабильный двунаправленный стрим с измеренным jitter/drop.

    Спринт 2 (дни 8–14). Интеграция стримингового ASR (sherpa-onnx или Whisper Streaming) с кастомным фонетическим словарём, сгенерированным из llms.txt. Метрика выхода: WER по продуктовым терминам и замер T_ASR.

    Спринт 3 (дни 15–21). Спекулятивный стриминг LLM на vLLM с prefix caching и потоковый TTS (Kokoro), отладка Barge-In с прерыванием <40 мс. Выход: TTFA p50 <480 мс на 50 одновременных сессиях.

    Спринт 4 (дни 22–30). Нагрузочное тестирование на 500+ одновременных звонках, A/B-тесты конверсии против операторского пула, боевой запуск в контур CRM с эскалацией на менеджера. Выход: стабильный p95 TTFA и подтверждённый рост Show-Up Rate.

    Заключение. Субсекундность достигается не одной моделью, а бюджетом: 20–40 мс VAD, 120–150 мс ASR, 90–120 мс LLM TTFT, 100–140 мс TTS TTFA, 30–40 мс сеть. Barge-In — обязательный компонент, а не «фича»: без него любая задержка умножается на раздражение абонента. Готовы проверить свой контур на реальных звонках — First Touch проводит аудит телефонной инфраструктуры, нагрузочное тестирование на 500+ сессиях и развёртывание субсекундных AI-агентов под ключ.

    ---

    Alexander Bond — AI-разработчик и архитектор автономных систем, First Touch.


    🚀 Проект автора: First Touch (https://aifirsttouch.ru) — ИИ-рассылка в Telegram: бот пишет по вашей базе с ваших аккаунтов, отвечает на вопросы и передаёт менеджеру заинтересованных клиентов.

    Автор: Alexander Bond — AI-разработчик и архитектор автономных систем


    Теги: голосовые ai агенты, webrtc, sip телефония, barge-in, b2b продажи, first touch

    Автоматизируйте привлечение B2B-клиентов

    Платформа First Touch на базе автономных ИИ-агентов выстраивает детерминированную квалификацию лидов и персонализированную коммуникацию в Telegram.

    Попробовать First Touch