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

Асинхронное распознавание — STT v3

Асинхронное распознавание предназначено для длинных записей: сервис обрабатывает файл в фоне, а вы получаете результат по идентификатору операции.

Метод совместим с Yandex SpeechKit STT v3. Отправьте аудио, получите id операции и запросите результат, когда он будет готов.

Как это работает

sequenceDiagram
    participant C as Клиент
    participant API as API

    C->>API: POST /api/stt/v3/recognizeFile
    API-->>C: {"id": "abc-123", "done": false}

    loop Ожидание
        C->>API: GET /api/operations/abc-123
        API-->>C: {"id": "abc-123", "done": false}
    end

    C->>API: GET /api/operations/abc-123
    API-->>C: {"id": "abc-123", "done": true, "response": {...}}

    C->>API: GET /api/stt/v3/getRecognition?operationId=abc-123
    API-->>C: Конечный JSONL-поток событий
  1. Отправьте аудио — получите id операции.
  2. Периодически запрашивайте статус операции по id.
  3. Когда в ответе появится done: true, заберите итоговый агрегат из поля response.
  4. Если нужен совместимый поток StreamingResponse, вызовите getRecognition с тем же id.

HTTP-запрос

POST /api/stt/v3/recognizeFile

Запрос отправляется в формате application/json и требует аутентификации — заголовок Authorization: Api-Key <ключ> (см. Аутентификация).

Параметры запроса

Источник аудио

Укажите ровно один из двух параметров:

Параметр Тип Описание
content string Аудио в кодировке Base64
uri string Публичный HTTP(S)-URL для скачивания аудиофайла

uri не принимает локальные/приватные IP, userinfo и произвольные схемы. Редиректы и TLS проверяются на каждом переходе, а объём скачивания ограничен 500 МБ. Проверка источника и скачивание выполняются внутри фоновой операции, поэтому часть ошибок появится в Operation.error, а не в исходном ответе создания задачи.

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

Параметр Тип По умолчанию Описание
includeVadSegments boolean false Запросить интервалы речи. Доступность и точность интервалов зависят от модели; при диаризации разметка соответствует speakerSegments. Для моделей без временной разметки поле может быть null
recognitionClassifier object — Классификаторы распознанной речи. Это основное расположение поля; устаревшее recognitionModel.recognitionClassifier пока принимается для обратной совместимости
speakerLabeling object — Настройки разметки спикеров в формате SpeechKit STT v3
speechAnalysis object — Детерминированная аналитика речи по таймстемпам: статистика спикеров, паузы, одновременная речь и перебивания
summarization object — LLM-суммаризация нормализованной транскрипции в текстовом или структурированном формате

Разметка спикеров (speakerLabeling)

Параметр Тип По умолчанию Описание
speakerLabeling string SPEAKER_LABELING_DISABLED SPEAKER_LABELING_ENABLED включает диаризацию, SPEAKER_LABELING_DISABLED выключает её
maxSpeakers integer — Расширение Speech Expert: максимальное число спикеров от 1 до 20. Gemini определяет спикеров автоматически и поддерживает значение не более 8; значение 1–8 не задаёт Google жёсткую цель

Стандартное поле speakerLabeling принимается в совместимом со SpeechKit формате. Поле maxSpeakers можно не передавать; оно является необязательным расширением Speech Expert.

Для gemini-3.5-transcribe включённый speakerLabeling использует диаризацию Google. Границы слов и метки спикеров сохраняются в совместимом ответе.

Аналитика речи (speechAnalysis)

Параметр Тип По умолчанию Описание
enableSpeakerAnalysis boolean false Статистика длительности речи и тишины, темпа и реплик каждого спикера
enableConversationAnalysis boolean false Общая речь и тишина, одновременная речь и перебивания; результат появляется при наличии минимум двух спикеров или каналов
descriptiveStatisticsQuantiles number[] [] Квантили от 0 до 1, например [0.5, 0.9, 0.95]; применяются ко всем распределениям

Расчёт не использует LLM: он воспроизводимо строится по временным границам диаризованных реплик, VAD-сегментов или слов. Для многоканального звонка speakerTag совпадает с channelTag. Перебиванием считается начало реплики, пока другой спикер уже говорит; одновременный старт реплик перебиванием не считается.

Аналитика требует хотя бы одного фрагмента с ненулевой длительностью. Если выбранная модель не вернула word offsets и запрос не создал разметку диаризации/VAD, speakerAnalysis будет пустым, а conversationAnalysis — отсутствовать. Паузы спикера считаются внутри окна от его первой до последней реплики; общая тишина — внутри границ всего разговора.

Классификаторы (recognitionClassifier)

recognitionClassifier передаётся на верхнем уровне запроса, рядом с recognitionModel, speakerLabeling и speechAnalysis.

Параметр Тип Описание
classifiers[].classifier string Поддерживается negative — классификация негативной эмоциональной окраски DUSHA
classifiers[].triggers string[] Для офлайн-метода recognizeFile: ON_FINAL, ON_UTTERANCE. Триггер ON_PARTIAL не поддерживается

Устаревшее расположение recognitionModel.recognitionClassifier продолжает приниматься. Если в запросе присутствуют оба поля, верхнеуровневое recognitionClassifier имеет приоритет. В частности, пустой верхнеуровневый список classifiers отключает классификаторы из legacy-поля.

Если запрошен классификатор negative, результаты появляются и в совместимом агрегате response.classifiers, и как отдельные classifierUpdate в response.recognitionEvents. Возможные метки: angry, neutral, other, positive, sad.

Классификация выполняется один раз по всему файлу. Поэтому ON_FINAL и ON_UTTERANCE возвращают один и тот же результат на интервале от начала до конца файла; различается только windowType события. highlights всегда пуст. Неизвестные имена классификаторов игнорируются, а недоступность анализа эмоций не прерывает распознавание — массив классификаторов остаётся пустым. ON_PARTIAL для файлового метода завершается ошибкой и его не следует отправлять.

Суммаризация (summarization)

Схема запроса соответствует SpeechKit STT v3:

Параметр Тип По умолчанию Описание
modelUri string "" Допускается пустая строка или совместимый публичный идентификатор google/gemini-3.5-flash-lite-minimal; фактическую модель выбирает сервер
properties object[] — От 1 до 16 независимых инструкций; для каждой выполняется отдельный LLM-запрос
outputLanguage string locale распознавания или язык речи BCP-47 tag языка сгенерированных значений, например en-US; не меняет транскрипт
properties[].instruction string — Непустая инструкция длиной до 8000 символов; пробелы по краям удаляются
properties[].jsonObject boolean — При true запрос результата в виде JSON-объекта; false эквивалентен обычному текстовому ответу
properties[].jsonSchema object — Объект вида {"schema": {...}} с валидной JSON Schema результата размером до 65 536 байт UTF-8

В одном элементе properties можно указать не более одного формата ответа: jsonObject или jsonSchema. Допустимо не указывать ни один из них. Даже jsonObject: false считается явно указанным вариантом и не может сочетаться с jsonSchema. В jsonSchema разрешены только локальные $ref/$dynamicRef, начинающиеся с #; внешние ссылки отклоняются до создания операции.

{
  "summarization": {
    "modelUri": "google/gemini-3.5-flash-lite-minimal",
    "outputLanguage": "en-US",
    "properties": [
      {
        "instruction": "Верни причину обращения и итог разговора",
        "jsonSchema": {
          "schema": {
            "type": "object",
            "properties": {
              "reason": {"type": "string"},
              "outcome": {"type": "string"}
            },
            "required": ["reason", "outcome"]
          }
        }
      }
    ]
  }
}

Суммаризация выполняется после распознавания и всей финальной текстовой обработки. В LLM передаётся нормализованная транскрипция; при диаризации или нескольких каналах в ней сохраняются метки и порядок реплик. Клиент не может переключить модель через modelUri: другое непустое публичное значение отклоняется как невалидный запрос.

Язык результата

summarization.outputLanguage независимо задаёт язык всех сгенерированных значений. Если поле не передано, сохраняется прежнее поведение: выбранный recognitionModel.languageRestriction.languageCode используется как locale результата, а без него выбирается преобладающий язык исходной речи. Язык инструкции, имена полей и описания JSON Schema не переключают ответ на другой язык. В многоязычной записи имена, термины и цитаты сохраняются на языке оригинала; технические JSON-ключи остаются такими, как их задал клиент.

