Python Junior
DS
Ответы
Ок — дам всё 4 в 1: **архитектура**, **нагрузка/стоимость (с формулами + примером)**, **узкие места**, **как масштабировать SFU (LiveKit) правильно**. --- ## 1) Архитектура LiveKit + FastAPI + ASR/TTS (реалтайм, с barge-in) Ниже схема “правильного” голосового тьютора: клиент говорит → стрим в ASR → LLM решает → TTS стримится обратно → barge-in прерывает TTS. ``` [Browser] - WebRTC mic track (Opus) - WebRTC speaker track (Opus) - DataChannel (events: partial ASR, state, barge-in) | | 1) WebRTC to LiveKit (SFU) v [LiveKit SFU Cluster] <----> [TURN (coturn)] (если нужен relay) | | 2) Server-side bot joins room (LiveKit server SDK) v [Voice Bot Service] (FastAPI + async workers) - Session manager (room_id, participant_id, state machine) - Audio ingress (subscribe to mic track) - VAD (server-side) - Streaming ASR client - LLM orchestration (tools, memory) - TTS streaming client - Audio egress (publish bot audio track) - Barge-in controller (stop TTS + flush buffers) | +--> [Streaming ASR] (Whisper/Deepgram/etc) | +--> [LLM] (OpenAI / локальная) | +--> [Streaming TTS] (OpenAI / ElevenLabs / локальная) | +--> [Redis] (state, locks, rate limits) | +--> [Postgres] (users, lessons, progress) | +--> [Object Storage] (recordings, logs, audits) | +--> [Metrics/Tracing] (Prometheus, Grafana, OTEL) ``` ### Ключевые детали, без которых “бесит” * **Barge-in**: как только VAD видит речь ученика, ты: 1. останавливаешь TTS-stream, 2. закрываешь публикацию аудиофреймов, 3. отправляешь клиенту событие “bot_speaking=false”. * **DataChannel**: туда лучше слать `partial_transcript`, `final_transcript`, `bot_state`, `latency` — UI становится живым. * **Единая state machine** на сервере: `LISTENING → THINKING → SPEAKING → (INTERRUPTED)`. --- ## 2) Нагрузка и примерная стоимость (как считать) Считаем отдельно **(A) SFU/сеть** и **(B) ASR/LLM/TTS**. ### 2.1 Трафик (очень грубо, но практично) Аудио WebRTC обычно Opus 16–32 kbps (часто ближе к 24 kbps). Возьмём консервативно **32 kbps**. Перевод: * 32 kbps = 32,000 бит/сек ≈ 4,000 байт/сек ≈ **3.9 KB/s** * В минуту: 3.9 × 60 ≈ **234 KB/min** * В час: 234 × 60 ≈ **14,040 KB/h ≈ 13.7 MB/h** (на один поток, в одну сторону) У нас 2 направления на участника: * uplink (ученик → SFU) ~ 13.7 MB/h * downlink (SFU → ученик) ~ 13.7 MB/h И для бота аналогично (бот тоже участник комнаты). #### Пример: 1 комната = 1 ученик + 1 бот * Ученик: ~27.4 MB/h (в обе стороны) * Бот: ~27.4 MB/h Итого на комнату ~ **54.8 MB/h** чистого аудио (без оверхедов). С оверхедом/ретрансляциями/ICE/RTCP считай ×1.3 → **~71 MB/h**. #### Пример: 100 одновременных занятий (100 комнат) * 71 MB/h × 100 = **7,100 MB/h ≈ 6.9 GB/h** * В день (8 часов пикового): 6.9 × 8 ≈ **55 GB/day** Это объясняет, почему Cloud может “вдруг” стать дорогим: оплата часто привязана к participant-minutes и/или egress. --- ### 2.2 Нагрузка на вычисления (серверный бот) Вычислительные блоки: **VAD** * копейки по CPU (с WebRTC VAD / Silero VAD на CPU норм). **ASR streaming** * если внешний провайдер: нагрузка на CPU небольшая, но важна **задержка сети**. * если свой ASR на GPU: там уже основной расход. **LLM** * “мозги” обычно дают самую большую вариативность по цене/латентности. **TTS streaming** * тоже либо провайдер, либо свой GPU/CPU (зависит от модели). --- ### 2.3 Стоимость “по формуле” (чтобы ты мог подставить свои цифры) Пусть: * `C` = concurrent rooms (одновременных занятий) * `H` = часов в месяц на одну комнату (средняя занятость) * `R` = средний аудио-битрейт (kbps), например 32 * `k` = коэффициент оверхеда, например 1.3 Тогда аудио-трафик на комнату в час (две стороны, два участника): * один поток в час: `R kbps → (R/8) KB/s → (R/8)*3600 KB/h` * упрощённо: при 32 kbps ≈ 13.7 MB/h на направление * на комнату: `~13.7 * 4 = 54.8 MB/h`, и умножить на `k` **Traffic_month ≈ C * H * 54.8MB * k** --- ### 2.4 “Пример цифр” (чтобы почувствовать масштаб) Допустим: * 200 одновременных занятий в пике, * 4 часа пиковой нагрузки в день, * 30 дней, * 32 kbps, k=1.3 Сначала считаем H на одну комнату в месяц при пике: * 4 часа/день × 30 = **120 h/month** (в пике на “канал комнаты”) Traffic: * на комнату: 54.8 × 1.3 ≈ **71.24 MB/h** * на 200 комнат: 71.24 × 200 = **14,248 MB/h ≈ 13.9 GB/h** * в месяц: 13.9 × 120 ≈ **1,668 GB ≈ 1.63 TB/month** только аудио на пике Если облако тарифицирует egress/минуты — вот откуда счёт. --- ## 3) Где bottleneck в voice AI пайплайне (по опыту продакшена) ### Главный “убийца UX” — не цена, а **латентность** и **джиттер** Топ-узкие места: 1. **ASR partial latency** * если частичные гипотезы приходят редко/поздно, репетитор “тупит”. * цель: первые partial < 300–500 мс после начала речи. 2. **Turn-taking (VAD + endpointing)** * самая частая проблема: “не понимает, когда я закончил”. * endpointing обычно нужно делать гибридно: * VAD + таймаут тишины (например 350–700 мс), * плюс “semantic endpointing” (если фраза явно завершена). 3. **LLM time-to-first-token** * даже 700–1200 мс ощущается как пауза. * решение: * заранее заготовленные фразы (“угу”, “секунду”), * параллельно готовить следующий шаг, * стримить ответ и рано запускать TTS. 4. **TTS buffering** * если ты ждёшь “слишком много” перед началом воспроизведения — голос появляется поздно. * цель: старт звука через 200–400 мс после LLM first token (реально достижимо). 5. **Barge-in race conditions** * классика: ученик говорит, а бот ещё 0.5–1.5 сек продолжает болтать (буфер не сброшен). * решение: * “hard stop” публикации трека, * отдельный `audio_seq`/`generation_id` у TTS — всё старое дропать. --- ## 4) Как масштабировать SFU (LiveKit) правильно ### 4.1 Важное про SFU SFU не “кодирует заново”, он **форвардит**. Нагрузка чаще упирается в: * сеть (egress), * количество подписок/forwarding, * ICE/TURN (особенно relay), * пики CPU на шифровании/пакетизации. ### 4.2 Паттерн масштабирования LiveKit **Горизонтально**: несколько SFU нод + общий control plane. Типовая схема: * **LiveKit Server** как кластер (несколько нод) * **Redis** общий (координация/состояние) * **Load balancer** для WebSocket/HTTP signaling * **Региональность** (если Европа/США разные) Ключевой момент: * комнаты должны быть “прикреплены” к конкретной ноде (room affinity / sticky). ### 4.3 Sticky sessions * Signaling может быть через LB, но **после того как room создан**, участники должны попадать на ту же ноду/регион. * Делается через: * встроенные механизмы LiveKit (распределение), * или свой “room allocator” сервис (простой: выбираем ноду по нагрузке и выдаём URL). ### 4.4 TURN масштабирование TURN часто становится бутылочным горлышком при: * корпоративных сетях, * мобильных операторах, * IPv6/CGNAT, * блокировках UDP. Правила: * TURN держать **отдельно** от SFU (разные инстансы). * мониторить долю relay-connections (%). * если relay% растёт — трафик и цена растут драматически. ### 4.5 Метрики, без которых нельзя На каждую ноду: * CPU, RAM * Network in/out * packet loss, jitter, RTT (по RTP/RTCP) * active participants, active tracks * relay vs p2p vs udp * “join time” (время подключения) --- ## Что я бы сделал именно для твоего AI Tutor (FastAPI) **MVP/первые пользователи** * LiveKit Cloud (или 1 регион self-hosted) * бот как отдельный сервис (не в FastAPI веб-процессе), а в воркерах * Redis для сессий * строгие latency budgets и метрики **Когда пойдёт нагрузка** * отдельный “Allocator” (распределять комнаты по SFU нодам) * мульти-регион * выделенные TURN * egress контроль и сжатие --- Если хочешь, следующим сообщением я: 1. дам **конкретный минимальный стек сервисов (docker-compose / k8s логика)**, 2. и посчитаю **2 сценария стоимости**: * LiveKit Cloud * self-hosted (VPS/Hetzner/AWS) на твоих целевых цифрах: **сколько одновременных учеников и сколько минут в месяц ты планируешь?** Если не хочешь уточнять — могу взять типовой сценарий: 100 / 300 / 1000 concurrent и показать табличкой.