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

Потоковое распознавание и синтез (gRPC)

Помимо HTTP REST API, сервис предоставляет gRPC-интерфейс для высокопроизводительных интеграций и потоковой обработки в реальном времени. Это совместимый поднабор Yandex SpeechKit API v3, а не полная реализация всех сообщений STT v3.

Подключение

import grpc

channel = grpc.insecure_channel("api.speech.example.com:50052")

В примерах используется публичный порт 50052. В Docker Compose он задаётся API_GRPC_PORT (по умолчанию 50052) и пробрасывается во внутренний GRPC_PORT=50051. При нативном запуске порт сервера задаётся непосредственно GRPC_PORTrun.ps150052). Не путайте публичный API с вышестоящим Riva-сервером, который обычно слушает 50051.

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

gRPC требует аутентификации: передавайте действующий API-ключ в metadata. Запросы без валидного ключа отклоняются с кодом UNAUTHENTICATED. Принимаются обе схемы — Bearer (как в клиентах в стиле Yandex) и Api-Key:

authorization: Bearer <api-key>

Сервисы

Recognizer — потоковое распознавание (STT v3)

Сервис speechkit.stt.v3.Recognizer реализует основной потоковый контракт Yandex SpeechKit STT v3.

Метод Описание
RecognizeStreaming Двунаправленный поток: клиент отправляет аудио (StreamingRequest), сервер возвращает промежуточные и итоговые результаты (StreamingResponse)

Первый кадр от клиента должен содержать session_options; только после него отправляйте аудио. Сервер технически может временно буферизовать ранние чанки, но это не является контрактом. Поток, завершившийся без конфигурации сессии, сейчас отклоняется с INTERNAL. Реализованы следующие параметры:

Поле Поведение
recognition_model.model Выбор STT-маршрута
recognition_model.language_restriction.language_code[0] Языковая подсказка; остальные коды и restriction_type пока не применяются
recognition_model.audio_format.raw_audio.sample_rate_hertz Частота входного PCM
recognition_model.audio_format.raw_audio.audio_channel_count Число interleaved-каналов, от 1 до 32
recognition_model.text_normalization Нормализация, profanity filter, литературный текст/пунктуация и форматирование телефонов
eou_classifier.default_classifier.max_pause_between_words_hint_ms Пауза завершения фразы: управляет splitter T-One, а для Riva применяется как endpointing и/или backend EOU buffer согласно STT_STREAM_EOU_MODE

eou_classifier.external_classifier пока принимается protobuf-схемой, но игнорируется. Параметры HTTP v3 recognitionClassifier, speakerLabeling, speechAnalysis и summarization в потоковую gRPC-сессию не входят.

После конфигурации клиент отправляет little-endian PCM16 в chunk.data. Для нескольких каналов сэмплы должны быть interleaved; сервер разделяет каналы и распознаёт их параллельно. Поток, оборванный посередине многоканального PCM-фрейма, отклоняется с INVALID_ARGUMENT.

Вместо байтов можно отправить silence_chunk.duration_ms. Сервер лениво добавляет нулевой PCM в исходную временную шкалу; допустимая длительность одного события — от 1 до 300000 мс. Для тишины частота должна быть задана в raw_audio либо иметь серверное значение по умолчанию.

Минимальный клиент для mono PCM16 16 кГц:

import os

import grpc

from speechkit.stt.v3 import stt_pb2, stt_service_pb2_grpc


def requests():
    options = stt_pb2.StreamingOptions()
    model = options.recognition_model
    model.model = "T-one"
    model.audio_format.raw_audio.sample_rate_hertz = 16_000
    model.audio_format.raw_audio.audio_channel_count = 1
    model.language_restriction.language_code.append("ru-RU")
    yield stt_pb2.StreamingRequest(session_options=options)

    with open("audio.pcm", "rb") as source:
        while chunk := source.read(3200):  # около 100 мс
            yield stt_pb2.StreamingRequest(
                chunk=stt_pb2.AudioChunk(data=chunk)
            )


channel = grpc.insecure_channel("api.speech.example.com:50052")
stub = stt_service_pb2_grpc.RecognizerStub(channel)
metadata = (("authorization", f"Bearer {os.environ['API_KEY']}"),)

for response in stub.RecognizeStreaming(requests(), metadata=metadata):
    event = response.WhichOneof("Event")
    if event in {"partial", "final"}:
        update = getattr(response, event)
        print(event, update.channel_tag, update.alternatives[0].text)
    elif event == "status_code":
        print("closed", response.status_code.code_type)

Импорт в клиенте зависит от package path, который использовался при генерации кода из stt.proto; имена protobuf-сообщений остаются теми же.

Маршрутизация моделей и псевдостриминг

Выбор модели задаётся в recognition_model.model:

  • SpeechExpert-STT — гибрид: T-One строит накопительные preview-сообщения, а после завершения входа SpeechExpert-STT распознаёт весь накопленный PCM и возвращает единственный авторитетный final;
  • T-one — прямой поток T-One с промежуточными и пофразовыми final;
  • GigaAM-Multilingual-Large-CTC и устаревшие алиасы GigaAM-v3-CTC / SpeechExpert-STT-Enhanced — T-One preview и единственный offline-финал multilingual GigaAM, только для русского; казахский, кыргызский и узбекский доступны через асинхронное распознавание готового файла;
  • пустое значение использует RIVA_ONLINE_MODEL; штатное значение — SpeechExpert-STT.

