OpenAI-совместимый API (аудио)¶
Методы распознавания и синтеза речи в формате OpenAI Audio API. Вы можете использовать официальный клиент openai, указав в нём base_url вашего экземпляра сервиса.
Настройка клиента¶
Задайте в клиенте два параметра:
- Base URL: —
- API-ключ: действующий ключ платформы (как и для остальных методов, см. Аутентификация).
Клиент openai передаёт ключ по схеме Authorization: Bearer <ключ> — эта схема поддерживается, поэтому достаточно указать ключ в api_key:
Распознавание речи (STT)¶
Метод соответствует 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:
При 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 и отдельно контролировать
полноту входной длительности.
Примеры использования¶
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)
Синтез речи (TTS)¶
Метод соответствует 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())
Проверка работоспособности¶
В репозитории есть скрипт для проверки через библиотеку 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: