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

Асинхронное распознавание — 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 Запросить VAD-разметку. Для GigaAM Multilingual возвращаются абсолютные интервалы MarbleNet с шагом 20 мс и длиной не более 24 секунд; в FastConformer/Riva-маршруте используется его VAD, при диаризации разметка зеркалится из speakerSegments. T-One, Whisper и ElevenLabs без speakerLabeling могут вернуть vadSegments: 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

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

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

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

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

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

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

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

Параметр Тип Описание
classifiers[].classifier string Поддерживается negative — классификация негативной эмоциональной окраски через DUSHA emotion backend
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.

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

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

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

Параметр Тип По умолчанию Описание
modelUri string "" Допускается пустая строка или совместимый публичный идентификатор google/gemini-3.1-flash-lite; фактическую модель выбирает сервер
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.1-flash-lite",
    "outputLanguage": "en-US",
    "properties": [
      {
        "instruction": "Верни причину обращения и итог разговора",
        "jsonSchema": {
          "schema": {
            "type": "object",
            "properties": {
              "reason": {"type": "string"},
              "outcome": {"type": "string"}
            },
            "required": ["reason", "outcome"]
          }
        }
      }
    ]
  }
}

Суммаризация выполняется после распознавания и всей финальной текстовой обработки. В LLM передаётся нормализованная транскрипция; при диаризации или нескольких каналах в ней сохраняются метки и порядок реплик. Фактическая модель задаётся серверной переменной LLM_SUMMARIZATION_MODEL (по умолчанию google/gemini-3.1-flash-lite). Клиент не может переключить её через 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"
    }
  }
}

Старты запросов к провайдеру ограничиваются общим для экземпляра API-процесса pacing-интервалом LLM_SUMMARIZATION_MIN_REQUEST_INTERVAL (по умолчанию 1,1 секунды), чтобы несколько properties не нарушали лимит VseGPT в один запрос в секунду. Транспортные ошибки, таймауты и HTTP 408, 429, 5xx повторяются в пределах LLM_SUMMARIZATION_MAX_ATTEMPTS (по умолчанию две попытки). Ответ 429 учитывает Retry-After в виде секунд или HTTP-date и сдвигает общий cooldown; без заголовка применяется backoff от 2,1 секунды. При нескольких API workers очередь не разделяется между процессами, поэтому внешний общий лимит нужно учитывать в настройках запуска или провайдера.

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

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

