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

OpenAI-совместимый API (аудио)

Методы распознавания и синтеза речи в формате OpenAI Audio API. Вы можете использовать официальный клиент openai, указав в нём base_url вашего экземпляра сервиса.

Настройка клиента

Задайте в клиенте два параметра:

  • Base URL:
  • API-ключ: действующий ключ платформы (как и для остальных методов, см. Аутентификация).

Клиент openai передаёт ключ по схеме Authorization: Bearer <ключ> — эта схема поддерживается, поэтому достаточно указать ключ в api_key:

from openai import OpenAI

client = OpenAI(
    base_url="<BASE_URL>",
    api_key="<ваш-ключ>",
)

Распознавание речи (STT)

POST /api/v1/audio/transcriptions

Метод соответствует client.audio.transcriptions.create(). Запрос отправляется в формате multipart/form-data.

Модели

Модель Описание Стриминг
SpeechExpert-STT Встроенная модель SpeechExpert для телефонного качества (офлайн или онлайн при stream=true) да (stream=true)
Whisper-Large-v3-Turbo Whisper Large v3 Turbo (офлайн) нет
GigaAM-Multilingual-Large-CTC GigaAM large CTC для ru, kk, ky, uz (офлайн) нет
ElevenLabs-Scribe-v2 ElevenLabs-Scribe-v2 для ru, en, kk, ky, uz, tg через /v1/speech-to-text; используется ключ ELEVENLABS_API_KEY нет

Стриминг

Потоковый режим (stream=true) поддерживается только для модели SpeechExpert-STT. Для остальных моделей запрос со стримингом завершается ошибкой 400.

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

Параметр Тип Обязательный По умолчанию Описание
file file да Аудио- или видеофайл с аудиодорожкой (WAV, MP3, M4A, MP4, MOV, WebM, MKV и др.)
model string нет SpeechExpert-STT Модель: SpeechExpert-STT, Whisper-Large-v3-Turbo, GigaAM-Multilingual-Large-CTC или ElevenLabs-Scribe-v2
language string нет ru Короткий код языка: ru, en, kk, ky, uz или tg; совместимость зависит от выбранной модели
response_format string нет json Формат ответа: json или text (при stream=false)
stream boolean нет false Потоковая выдача результата для SpeechExpert-STT
prompt string нет Принимается для совместимости с OpenAI, но сейчас не влияет на распознавание
temperature number нет Принимается для совместимости с OpenAI, но сейчас игнорируется

Для GigaAM-Multilingual-Large-CTC поле language обязательно должно разрешаться в один из кодов публичного профиля ru, kk, ky, uz; остальные значения отклоняются с HTTP 400 до чтения файла. Для других моделей действуют их собственные языковые ограничения.

Для kk, ky и uz поддерживаемый маршрут не гарантирует качество на любом домене: перед production-включением нужна локальная оценка GigaAM и MarbleNet на своей акустике, акцентах и словаре; эти три языка не входят в опубликованный список языков обучения checkpoint VAD. Таджикский tg не включён именно в GigaAM — у опубликованного large_ctc нет готового декодера и приемлемой оценки качества для этого языка. Он, как и ru, en, kk, ky, uz, доступен через ElevenLabs-Scribe-v2.

OpenAI-совместимый метод использует короткие коды, в отличие от STT v1/v3:

OpenAI Audio STT v1/v3 Язык
ru ru-RU русский
en en-US английский
kk kk-KZ казахский
ky ky-KG кыргызский
uz uz-UZ узбекский
tg tg-TJ таджикский

Код языка не выбирает модель автоматически. Для казахского, кыргызского и узбекского файла с локальной моделью передавайте одновременно language и model=GigaAM-Multilingual-Large-CTC. Для таджикского выбирайте model=ElevenLabs-Scribe-v2; эта же модель совместима со всеми кодами таблицы.