Для каждого элемента properties выполняется один независимый LLM-запрос. Порядок элементов в summarization.results совпадает с порядком инструкций, а каждый response всегда является строкой. Для jsonObject и jsonSchema эта строка содержит сериализованный JSON. contentUsage суммирует расход всех инструкций, только если провайдер вернул корректный usage для каждой из них; его поля inputTextTokens, completionTokens и totalTokens сериализуются строками, как protobuf int64. Для пустой транскрипции провайдер не вызывается: на каждую инструкцию возвращается пустая строка и нулевой contentUsage.

Пример структурированного результата (JSON Schema проверяется сервером, но совместимое поле response остаётся строкой):

{
  "summarization": {
    "results": [
      {
        "response": "{\"reason\":\"Delivery problem\",\"outcome\":\"The order was canceled\"}"
      }
    ],
    "contentUsage": {
      "inputTextTokens": "365",
      "completionTokens": "23",
      "totalTokens": "388"
    }
  }
}

Результат хранится в агрегированном response.summarization и одновременно добавляется последним session-level событием summarization в response.recognitionEvents/getRecognition. У него нет channelTag, в том числе для многоканального аудио: вся сессия суммаризируется один раз после объединения каналов.

Ошибка LLM, невалидный структурированный ответ или превышение лимита входа считаются ошибкой всей операции. Частичный успешный результат распознавания в этом случае не выдаётся как завершённая суммаризация. Отсутствующее поле или "summarization": null отключает функцию.

Модель распознавания (recognitionModel)

Параметр Тип По умолчанию Описание
model string SpeechExpert-STT-RU Файловая модель: SpeechExpert-STT-RU, GigaAM-Multilingual-Large-CTC, SpeechExpert-STT-UZ, Parakeet-EN, SpeechExpert-STT-EN, Whisper-Large-v3-Turbo, ElevenLabs-Scribe-v2, ElevenLabs-Scribe-v2-Medical или gemini-3.5-transcribe

ElevenLabs-Scribe-v2-Medical — вариант Scribe v2 для медицинской и клинической речи. Обе модели используют общий курируемый набор 75 языков, включая русский, английский, казахский, кыргызский, узбекский и таджикский; Medical передаёт провайдеру model_id=scribe_v2_medical и использует тот же тариф. По справочнику ElevenLabs Medical предназначена для файлового распознавания, а для живой речи предусмотрена отдельная ElevenLabs-Scribe-v2-Realtime. В нашем сервисе она доступна в gRPC STT v3 и WebSocket без дополнительного файлового уточнения; recognizeFile её не принимает.

Потоковые модели SpeechExpert-STT-RU-stream, SpeechExpert-STT-KK-stream, SpeechExpert-STT-UZ-stream и SpeechExpert-STT-TG-stream доступны через gRPC и WebSocket, включая потоковую загрузку длинного файла. Для асинхронного файлового HTTP v3 выбирайте модель из таблицы выше. gRPC RecognizeStreaming использует отдельные session_options и поле recognition_model.model; для Realtime передайте "ElevenLabs-Scribe-v2-Realtime" или "scribe_v2_realtime".

Для узбекского файла используйте SpeechExpert-STT-UZ. Отдельный потоковый профиль SpeechExpert-STT-UZ-stream возвращает предварительный текст по мере речи и уточняет окончательный текст каждой завершённой фразы.

Для кыргызского ky-KG значение GigaAM-Multilingual-Large-CTC также доступно в gRPC, живом WebSocket и потоковой загрузке длинного файла. В потоке итог каждой фразы уточняется автоматически. Обычный файловый HTTP v3 по-прежнему распознаёт готовую запись с указанным значением модели; промежуточные результаты живой речи через getRecognition не передаются.

Язык (recognitionModel.languageRestriction)

Параметр По умолчанию Поведение
restrictionType LANGUAGE_RESTRICTION_TYPE_UNSPECIFIED Для GigaAM Multilingual WHITELIST требует ровно один код, а BLACKLIST отклоняется с HTTP 400; для остальных моделей сохраняется совместимое поведение
languageCode [] Для GigaAM Multilingual укажите один из ru-RU, kk-KZ, ky-KG; SpeechExpert-STT-UZ принимает uz/uz-UZ; Parakeet-EN и SpeechExpert-STT-EN — английский (en/en-US); публичный профиль Gemini принимает ru-RU, en-US, kk-KZ, ky-KG, uz-UZ, tg-TJ; обе модели Scribe v2 принимают 75 языков курируемого набора. Без списка применяется серверный язык. У остальных моделей из непустого списка используется первый код

Это языковая подсказка, а не результат автоматического определения языка. Для GigaAM Multilingual alternatives[].languages остаётся пустым: выбранный код не выдаётся за результат language detection.

Для kk-KZ и ky-KG рекомендуется проверить качество GigaAM на своей акустике, акцентах и словаре. Таджикский tg-TJ этой моделью не поддерживается.

gemini-3.5-transcribe — внешняя мультиязычная модель Google. Она поддерживает переключение языков внутри записи; выбранный BCP-47 код передаётся как языковая подсказка, а не как выбор отдельной модели. STT v3 всегда запрашивает нативные таймстемпы слов, поэтому предел этого маршрута — 30 минут на запрос. Лимит 60 минут относится только к text-only OpenAI-совместимому маршруту без timestamps и диаризации. Word timestamps могут снижать точность распознавания. Потоковые WebSocket/gRPC-методы модель не поддерживает.

Формат аудио (recognitionModel.audioFormat)

audioFormat совместим с oneof SpeechKit v3: в нём должен быть указан ровно один объект — rawAudio или containerAudio. Если весь audioFormat опущен, сохраняется прежнее поведение: WAV определяется по заголовку, остальные байты считаются raw PCM. Поэтому для MP3 и OGG/Opus containerAudio нужно указывать явно.

Параметры rawAudio:

Параметр По умолчанию Описание
audioEncoding LINEAR16_PCM Кодирование аудио
sampleRateHertz 16000 Частота дискретизации
audioChannelCount 1 Количество каналов raw PCM. Каждый указанный канал распознаётся отдельно

Поддерживается от 1 до 32 каналов. Raw PCM должен содержать целые interleaved-кадры: число 16-битных сэмплов в payload кратно числу каналов, а каждый сэмпл имеет формат signed little-endian PCM16.

Для контейнерного аудио передаётся только тип; частота и количество каналов извлекаются из самого файла:

{
  "recognitionModel": {
    "audioFormat": {
      "containerAudio": {
        "containerAudioType": "OGG_OPUS"
      }
    }
  }
}

Поддерживаются WAV (целочисленный PCM и IEEE Float, wFormatTag=3), OGG_OPUS (именно Opus в OGG, не Vorbis) и MP3. Контейнер проверяется по фактическим байтам; исходное количество каналов сохраняется. Многоканальное аудио распознаётся поканально и возвращается в channelResults. Если явно включён speakerLabeling, каналы вместо этого сводятся в mono и проходят один общий режим диаризации.

Перед поканальным распознаванием двухканальный PCM анализируется по активным временным окнам. Если каналы имеют устойчивую высокую положительную корреляцию и стабильное соотношение уровней, запись считается dual-mono и сводится в один mono WAV. В этом случае ответ использует обычный final без channelResults. Раздельное stereo, противофаза, один тихий канал и частичное совпадение не схлопываются.

Для ElevenLabs-Scribe-v2 и ElevenLabs-Scribe-v2-Medical исходное аудио передаётся внешнему сервису одним запросом. Многоканальный результат преобразуется в channelResults; channel_index=0 становится channelTag="1". Поддерживается до пяти каналов.

Для обнаруженного dual-mono в ElevenLabs передаётся сведённая mono-запись, чтобы результат не содержал два одинаковых канала. Несовпадение containerAudioType с файлом или повреждённый контейнер завершает операцию с ошибкой 400. Пустой containerAudio, значение CONTAINER_AUDIO_TYPE_UNSPECIFIED, одновременные rawAudio и containerAudio отклоняются при валидации запроса (422).

Для gemini-3.5-transcribe аудио передаётся внешнему Google API. Нативные word timestamps преобразуются в final.alternatives[].words, а нативные метки спикеров — в speakerSegments и связанные события распознавания. Для многоканального входа сохраняется совместимый поканальный контракт STT v3.

Поддержка контейнеров на этом этапе относится к асинхронному HTTP API v3. Потоковый gRPC API по-прежнему принимает raw PCM chunks.

Нормализация текста (recognitionModel.textNormalization)

Параметр По умолчанию Описание
textNormalization TEXT_NORMALIZATION_DISABLED TEXT_NORMALIZATION_ENABLED или TEXT_NORMALIZATION_DISABLED
profanityFilter false Фильтр нецензурной лексики
literatureText false Литературный стиль текста
phoneFormattingMode PHONE_FORMATTING_MODE_DISABLED PHONE_FORMATTING_MODE_UNSPECIFIED включает форматирование телефонных номеров, PHONE_FORMATTING_MODE_DISABLED выключает его

Ответ

Для многоканального аудио стандартное поле final сохраняется. В final.alternatives[0].text тексты каналов соединяются в порядке каналов, а массив words объединяется и сортируется по startTimeMs. Дополнительно Speech Expert возвращает channelResults: один AlternativeUpdate на канал с номером канала в channelTag ("1", "2" и далее). Для одноканального аудио ответ остаётся стандартным, без channelResults.

Канонические события (recognitionEvents)

Завершённая операция дополнительно содержит response.recognitionEvents — упорядоченный список событий в форме StreamingResponse. Каждый элемент содержит общую оболочку:

  • sessionUuid — идентификатор сессии;
  • audioCursors — позиции обработанного аудио;
  • responseWallTimeMs — прошедшее время обработки операции на момент события, в миллисекундах;
  • channelTag — канал события, если он определён;
  • ровно одну полезную нагрузку события. recognizeFile создаёт final, finalRefinement, eouUpdate, classifierUpdate, speakerAnalysis, conversationAnalysis и summarization.

Для mono создаётся последовательность final → необязательный finalRefinement → eouUpdate. Для multichannel такая последовательность создаётся для каждого элемента channelResults; номер канала помещается в recognitionEvents[].channelTag (и сохраняется в самом AlternativeUpdate). Объединённый верхнеуровневый final остаётся агрегатом для обратной совместимости, но отдельное синтетическое событие для него не создаётся. После событий распознавания в список добавляются события классификаторов, аналитика каждого спикера и общая аналитика разговора. В multichannel-ответе classifierUpdate и speakerAnalysis также получают channelTag исходного канала; у общей conversationAnalysis канал не задаётся.

{
  "recognitionEvents": [
    {
      "sessionUuid": {
        "uuid": "550e8400-e29b-41d4-a716-446655440000",
        "userRequestId": "operation-id"
      },
      "audioCursors": {
        "receivedDataMs": "10100",
        "resetTimeMs": "0",
        "partialTimeMs": "10100",
        "finalTimeMs": "10100",
        "finalIndex": "0",
        "eouTimeMs": "0"
      },
      "responseWallTimeMs": "12",
      "channelTag": "1",
      "final": {
        "channelTag": "1",
        "alternatives": [{"text": "Здравствуйте", "startTimeMs": "120", "endTimeMs": "980", "confidence": "0", "words": [], "languages": [{"languageCode": "ru-RU", "probability": "1"}]}]
      }
    }
  ]
}

Поля final, channelResults, vadSegments, speakerSegments, classifiers, speechAnalysis и summarization продолжают возвращаться как агрегаты. Клиенты могут мигрировать на recognitionEvents постепенно.

Для SpeechExpert-STT-RU, SpeechExpert-STT-EN и SpeechExpert-STT-UZ final.alternatives[].words содержит нативные startTimeMs/endTimeMs от начала исходного файла. Эти значения сохраняются и при includeVadSegments=true, и после текстовой нормализации: words описывают результат модели до финальной обработки, а text — итоговый обработанный текст. При разбиении длинного файла каждый чанк переводится в общую абсолютную шкалу. Текст vadSegments[] проходит ту же пунктуацию и нормализацию, что и итоговый final.alternatives[].text; временные границы VAD при этом не меняются.

Для GigaAM-Multilingual-Large-CTC нативные CTC-границы слов переводятся на абсолютную шкалу исходного файла. Границы альтернативы охватывают первое и последнее распознанные слова, а реальные паузы сохраняют разделение реплик.

При includeVadSegments=true поле vadSegments содержит распознанные реплики на той же абсолютной шкале. audioCursors.receivedDataMs, partialTimeMs, finalTimeMs и eouTimeMs отражают курсор всего исходного PCM, включая паузы, и не заменяют word timestamps. При включённой разметке спикеров speakerSegments описывает интервалы диаризации. Для записи без распознанной речи текст и words пусты, а audioCursors всё равно достигает длительности входного аудио.

Сервис возвращает объект операции:

{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "createdAt": "2025-01-15T10:30:00.000000",
  "done": false,
  "response": null,
  "error": null
}

Дальнейший статус проверяйте методом GET /api/operations/{id} — подробности в разделе Операции.

Получение событий (getRecognition)

После того как GET /api/operations/{id} вернул done: true, канонический поток событий можно получить отдельным SpeechKit-совместимым методом:

GET /api/stt/v3/getRecognition?operationId=<UUID>

Метод принимает идентификатор как operationId или operation_id. Если переданы оба параметра, их значения должны совпадать. Ответ — конечная последовательность JSON-объектов, разделённых переводами строки; это не JSON-массив. Каждая непустая строка имеет форму {"result": <StreamingResponse>}; HTTP Content-Type при этом остаётся application/json.

curl --no-buffer \
  -H "Authorization: Api-Key $API_KEY" \
  "https://api.speech.example.com/api/stt/v3/getRecognition?operationId=$OPERATION_ID"

Пример JSONL-ответа для включённой нормализации (каждый объект расположен на отдельной строке):

{"result":{"sessionUuid":{"uuid":"550e8400-e29b-41d4-a716-446655440000","userRequestId":"operation-id"},"audioCursors":{"receivedDataMs":"980","resetTimeMs":"0","partialTimeMs":"980","finalTimeMs":"980","finalIndex":"0","eouTimeMs":"0"},"responseWallTimeMs":"12","final":{"alternatives":[{"words":[],"text":"здравствуйте иван","startTimeMs":"120","endTimeMs":"980","confidence":"0","languages":[{"languageCode":"ru-RU","probability":"1"}]}]}}}
{"result":{"sessionUuid":{"uuid":"550e8400-e29b-41d4-a716-446655440000","userRequestId":"operation-id"},"audioCursors":{"receivedDataMs":"980","resetTimeMs":"0","partialTimeMs":"980","finalTimeMs":"980","finalIndex":"0","eouTimeMs":"0"},"responseWallTimeMs":"12","finalRefinement":{"finalIndex":"0","normalizedText":{"alternatives":[{"words":[],"text":"Здравствуйте, Иван.","startTimeMs":"120","endTimeMs":"980","confidence":"0","languages":[{"languageCode":"ru-RU","probability":"1"}]}]}}}}
{"result":{"sessionUuid":{"uuid":"550e8400-e29b-41d4-a716-446655440000","userRequestId":"operation-id"},"audioCursors":{"receivedDataMs":"980","resetTimeMs":"0","partialTimeMs":"980","finalTimeMs":"980","finalIndex":"0","eouTimeMs":"980"},"responseWallTimeMs":"12","eouUpdate":{"timeMs":"980"}}}

Порядок событий фиксирован:

final (до локальной постобработки) → finalRefinement (опционально) → eouUpdate
→ classifierUpdate* → speakerAnalysis* → conversationAnalysis?
→ summarization?

Для multichannel первая цепочка повторяется для каждого канала, и только затем добавляются классификаторы и аналитика. finalRefinement создаётся, если запрошен хотя бы один режим финальной обработки: textNormalization, profanityFilter, literatureText или активный phoneFormattingMode. Событие создаётся и тогда, когда обработчик не изменил строку. Его normalizedText содержит обработанный текст, а words, языки и временные границы сохраняются от final до локальной постобработки.

Если запрошена суммаризация, её единственное session-level событие приходит последним, после аналитики. В то же время GET /api/operations/{id} возвращает итоговый обработанный агрегат в response.final, а текст до локальной постобработки доступен в событии recognitionEvents[].final/потоке getRecognition.

В снимках audioCursors у final и finalRefinement поле eouTimeMs равно "0"; конечное значение появляется начиная с события eouUpdate.

finalRefinement.finalIndex указывает на исходный final. Для многоканального аудио события следует связывать по паре (channelTag, finalIndex): индексы могут повторяться в разных каналах. Поток завершается обычным EOF; отдельное синтетическое событие statusCode: CLOSED не добавляется, поэтому клиенту нужно читать ответ до конца.

Для ранее сохранённой STT v3 операции без response.recognitionEvents метод строит доступные события из агрегированных полей. Текст до локальной постобработки в таком старом результате восстановить нельзя, поэтому синтетический finalRefinement не создаётся.

Ошибки метода:

Код Когда возникает
404 Операция не найдена, принадлежит другому владельцу или создана не методом STT v3 recognizeFile
409 Операция ещё не завершена (done: false)
422 Идентификатор не передан, превышает допустимую длину или два query-параметра не совпадают
код сохранённой ошибки / 500 Распознавание завершилось ошибкой; HTTP-код из диапазона 400–599 сохраняется, остальные ошибки возвращаются как 500

При включенной разметке обычный результат в final сохраняется, а реплики дополнительно возвращаются в speakerSegments:

{
  "final": {
    "alternatives": [
      {
        "text": "Здравствуйте Добрый день",
        "startTimeMs": "120",
        "endTimeMs": "2450"
      }
    ]
  },
  "speakerSegments": [
    {
      "speakerId": 1,
      "startTimeMs": "120",
      "endTimeMs": "980",
      "text": "Здравствуйте"
    },
    {
      "speakerId": 2,
      "startTimeMs": "1150",
      "endTimeMs": "2450",
      "text": "Добрый день"
    }
  ]
}

speakerSegments — расширение Speech Expert. Если разметка выключена или не указана, поле отсутствует, а поведение v3 остается прежним.

При включённом speechAnalysis в результате операции появляется одноимённый агрегированный блок. Целочисленные длительности и счётчики сериализуются строками, как int64 в protobuf JSON:

{
  "speechAnalysis": {
    "speakerAnalysis": [
      {
        "speakerTag": "1",
        "windowType": "TOTAL",
        "speechBoundaries": {"startTimeMs": "120", "endTimeMs": "8900"},
        "totalSpeechMs": "5410",
        "speechRatio": 0.6162,
        "totalSilenceMs": "3370",
        "silenceRatio": 0.3838,
        "wordsCount": "74",
        "lettersCount": "392",
        "utteranceCount": "9",
        "wordsPerSecond": {"min": 1.2, "max": 4.1, "mean": 2.7, "std": 0.8, "quantiles": []}
      }
    ],
    "conversationAnalysis": {
      "conversationBoundaries": {"startTimeMs": "120", "endTimeMs": "10100"},
      "totalSpeechDurationMs": "8140",
      "totalSpeechRatio": 0.8156,
      "totalSimultaneousSilenceDurationMs": "1840",
      "totalSimultaneousSilenceRatio": 0.1844,
      "totalSimultaneousSpeechDurationMs": "620",
      "totalSimultaneousSpeechRatio": 0.0621,
      "speakerInterrupts": [
        {
          "speakerTag": "2",
          "interruptsCount": "2",
          "interruptsDurationMs": "620",
          "interrupts": [
            {"startTimeMs": "1820", "endTimeMs": "2150"},
            {"startTimeMs": "7040", "endTimeMs": "7330"}
          ]
        }
      ]
    }
  }
}

Полные объекты также содержат распределения lettersPerSecond, wordsPerUtterance, lettersPerUtterance, utteranceDurationEstimation, simultaneousSilenceDurationEstimation и simultaneousSpeechDurationEstimation.

Примеры использования

AUDIO_BASE64=$(base64 -i audio.wav)

curl -X POST "https://api.speech.example.com/api/stt/v3/recognizeFile" \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d "{
    \"content\": \"$AUDIO_BASE64\",
    \"recognitionModel\": {
      \"model\": \"SpeechExpert-STT-RU\"
    }
  }"
