Аудио в чате (запись на вход, голос на выход)
Модель слушает аудиозапись и рассуждает о ней — либо отвечает голосом. Content-part input_audio, modalities audio, стриминг delta.audio.
Обычный /v1/chat/completions умеет работать со звуком в обе стороны:
- Аудио на вход — вы прикладываете запись к сообщению, и модель отвечает на вопросы о ней: кто говорит, каким тоном, что за шум на фоне, о чём договорились в звонке.
- Голос на выход — модель озвучивает свой ответ, аудио приходит в потоке вместе с текстовой расшифровкой.
Не путайте с транскрибацией: /v1/audio/transcriptions решает одну задачу — превратить запись в текст, дёшево и предсказуемо. Аудио в чате — это рассуждение о записи (можно совмещать с tool calling и структурированным выводом) и разговорный сценарий. Если нужен просто текст расшифровки, берите транскрибацию — она для этого и сделана.
Эндпоинт
https://api.hubris.pw/v1/chat/completionsАудио на вход
Запись передаётся как content-part типа input_audio внутри user-сообщения — рядом с обычным текстом:
{
"role": "user",
"content": [
{ "type": "text", "text": "О чём эта запись и каким тоном говорят?" },
{
"type": "input_audio",
"input_audio": { "data": "UklGRiQAAABXQVZFZm10...", "format": "wav" }
}
]
}| Поле | Тип | Описание |
|---|---|---|
type | "input_audio" | Дискриминатор content-part. |
input_audio.data | string | Аудио в base64 без префикса data:audio/...;base64, — только сами данные. |
input_audio.format | string | wav, mp3, aiff, aac, ogg, flac, m4a, pcm16, pcm24. |
Ссылки на аудио не поддерживаются — только base64. Base64 раздувает запрос примерно на треть, поэтому длинные записи лучше резать: тело запроса ограничено 36 МБ, минута WAV — это около 2,7 МБ.
Минимальный пример
# base64 записи подставьте вместо UklGRiQAAAB... — например: base64 -w0 voice.wavcurl -s https://api.hubris.pw/v1/chat/completions \-H "Authorization: Bearer sk-gw-..." \-H "Content-Type: application/json" \-d '{ "model": "google/gemini-3.5-flash", "messages": [{ "role": "user", "content": [ {"type": "text", "text": "Опиши, что слышно на записи."}, {"type": "input_audio", "input_audio": {"data": "UklGRiQAAAB...", "format": "wav"}} ] }]}'import base64from openai import OpenAIclient = OpenAI( base_url="https://api.hubris.pw/v1", api_key="sk-gw-...",)with open("voice.wav", "rb") as f: audio = base64.b64encode(f.read()).decode()response = client.chat.completions.create( model="google/gemini-3.5-flash", messages=[ { "role": "user", "content": [ {"type": "text", "text": "Опиши, что слышно на записи."}, { "type": "input_audio", "input_audio": {"data": audio, "format": "wav"}, }, ], } ],)print(response.choices[0].message.content)import fs from "node:fs";import OpenAI from "openai";const client = new OpenAI({baseURL: "https://api.hubris.pw/v1",apiKey: "sk-gw-...",});const audio = fs.readFileSync("voice.wav").toString("base64");const response = await client.chat.completions.create({model: "google/gemini-3.5-flash",messages: [ { role: "user", content: [ { type: "text", text: "Опиши, что слышно на записи." }, { type: "input_audio", input_audio: { data: audio, format: "wav" } }, ], },],});console.log(response.choices[0].message.content);Официальные openai SDK для Python и TypeScript работают без правок — достаточно поменять base_url / baseURL.
Сколько это стоит в токенах
Аудио превращается в отдельный подвид prompt-токенов. В ответе они видны в usage.prompt_tokens_details.audio_tokens и входят в общий usage.prompt_tokens:
{
"usage": {
"prompt_tokens": 70,
"completion_tokens": 38,
"total_tokens": 108,
"prompt_tokens_details": { "audio_tokens": 50 },
"cost": 9
}
}Какие модели принимают аудио
Те, у которых в карточке (GET /v1/models) поле input_modalities содержит "audio" — в каталоге это фильтр «принимает аудио». Обратите внимание: под фильтр попадают и модели распознавания речи (Whisper, Voxtral, Nova и другие) — они предназначены для /v1/audio/transcriptions, а не для чата. Для аудио в диалоге берите чат-модели: семейство Gemini, openai/gpt-audio и подобные.
Если модель аудио не принимает, запрос отклоняется до обращения к провайдеру — с кодом 400 и code: "audio_input_not_supported". За такой вызов ничего не списывается.
Голос на выход
Чтобы модель ответила голосом, нужны три вещи одновременно:
modalities: ["text", "audio"];- объект
audioс голосом и форматом; stream: true— иначе400сcode: "audio_output_requires_stream".
| Поле | Тип | Описание |
|---|---|---|
modalities | array<string> | ["text","audio"]. |
audio.voice | string | Голос озвучки, например alloy. Список — ниже. |
audio.format | string | Формат ответа: wav, mp3, pcm16 и т. п. |
stream | boolean | Обязательно true. |
curl -N -s https://api.hubris.pw/v1/chat/completions \
-H "Authorization: Bearer sk-gw-..." \
-H "Content-Type: application/json" \
-d '{
"model": "openai/gpt-audio",
"modalities": ["text", "audio"],
"audio": {"voice": "alloy", "format": "pcm16"},
"stream": true,
"messages": [{"role": "user", "content": "Скажи «Привет!»"}]
}'Звук приходит частями внутри обычных SSE-чанков, в поле delta.audio:
{
"id": "chatcmpl-abc123",
"object": "chat.completion.chunk",
"choices": [
{
"index": 0,
"delta": {
"audio": {
"id": "audio_abc123",
"data": "GAAaABwAHgAgACIAJAAmACgA...",
"transcript": "Привет"
}
}
}
]
}| Поле | Описание |
|---|---|
delta.audio.id | Идентификатор аудио-фрагмента ответа. |
delta.audio.data | Кусок аудио в base64, в формате из audio.format. Склеивайте куски по порядку и декодируйте один раз в конце. |
delta.audio.transcript | Текстовая расшифровка того, что модель произносит в этом куске. |
Расшифровка удобна, чтобы показывать субтитры в момент воспроизведения и сохранять реплику в историю диалога. Общий формат SSE, [DONE] и сбор usage — на странице Стриминг.
Токены озвучки приходят в completion_tokens_details.audio_tokens (подмножество completion_tokens).
Какие бывают голоса
Для моделей семейства openai/gpt-audio* доступны тринадцать голосов:
alloy, echo, fable, onyx, nova, shimmer, coral, verse, ballad, ash, sage, marin, cedar.
Отдельного справочника голосов у провайдеров нет, и в карточке модели они не публикуются. Если голос указан неверно, ответ приходит с кодом 400, а в тексте ошибки провайдер перечисляет все допустимые значения для этой модели — это и есть способ узнать список для любой новой модели:
{
"error": {
"message": "Invalid value: 'нет-такого'. Supported values are: 'alloy', 'echo', 'fable', ...",
"type": "invalid_request_error",
"code": "invalid_value"
}
}Модели с голосовым ответом
Их сильно меньше, чем принимающих аудио: нужно "audio" в output_modalities карточки модели — например openai/gpt-audio и openai/gpt-audio-mini. Актуальный список — в каталоге. Запрос modalities: ["text","audio"] к модели без голосового выхода отклоняется с 400 и code: "audio_output_not_supported".
Не путайте с генерацией музыки: у моделей вроде google/lyria-3-pro-preview на выходе тоже аудио, но это сочинённый трек по текстовому описанию, а не проговоренный ответ. Они не ведут диалог голосом и оплачиваются за клип или песню — смотрите цену на карточке модели.
Ограничение: аудио не переиспользуется по id
Ссылаться в следующем запросе на прошлый ответ по audio.id нельзя — идентификатор живёт в пределах одного ответа. Историю мультитёрн-диалога ведите текстом: кладите в messages assistant-сообщение с content или с audio.transcript предыдущей реплики. Сами байты озвучки в историю отправлять не нужно — это только раздует запрос.
HTTP-коды
| Код | code | Когда |
|---|---|---|
200 | — | Успешный ответ. |
400 | audio_input_not_supported | В сообщениях есть input_audio, а модель не принимает аудио. |
400 | audio_output_not_supported | В modalities есть audio, а модель не умеет отвечать голосом. |
400 | audio_output_requires_stream | Голосовой ответ запрошен без stream: true. |
400 | — | Тело не соответствует схеме (например, data с префиксом data: или неизвестный format). |
401 | — | Ключ отсутствует / отозван / невалиден. |
402 | — | Недостаточно средств на балансе. |
413 | request_too_large | Тело запроса больше 36 МБ. |
429 | — | Превышен дневной лимит на ключе. |
502 / 503 / 504 | — | Транзиентные сбои у провайдера или курса ЦБ. |
Формат тела ошибки и стратегия retry — Ошибки.
Биллинг
Аудио-токены тарифицируются по отдельной ставке модели — обычно она выше текстовой, и на вход и на выход ставки разные. Итоговая стоимость запроса приходит в usage.cost в копейках: это ровно та сумма, что списывается с баланса и попадает в журнал запросов, с уже учтённой разницей ставок.
Списание — по факту завершённого запроса, итог приходит в usage.cost в копейках (как на всех остальных эндпоинтах) и совпадает с суммой в логах. При обрыве соединения во время голосового стрима ответ дочитывается до конца и оплачивается полностью.
Что дальше
- POST /v1/audio/transcriptions — просто получить текст записи, без рассуждений модели.
- Стриминг — разбор SSE-чанков и сбор
usage. - POST /v1/chat/completions — полная схема параметров.
- Изображения на вход — та же логика мультимодального ввода, только для картинок.
- Цены — как считается
usage.cost.
Обновлено: