Skip to content

Жизненный цикл сессии

Состояния сессии

СостояниеОписание
WaitingAuthСоединение установлено, ожидается session.begin (до 10 сек)
ReadyСессия аутентифицирована, готова принимать текст
GeneratingИдёт синтез речи, клиент получает audio.chunk

Типичный поток взаимодействия

Буферизация текста

Сервер не отправляет каждый text.chunk на синтез по отдельности. Текст накапливается в буфере и сбрасывается на синтез при выполнении определённых условий.

Двухуровневые пороги

ПорогПо умолчаниюНазначение
first_chunk_threshold120 символовМинимум текста для первого сброса — обеспечивает низкую задержку до первого аудио
subsequent_chunk_threshold160 символовПорог для последующих сбросов — даёт больше контекста для качественного синтеза

Алгоритм сброса буфера

  1. Буфер < порог → ждём следующий text.chunk
  2. Буфер ≥ порог + найдена граница предложения (. ? ! ; …) → сброс до этой границы
  3. Буфер ≥ порог, но < 200 символов, нет границы предложения → ждём (предложение может завершиться)
  4. Буфер ≥ 200 символов (hard limit) → принудительный сброс:
    • Ищем ближайшую запятую или тире (, – —)
    • Иначе — ищем границу слова (пробел)
    • Иначе — сбрасываем весь буфер

Принудительный сброс

Два способа немедленно отправить буфер на синтез:

  • flush: true в text.chunk — сбрасывает весь текущий буфер, включая только что отправленный текст
  • text.end — сбрасывает остаток буфера и завершает генерацию

Рекомендация для коротких фраз

Если общий объём текста менее 120 символов (например, короткий ответ бота), всегда передавайте flush: true — иначе текст будет ждать в буфере до text.end.

Рекомендация для LLM-стриминга

При потоковом получении текста от LLM отправляйте токены без flush — буфер сам определит оптимальные границы для синтеза. Используйте text.end когда LLM завершит генерацию.

Прерывание генерации (interrupt)

Прерывание позволяет мгновенно остановить синтез — например, когда пользователь начал говорить поверх бота.

Что происходит на сервере

  1. Устанавливается флаг прерывания
  2. Отменяется фоновая задача синтеза
  3. Очищается текстовый буфер
  4. Очищается очередь ожидающих сегментов
  5. Сбрасывается счётчик sequence
  6. Сессия переходит в состояние Ready
  7. Отправляется 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)
        break

Edge 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 секунд).

Следующие шаги