hubris
Возможности
ВОЗМОЖНОСТИ

Аудио в чате (запись на вход, голос на выход)

Модель слушает аудиозапись и рассуждает о ней — либо отвечает голосом. Content-part input_audio, modalities audio, стриминг delta.audio.

Обычный /v1/chat/completions умеет работать со звуком в обе стороны:

  • Аудио на вход — вы прикладываете запись к сообщению, и модель отвечает на вопросы о ней: кто говорит, каким тоном, что за шум на фоне, о чём договорились в звонке.
  • Голос на выход — модель озвучивает свой ответ, аудио приходит в потоке вместе с текстовой расшифровкой.

Не путайте с транскрибацией: /v1/audio/transcriptions решает одну задачу — превратить запись в текст, дёшево и предсказуемо. Аудио в чате — это рассуждение о записи (можно совмещать с tool calling и структурированным выводом) и разговорный сценарий. Если нужен просто текст расшифровки, берите транскрибацию — она для этого и сделана.

Эндпоинт

POSThttps://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.datastringАудио в base64 без префикса data:audio/...;base64, — только сами данные.
input_audio.formatstringwav, 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". За такой вызов ничего не списывается.

Голос на выход

Чтобы модель ответила голосом, нужны три вещи одновременно:

  1. modalities: ["text", "audio"];
  2. объект audio с голосом и форматом;
  3. stream: true — иначе 400 с code: "audio_output_requires_stream".
ПолеТипОписание
modalitiesarray<string>["text","audio"].
audio.voicestringГолос озвучки, например alloy. Список — ниже.
audio.formatstringФормат ответа: wav, mp3, pcm16 и т. п.
streambooleanОбязательно 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Успешный ответ.
400audio_input_not_supportedВ сообщениях есть input_audio, а модель не принимает аудио.
400audio_output_not_supportedВ modalities есть audio, а модель не умеет отвечать голосом.
400audio_output_requires_streamГолосовой ответ запрошен без stream: true.
400Тело не соответствует схеме (например, data с префиксом data: или неизвестный format).
401Ключ отсутствует / отозван / невалиден.
402Недостаточно средств на балансе.
413request_too_largeТело запроса больше 36 МБ.
429Превышен дневной лимит на ключе.
502 / 503 / 504Транзиентные сбои у провайдера или курса ЦБ.

Формат тела ошибки и стратегия retry — Ошибки.

Биллинг

Аудио-токены тарифицируются по отдельной ставке модели — обычно она выше текстовой, и на вход и на выход ставки разные. Итоговая стоимость запроса приходит в usage.cost в копейках: это ровно та сумма, что списывается с баланса и попадает в журнал запросов, с уже учтённой разницей ставок.

Списание — по факту завершённого запроса, итог приходит в usage.cost в копейках (как на всех остальных эндпоинтах) и совпадает с суммой в логах. При обрыве соединения во время голосового стрима ответ дочитывается до конца и оплачивается полностью.

Что дальше

Обновлено: