Перейти к содержанию

Потоковое распознавание — WebSocket

WebSocket STT предназначен для живого звука из браузера или приложения. Клиент отправляет бинарные PCM-фреймы, а сервер возвращает JSON с промежуточным и финальным текстом.

wss://api.speech.example.com/api/stt/v1/ws

В отличие от файловых методов, поток не сохраняет запись или транскрипт и не создаёт операцию. Коррекция LLM, NER, диаризация и суммаризация здесь не выполняются.

Аутентификация

Используйте один из способов:

  • Authorization: Api-Key <ключ> или Authorization: Bearer <ключ>;
  • cookie активной portal-сессии;
  • legacy query-параметр ?api_key=<ключ>.

Для новых клиентов предпочтителен заголовок: query string может попасть в access-логи. Без аутентификации сервер закрывает соединение с кодом 1008, если не включён PUBLIC_API_ALLOW_ANONYMOUS.

Перед распознаванием сервер проверяет, что на балансе достаточно кредитов хотя бы на одну секунду STT. При отказе он отправляет {"error":"...","code":"insufficient_credits"} и закрывает соединение с кодом 1008. Фактический объём принятого аудио учитывается после потока.

Входной поток

Аудио имеет фиксированный формат:

Параметр Значение
Кодирование signed PCM16 little-endian
Частота 16 кГц
Каналы mono

Первое сообщение может быть JSON-конфигурацией:

{"language":"ru-RU","model":"T-one"}
Поле По умолчанию Описание
language ru-RU Языковая подсказка backend
model RIVA_ONLINE_MODEL сервера, обычно SpeechExpert-STT Маршрут распознавания

Конфигурация необязательна: первым сообщением можно сразу отправить бинарный PCM-фрейм. Сервер ждёт первое сообщение до 30 секунд, после чего продолжает с настройками по умолчанию. Последующие бинарные сообщения помещаются в очередь до 256 фреймов; если обработка отстаёт, чтение WebSocket приостанавливается и создаёт обратное давление клиенту.

Поддерживаемые потоковые маршруты:

  • T-one — прямые partial и пофразовые final T-One;
  • SpeechExpert-STT — T-One для накопительных preview и один авторитетный offline-final SpeechExpert-STT;
  • GigaAM-Multilingual-Large-CTC и устаревшие алиасы GigaAM-v3-CTC / SpeechExpert-STT-Enhanced — T-One preview и один offline-final multilingual GigaAM, только для русского. Для kk-KZ, ky-KG и uz-UZ используйте асинхронное распознавание файла: русская T-One не может формировать корректные partial на этих языках.

После всех PCM-фреймов отправьте один из вариантов EOF и продолжайте читать ответы до закрытия потока:

{"event":"eof"}
{"eof":true}

Ответы и таймстемпы

Каждое серверное сообщение — самостоятельный JSON-объект. В каждом событии распознавания присутствуют text и is_final; тайминги добавляются, только если backend их вернул. Сообщения об ошибках имеют отдельную форму без этих полей.

{
  "text": "добрый день",
  "is_final": false,
  "start_ms": 340,
  "end_ms": 1080,
  "timing_source": "t-one",
  "words": [
    {"text": "добрый", "start_ms": 340, "end_ms": 720},
    {"text": "день", "start_ms": 760, "end_ms": 1080}
  ]
}

start_ms и end_ms отсчитываются от начала входного потока; нулевое начало валидно. Сейчас WebSocket возвращает нативные тайминги для T-One с timing_source: "t-one". Потоковые ветки Riva пока не передают тайминги в это событие; для backend без нативного тайминга start_ms, end_ms, timing_source и words опускаются.

Для hybrid-маршрута финальные границы берутся из T-One preview. Если offline- текст отличается от preview, у финального сообщения words будет пустым: слово-в-слово выравнивание неизвестно. На финальный text также накладывается восстановление пунктуации, поэтому слова описывают исходные токены backend.

Ошибка распознавания возвращается как {"error":"..."}. Отдельного события done нет: после EOF клиент дочитывает все final-сообщения до закрытия соединения.

Пример Python

import asyncio
import json
import os

import websockets


async def main():
    uri = "wss://api.speech.example.com/api/stt/v1/ws"
    headers = {"Authorization": f"Api-Key {os.environ['API_KEY']}"}

    async with websockets.connect(uri, additional_headers=headers) as ws:
        await ws.send(json.dumps({"language": "ru-RU", "model": "T-one"}))

        with open("audio.pcm", "rb") as source:
            while chunk := source.read(3200):  # около 100 мс
                await ws.send(chunk)

        await ws.send(json.dumps({"event": "eof"}))

        async for message in ws:
            event = json.loads(message)
            print(event)


asyncio.run(main())

Выбор потокового API

Интерфейс Когда использовать
WebSocket Браузер, простой JSON-протокол, mono PCM16 16 кГц
gRPC Protobuf, несколько каналов, произвольная частота PCM, silence_chunk и нормализация
OpenAI SSE Псевдострим готового загруженного файла; не для живого входящего аудио

См. также