Appearance
Жизненный цикл сессии
Состояния сессии
| Состояние | Описание |
|---|---|
| WaitingAuth | Соединение установлено, ожидается session.begin (до 10 сек) |
| Ready | Сессия аутентифицирована, готова принимать текст |
| Generating | Идёт синтез речи, клиент получает audio.chunk |
Типичный поток взаимодействия
Буферизация текста
Сервер не отправляет каждый text.chunk на синтез по отдельности. Текст накапливается в буфере и сбрасывается на синтез при выполнении определённых условий.
Двухуровневые пороги
| Порог | По умолчанию | Назначение |
|---|---|---|
first_chunk_threshold | 120 символов | Минимум текста для первого сброса — обеспечивает низкую задержку до первого аудио |
subsequent_chunk_threshold | 160 символов | Порог для последующих сбросов — даёт больше контекста для качественного синтеза |
Алгоритм сброса буфера
- Буфер < порог → ждём следующий
text.chunk - Буфер ≥ порог + найдена граница предложения (
. ? ! ; …) → сброс до этой границы - Буфер ≥ порог, но < 200 символов, нет границы предложения → ждём (предложение может завершиться)
- Буфер ≥ 200 символов (hard limit) → принудительный сброс:
- Ищем ближайшую запятую или тире (
, – —) - Иначе — ищем границу слова (пробел)
- Иначе — сбрасываем весь буфер
- Ищем ближайшую запятую или тире (
Принудительный сброс
Два способа немедленно отправить буфер на синтез:
flush: trueвtext.chunk— сбрасывает весь текущий буфер, включая только что отправленный текстtext.end— сбрасывает остаток буфера и завершает генерацию
Рекомендация для коротких фраз
Если общий объём текста менее 120 символов (например, короткий ответ бота), всегда передавайте flush: true — иначе текст будет ждать в буфере до text.end.
Рекомендация для LLM-стриминга
При потоковом получении текста от LLM отправляйте токены без flush — буфер сам определит оптимальные границы для синтеза. Используйте text.end когда LLM завершит генерацию.
Прерывание генерации (interrupt)
Прерывание позволяет мгновенно остановить синтез — например, когда пользователь начал говорить поверх бота.
Что происходит на сервере
- Устанавливается флаг прерывания
- Отменяется фоновая задача синтеза
- Очищается текстовый буфер
- Очищается очередь ожидающих сегментов
- Сбрасывается счётчик sequence
- Сессия переходит в состояние Ready
- Отправляется
generation.interrupted
Что получает клиент
Обязательно: drain после interrupt
После отправки interrupt клиент обязан продолжать читать сообщения из WebSocket до получения generation.interrupted. Между отправкой interrupt и получением generation.interrupted в канале могут быть audio.chunk сообщения, которые сервер успел отправить до обработки прерывания.
Эти чанки нужно вычитать из WebSocket, но НЕ воспроизводить на клиенте.
Никогда не прекращайте чтение WebSocket после отправки interrupt — это приведёт к рассинхронизации протокола.
Паттерн обработки на клиенте
python
# Отправляем interrupt
await ws.send_json({"event": "interrupt"})
# Вычитываем канал до generation.interrupted
while True:
msg = await ws.receive()
data = json.loads(msg.data)
if data["event"] == "generation.interrupted":
break # Прерывание подтверждено, сессия в READY
elif data["event"] == "audio.chunk":
pass # Игнорируем — НЕ воспроизводим
elif data["event"] == "error":
handle_error(data)
breakEdge cases
| Ситуация | Поведение |
|---|---|
| Interrupt в состоянии READY (нет активной генерации) | Допустимо. Вернёт generation.interrupted с chunks_sent: 0 |
| Двойной interrupt | Безопасно. Второй вернёт chunks_sent: 0 |
Interrupt после text.end, но до generation.complete | Генерация прервётся, придёт generation.interrupted вместо generation.complete |
Множественные генерации
После generation.complete или generation.interrupted сессия возвращается в состояние Ready:
- Счётчик
sequenceсбрасывается в 0 - Можно сразу отправлять новый текст через
text.chunk - Переподключение не требуется
Это позволяет вести диалог в рамках одного WebSocket-соединения: каждая реплика бота — отдельная генерация.
Keepalive
Для предотвращения inactivity_timeout отправляйте ping каждые 10–20 секунд в периоды, когда нет активного обмена данными.
json
→ {"event": "ping"}
← {"event": "pong"}ping можно отправлять в любом состоянии, включая до аутентификации и во время генерации.
Inactivity Timeout
По умолчанию соединение закрывается через 30 секунд без активности (код 4008). Активностью считается любое сообщение от клиента (включая ping).
Значение настраивается в session.begin через поле inactivity_timeout (допустимый диапазон: 5–180 секунд).
Следующие шаги
- Примеры интеграции — полный Python-клиент, обработка interrupt, LLM-стриминг