curl -X POST "https://api.speech.example.com/api/stt/v3/recognizeFile" \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "uri": "https://storage.example.com/audio.wav",
    "recognitionModel": {
      "model": "SpeechExpert-STT-RU"
    }
  }'
import requests
import base64
import time

BASE_URL = "https://api.speech.example.com"
HEADERS = {"Authorization": "Api-Key <ваш-ключ>"}

# 1. Отправляем файл
with open("audio.wav", "rb") as f:
    audio_b64 = base64.b64encode(f.read()).decode()

resp = requests.post(f"{BASE_URL}/api/stt/v3/recognizeFile", headers=HEADERS, json={
    "content": audio_b64,
    "recognitionModel": {
        "model": "SpeechExpert-STT-RU",
    },
})
operation = resp.json()
print(f"Операция создана: {operation['id']}")

# 2. Ждём результат
while not operation["done"]:
    time.sleep(2)
    resp = requests.get(f"{BASE_URL}/api/operations/{operation['id']}", headers=HEADERS)
    operation = resp.json()

# 3. Читаем результат
if operation.get("error"):
    print(f"Ошибка: {operation['error']['message']}")
else:
    alternative = operation["response"]["final"]["alternatives"][0]
    print(f"Текст: {alternative['text']}")

Полный пример запроса

{
  "content": "UklGRi4AAABXQVZFZm10IBAAAA...",
  "recognitionModel": {
    "model": "SpeechExpert-STT-RU",
    "audioFormat": {
      "containerAudio": {
        "containerAudioType": "WAV"
      }
    },
    "textNormalization": {
      "textNormalization": "TEXT_NORMALIZATION_ENABLED",
      "profanityFilter": false
    }
  },
  "recognitionClassifier": {
    "classifiers": [
      {
        "classifier": "negative",
        "triggers": ["ON_FINAL"]
      }
    ]
  },
  "speakerLabeling": {
    "speakerLabeling": "SPEAKER_LABELING_ENABLED",
    "maxSpeakers": 4
  },
  "speechAnalysis": {
    "enableSpeakerAnalysis": true,
    "enableConversationAnalysis": true,
    "descriptiveStatisticsQuantiles": [0.5, 0.9]
  },
  "summarization": {
    "modelUri": "google/gemini-3.5-flash-lite-minimal",
    "outputLanguage": "en-US",
    "properties": [
      {
        "instruction": "Верни причину обращения и итог разговора",
        "jsonSchema": {
          "schema": {
            "type": "object",
            "properties": {
              "reason": {"type": "string"},
              "outcome": {"type": "string"}
            },
            "required": ["reason", "outcome"]
          }
        }
      }
    ]
  }
}

Ограничения

Параметр Значение
Максимальный размер файла, скачиваемого по uri 500 МБ
Максимальная длительность Gemini в text-only OpenAI-маршруте без word timestamps и диаризации 60 минут
Максимальная длительность Gemini в STT v3 (word timestamps запрашиваются всегда) 30 минут
Количество каналов raw PCM от 1 до 32
Количество summarization.properties от 1 до 16
Длина summarization.properties[].instruction от 1 до 8000 символов после удаления пробелов по краям
Размер summarization.properties[].jsonSchema.schema до 65 536 байт UTF-8
Длина summarization.outputLanguage до 35 символов; требуется валидный BCP-47 tag
Длина нормализованной транскрипции для суммаризации до 500 000 символов

Ошибки

Код Когда возникает
422 Некорректный JSON или невалидные параметры схемы
401 Не аутентифицирован (отсутствует или неверный API-ключ)
503 Сервис суммаризации недоступен; операция не создаётся
500 Ошибка создания задачи

Проверка uri, загрузка, декодирование контейнера, проверка межканального выравнивания raw PCM и само распознавание происходят в фоне. Поэтому их ошибки возвращаются в Operation.error уже созданной задачи, обычно с HTTP-кодом 400, 413, 422 или 500, а не обязательно в ответе recognizeFile.

Ошибки уже созданной операции суммаризации также сохраняются в Operation.error: 413 при превышении лимита транскрипции, 502 при ошибке провайдера или невалидном структурированном ответе и 504 при таймауте провайдера.

См. также