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 и показать табличкой.