Сервер обращается к OpenAI-совместимому endpoint LLM_SUMMARIZATION_BASE_URL (по умолчанию https://api.vsegpt.ru/v1). Модель должна быть доступна у настроенного провайдера в момент выполнения; временная ошибка или недоступность модели после повторных попыток приводит к ошибке операции по описанной выше hard-failure семантике. Ключ задаётся через LLM_SUMMARIZATION_API_KEY; если он пуст, используются по очереди LLM_CORRECTION_API_KEY и VSEGPT_API_KEY.

Основные серверные настройки:

Переменная По умолчанию Назначение
LLM_SUMMARIZATION_API_KEY пусто Отдельный ключ суммаризации; при отсутствии используются LLM_CORRECTION_API_KEY, затем VSEGPT_API_KEY
LLM_SUMMARIZATION_BASE_URL https://api.vsegpt.ru/v1 Базовый URL OpenAI-совместимого API
LLM_SUMMARIZATION_MODEL google/gemini-3.1-flash-lite Модель, фактически отправляемая провайдеру; клиентский modelUri её не переопределяет
LLM_SUMMARIZATION_TIMEOUT 120 Таймаут одного запроса, секунды
LLM_SUMMARIZATION_MAX_TOKENS 8192 Максимум выходных токенов для одной инструкции
LLM_SUMMARIZATION_TEMPERATURE 0 Температура генерации
LLM_SUMMARIZATION_MAX_CONCURRENCY 2 Максимум одновременных запросов на один API worker
LLM_SUMMARIZATION_MIN_REQUEST_INTERVAL 1.1 Минимальный интервал между стартами запросов на один API worker, секунды
LLM_SUMMARIZATION_MAX_ATTEMPTS 2 Число попыток для одной инструкции
LLM_SUMMARIZATION_MAX_INPUT_CHARS 500000 Максимальная длина подготовленной транскрипции

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

Параметр Тип По умолчанию Описание
model string SpeechExpert-STT Публичное имя модели: SpeechExpert-STT, T-one, GigaAM-v3-CTC, GigaAM-Multilingual-Large-CTC, Whisper-Large-v3-Turbo или ElevenLabs-Scribe-v2

Для совместимости также принимаются устаревшие алиасы SpeechExpert-STT-Enhanced и GigaAM-v3-CTCGigaAM-Multilingual-Large-CTC, а также SpeechExpert-STT-TurboWhisper-Large-v3-Turbo. Новым клиентам следует использовать канонические имена.

Язык (recognitionModel.languageRestriction)

Параметр По умолчанию Текущее поведение
restrictionType LANGUAGE_RESTRICTION_TYPE_UNSPECIFIED Для GigaAM Multilingual WHITELIST требует ровно один код, а BLACKLIST отклоняется с HTTP 400; для остальных моделей сохраняется совместимое поведение
languageCode [] Для GigaAM Multilingual разрешён ровно один из ru-RU, kk-KZ, ky-KG, uz-UZ; без списка применяется серверный язык. У остальных моделей из непустого списка используется первый код

Это выбор language hint, а не полноценное определение языка: large_ctc.transcribe() не принимает параметр языка и использует общий multilingual CTC vocabulary. Код управляет допустимостью пары модель–язык и последующей обработкой. Для GigaAM Multilingual alternatives[].languages остаётся пустым: сервер не выдаёт выбранный hint за результат language detection.

Для kk-KZ, ky-KG и uz-UZ поддерживаемый маршрут не является гарантией качества на конкретном домене: до production-включения оцените GigaAM и MarbleNet на своей акустике, акцентах и словаре. kk, ky и uz не входят в опубликованный список языков обучения этого checkpoint VAD. Таджикский tg-TJ не включён, поскольку для него нет готового large_ctc декодера и опубликованной приемлемой оценки качества.

Формат аудио (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, OGG_OPUS (именно Opus в OGG, не Vorbis) и MP3. Контейнер проверяется по фактическим байтам и декодируется во внутренний PCM16 WAV с частотой backend (RIVA_SAMPLE_RATE_HZ, по умолчанию 16 кГц); исходное количество каналов сохраняется. Многоканальное аудио затем распознаётся поканально и возвращается в channelResults. Несовпадение containerAudioType с файлом или повреждённый контейнер завершает операцию с ошибкой 400. Пустой containerAudio, значение CONTAINER_AUDIO_TYPE_UNSPECIFIED, одновременные rawAudio и containerAudio отклоняются при валидации запроса (422).

Поддержка контейнеров на этом этапе относится к асинхронному 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 → необязательный finalRefinementeouUpdate. Для 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 постепенно.

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

Для GigaAM-Multilingual large_ctc API сначала получает интервалы MarbleNet от модели marblenet_vad в существующем tone-triton. Triton wrapper принимает сырое mono-аудио 16 кГц, воспроизводит NeMo mel-препроцессинг и через BLS вызывает ONNX-core. Вероятности с шагом 20 мс сшиваются на общей шкале даже для длинной записи, после чего API режет исходный PCM на реплики не длиннее 24 секунд. GigaAM вызывается с word_timestamps=true и возвращает нативные CTC-границы слов внутри каждой реплики. API прибавляет начало VAD-интервала, поэтому final.alternatives[].words[].startTimeMs/endTimeMs лежат на абсолютной шкале исходного файла, а границы самой альтернативы охватывают первое и последнее распознанные слова.

Если 24-секундный лимит разрезал непрерывную речь на смежные части, API объединяет их только для распознавания: GigaAM worker обрабатывает внутреннюю границу с overlap, затем каждое нативное слово по midpoint ровно один раз возвращается в исходный VAD-сегмент. Части с реальным промежутком не склеиваются.

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

Успешный пустой ответ MarbleNet считается строгой тишиной: GigaAM не вызывается, текст и words пусты, а audioCursors всё равно достигает длительности входного аудио. Если VAD недоступен, по умолчанию GigaAM распознаёт весь файл и сохраняет нативные CTC-таймкоды; MARBLENET_VAD_REQUIRED=true вместо этого завершает операцию ошибкой.

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

{
  "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-код из диапазона 400599 сохраняется, остальные ошибки возвращаются как 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\"
    }
  }"
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"
    }
  }'
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",
    },
})
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",
    "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.1-flash-lite",
    "outputLanguage": "en-US",
    "properties": [
      {
        "instruction": "Верни причину обращения и итог разговора",
        "jsonSchema": {
          "schema": {
            "type": "object",
            "properties": {
              "reason": {"type": "string"},
              "outcome": {"type": "string"}
            },
            "required": ["reason", "outcome"]
          }
        }
      }
    ]
  }
}

Ограничения

Параметр Значение
Максимальный размер файла, скачиваемого по uri 500 МБ
Количество каналов 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 Запрошена суммаризация, но не настроен LLM API key; операция не создаётся
500 Ошибка создания задачи

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

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

См. также