В offline-режиме этот маршрут использует тот же пайплайн, что STT v1/v3: модель marblenet_vad в существующем tone-triton принимает сырое mono-аудио, формирует речевые реплики с шагом 20 мс и длиной не более 24 секунд, а нативные CTC-таймкоды GigaAM переводятся обратно на шкалу исходного аудио. Смежные части, созданные только 24-секундным лимитом, распознаются совместно с overlap и затем без дублей раскладываются обратно; реальные паузы не склеиваются. Текущий OpenAI-совместимый контракт с response_format=json|text возвращает только текст и не публикует эти таймкоды. Успешный пустой результат VAD трактуется как тишина без вызова GigaAM; при недоступном VAD используется весь файл, если не задано MARBLENET_VAD_REQUIRED=true.

Поле multipart file обычно содержит бинарный файл. Для совместимости можно передать в нём и чистую Base64-строку поддерживаемого аудио/видеоконтейнера: сервис распознает сигнатуру после декодирования. Data URL с префиксом data:...;base64, не поддерживается.

Ответ

Без стриминга (stream=false или параметр не указан).

При response_format=json:

{
  "text": "распознанный текст"
}

При response_format=text тело ответа — обычный текст (text/plain).

Потоковый ответ (SSE)

При stream=true и модели SpeechExpert-STT потоково выдаётся только ответ. Сервер сначала целиком принимает multipart file, при необходимости декодирует Base64 и конвертирует запись в mono WAV, а уже затем подаёт PCM чанками в псевдострим. Для живого входящего аудио используйте WebSocket или gRPC.

Ответ — поток Server-Sent Events (SSE). События:

  • transcript.text.delta — очередной фрагмент текста;
  • transcript.text.done — завершение с полным текстом после восстановления пунктуации.

Кадры содержат только строку data:. Значения transcript.text.delta и transcript.text.done находятся в JSON-поле type, а не в отдельном поле SSE event:. После финального объекта поток завершается обычным EOF; маркер data: [DONE] не отправляется.

Помимо OpenAI-совместимых полей type, delta и text, Speech Expert добавляет тайминги, когда потоковый backend их вернул:

Поле Тип Описание
start_ms integer Начало текущей гипотезы или финального диапазона на шкале исходного аудио
end_ms integer Конец диапазона
timing_source string Источник таймингов, например t-one
words object[] Слова с полями text, start_ms, end_ms

Пример промежуточного события:

data: {"type":"transcript.text.delta","delta":"добрый день","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}]}

Финальное событие сохраняет последний диапазон и полный список доступных слов:

data: {"type":"transcript.text.done","text":"Добрый день.","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}]}

Поля таймингов являются расширением Speech Expert и могут отсутствовать у backend, который не возвращает временные границы. Клиентам следует воспринимать их как необязательные. Для SpeechExpert-STT промежуточные гипотезы формирует T-One, а итоговый текст — offline-модель SpeechExpert-STT. Если offline-final отличается от T-One preview, поле words у события done может отсутствовать. Клиенту, которому важны промежуточные тайминги, следует сохранять words из delta, а не рассчитывать только на финальный объект.

Если распознавание аварийно завершилось уже после отправки SSE-заголовков, сервер закрывает поток финальным transcript.text.done с накопленным текстом; отдельного error-события сейчас нет, а HTTP status остаётся 200. Критичным клиентам следует считать такой поток best-effort и отдельно контролировать полноту входной длительности.

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

from openai import OpenAI

client = OpenAI(base_url="https://api.speech.example.com/api/v1", api_key="<ваш-ключ>")

with open("audio.wav", "rb") as f:
    transcript = client.audio.transcriptions.create(
        model="SpeechExpert-STT",
        file=f,
        language="ru",
        response_format="json",
    )

print(transcript.text)
with open("audio.wav", "rb") as f:
    stream = client.audio.transcriptions.create(
        model="SpeechExpert-STT",
        file=f,
        language="ru",
        stream=True,
    )
    for event in stream:
        if hasattr(event, "delta") and event.delta:
            print(event.delta, end="", flush=True)
        if hasattr(event, "text") and event.type == "transcript.text.done":
            print("\n[Done]", event.text)
curl -X POST "https://api.speech.example.com/api/v1/audio/transcriptions" \
  -H "Authorization: Api-Key $API_KEY" \
  -F "file=@audio.wav" \
  -F "model=SpeechExpert-STT" \
  -F "language=ru" \
  -F "response_format=json"
curl -X POST "https://api.speech.example.com/api/v1/audio/transcriptions" \
  -H "Authorization: Api-Key $API_KEY" \
  -F "file=@audio.wav" \
  -F "model=SpeechExpert-STT" \
  -F "language=ru" \
  -F "stream=true"

Синтез речи (TTS)

POST /api/v1/audio/speech

Метод соответствует client.audio.speech.create(). Тело запроса — application/json.

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

Параметр Тип Обязательный По умолчанию Описание
model string нет tts-1 Модель (игнорируется, оставлена для совместимости)
input string да Текст для синтеза (до 4096 символов)
voice string нет первый из конфигурации Имя встроенного голоса из конфигурации сервиса
response_format string нет mp3 Формат аудио: mp3, opus, aac, flac, wav, pcm
speed number нет 1.0 Скорость от 0.25 до 4.0

Только встроенные голоса

OpenAI-совместимый метод использует только голоса из конфигурации сервиса. GET /api/tts/v1/voices также возвращает пользовательские user-voice-*, но этот endpoint их не поддерживает. Для собственного голоса вызывайте POST /api/tts/v1. Если voice не найден среди встроенных, сервер без ошибки выбирает первый настроенный голос.

Ответ

В ответе сервис возвращает бинарный аудиофайл. Тип содержимого зависит от response_format:

response_format Content-Type
mp3 audio/mpeg
opus audio/ogg
wav audio/wav
flac audio/flac
aac audio/aac
pcm audio/basic

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

from openai import OpenAI

client = OpenAI(base_url="https://api.speech.example.com/api/v1", api_key="<ваш-ключ>")

response = client.audio.speech.create(
    model="tts-1",
    input="Привет, это синтез речи через OpenAI-совместимый API.",
    voice="elena-speech-expert-tts",  # встроенный голос конфигурации сервера
    response_format="mp3",
    speed=1.0,
)

with open("speech.mp3", "wb") as f:
    f.write(response.read())
curl -X POST "https://api.speech.example.com/api/v1/audio/speech" \
  -H "Authorization: Api-Key $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "tts-1",
    "input": "Привет, мир!",
    "voice": "elena-speech-expert-tts",
    "response_format": "mp3"
  }' \
  --output speech.mp3

Проверка работоспособности

В репозитории есть скрипт для проверки через библиотеку openai:

export OPENAI_BASE_URL=http://localhost:8000/api/v1
export OPENAI_API_KEY=<ваш-ключ>
uv run python scripts/test_openai_audio.py

Скрипт сначала синтезирует фразу в MP3, затем распознаёт этот файл и выводит текст. Клиент openai передаёт OPENAI_API_KEY по схеме Bearer, которая поддерживается сервером.

Ошибки

Код Когда возникает
400 Пустой файл (STT), неизвестная модель, неподдерживаемый стриминг, ошибка декодирования/конвертации или отсутствие настроенных голосов (TTS)
401 Не аутентифицирован (отсутствует или неверный API-ключ)
402 Недостаточно кредитов
413 Multipart-файл превышает серверный лимит загрузки
422 FastAPI не смог проверить форму или тело запроса, например неизвестен response_format
500 Внутренняя ошибка синтеза или распознавания
503 Сервис распознавания недоступен

Неизвестный голос (TTS)

Если переданный voice не найден в конфигурации, ошибка не возвращается — берётся первый доступный голос. Код 400 возникает только если на сервере вообще нет настроенных голосов.

Формат ошибки — JSON с полем detail:

{
  "detail": "Описание ошибки"
}

См. также