В гибридном режиме пофразовые финалы T-One добавляются к накопленному preview, но наружу всё равно отправляются как partial. Если offline-модель вернула пустой текст, последний накопленный preview повышается до итогового результата.

T-One принимает 8 кГц mono; входной канал автоматически ресемплируется. По умолчанию используется CTC greedy decoder (TONE_DECODER=greedy). Опциональный TONE_DECODER=kenlm требует kenlm.bin и зависимостей KenLM; provisional partial при любом режиме декодируется greedy, а закрытая фраза — выбранным декодером.

Статус KenLM

Штатный проект работает на Python 3.13, а зависимость kenlm объявлена только для Python ниже 3.13, поэтому стандартные uv, Docker Compose и run.ps1 используют greedy; run.ps1 дополнительно переопределяет значение после загрузки .env. Для эксперимента с KenLM нужна отдельная совместимая сборка runtime, установленные pyctcdecode/kenlm и доступный процессу API файл kenlm.bin. В контейнере путь к файлу должен быть контейнерным и файл нужно явно смонтировать; Windows-путь из .env.example сам по себе не монтируется.

Vosk удалён

В псевдостриминге и прямом потоке Vosk полностью заменён на T-One; Vosk-клиент, контейнеры и переменные окружения больше не используются. Для плавной миграции старые идентификаторы Vosk 0.62, Vosk 0.56, Vosk 0.54, Zipformer2-RU, Vosk-Streaming-RU, Sherpa-Streaming-RU и SpeechExpert-STT-Streaming пока маршрутизируются в T-one. Новые клиенты должны отправлять T-one. Для списания кредитов эти legacy-ID также считаются T-one; отдельного тарифа Vosk в публичном списке больше нет, хотя исходное имя сохраняется в истории ранее созданных записей и транзакций.

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

Сервер возвращает только partial, final и завершающий status_code. События HTTP v3 finalRefinement, eouUpdate, classifierUpdate, аналитика спикеров/разговора и суммаризация в этом gRPC-контракте не реализованы.

Для T-One поля Alternative.start_time_ms, Alternative.end_time_ms и Alternative.words[] содержат границы речи на шкале исходного потока, рассчитанные из acoustic frames модели с шагом 30 мс. Это время речи, а не момент получения сетевого чанка. Нулевой start_time_ms валиден.

В гибридном режиме все preview получают накопленный диапазон T-One. Итоговый offline-текст получает тот же диапазон; если его текст отличается от preview, words у финала остаётся пустым, потому что слово-в-слово выравнивание неизвестно. Для backend без нативного тайминга scalar-поля protobuf остаются со значением 0, а words пуст.

AudioCursors — отдельная шкала прогресса транспорта:

Поле Семантика
received_data_ms Объём принятых PCM-данных, включая silence_chunk
partial_time_ms Монотонная граница обработанного аудио; не меньше final_time_ms
final_time_ms Монотонный максимум границ отправленных финалов
eou_time_ms Сейчас совпадает с final_time_ms
final_index Число отправленных final в сессии

Нативные границы в Alternative не подменяются курсорами: уточнённый финал может заканчиваться раньше предыдущего partial, тогда как partial_time_ms всё равно движется только вперёд. Для многоканального аудио AlternativeUpdate.channel_tag равен строковому номеру канала, начиная с "1". После обработки всего ввода сервер отправляет отдельный status_code с code_type=CLOSED.

response_wall_time_ms в gRPC содержит Unix-время формирования ответа в миллисекундах. Это отличается от одноимённого JSON-поля HTTP v3, где хранится прошедшее время обработки операции.

Нормализация применяется непосредственно к каждому partial и final, отдельного finalRefinement нет. Если объект text_normalization передан с PHONE_FORMATTING_MODE_UNSPECIFIED, форматирование телефонов считается включённым. words[] сохраняют токены backend и поэтому после пунктуации, фильтрации или нормализации могут уже не совпадать с итоговым text.

WebSocket STT

Для браузерных клиентов и более простого JSON-протокола используйте отдельный WebSocket STT. Он принимает mono PCM16 16 кГц и не требует генерации клиентского кода из protobuf.

Synthesizer — потоковый синтез (TTS v3)

Сервис speechkit.tts.v3.Synthesizer поддерживает синтез речи через методы UtteranceSynthesis и StreamSynthesis.

Server Reflection

Сервер поддерживает gRPC Server Reflection — API можно исследовать через grpcurl:

# Список доступных сервисов
grpcurl -plaintext -H "authorization: Bearer ${API_KEY}" api.speech.example.com:50052 list

# Описание доступных методов
grpcurl -plaintext -H "authorization: Bearer ${API_KEY}" api.speech.example.com:50052 describe

Proto-файлы

Proto-файлы находятся в app/grpc_services/proto/speechkit/. Для генерации клиентского кода используйте их либо Server Reflection.

См. также