Текст, изображения, видео и музыка через один API — выберите модель
  1. Главная
  2. /
  3. Документация
  4. /
  5. Гайды

Streaming текста, изображений и аудио

Гайды, примеры запросов и справочник методов. Всё, что нужно для подключения нейросетей к вашему проекту.

Гайды

POST /chat/completions с stream: true возвращает Content-Type: text/event-stream. Части имеют object=chat.completion.chunk, собственные id/model и choices.delta. Несовместимый маршрут отклоняется до генерации; поддержка определяется опубликованной операцией модели.

Событие — строка data: JSON и пустая строка. Текст приходит в delta.content; tool_calls.arguments может состоять из нескольких частей. Поток завершается data: [DONE]. Собирайте части по choices.index и tool_calls.index, не исполняйте незавершённые аргументы.

POST /images с stream: true передаёт события image_generation.partial_image и image_generation.completed с data[], затем image_generation.settled с итоговым статусом и расчётом. Промежуточные кадры зависят от модели; полный JSON поставщика даёт одно событие completed. Файлы выдаются через защищённые адреса Formula AI либо запрошенный response_format=b64_json.

Для аудио передайте modalities=["text","audio"], audio.voice и audio.format="pcm16", stream=true. delta.audio.data содержит независимый base64-фрагмент: сначала декодируйте каждый фрагмент, затем объединяйте байты. delta.audio.transcript содержит части расшифровки. PCM16 — знаковые 16-битные значения little-endian, моно, 24000 Гц; контейнер WAV/MP3 автоматически не добавляется. После completed запросите GET /requests/{id}: choices.message.audio содержит приватный url, format, sample_rate, channels и transcript. Голоса указаны в схеме запроса.

stream_options: {"include_usage": true} включает клиентский usage. Последнее JSON-событие содержит ai_service.status и billing. DONE означает закрытие соединения: проверяйте status и события error. Стоимость поставщика, его модель, ключ и адрес не выдаются.

Ошибка до начала ответа сохраняет обычный HTTP/JSON-формат. После первой части HTTP-статус уже не меняется: ошибка передаётся в data.error, затем итоговый расчёт и DONE. После начала выдачи новая попытка у другого ключа или поставщика не создаётся.

Idempotency-Key сохраняет одну операцию. Повтор завершённого потока воспроизводит сохранённые части без нового расхода. Пока первоначальная операция выполняется, повтор возвращает HTTP 202 с собственным статусом; используйте GET /requests/{id}. Повтор после очистки содержимого даёт 410.

При отключении клиента сервис продолжает получать результат и учитывает фактический расход. Обрыв внешнего потока оставляет частичный ответ и pending-сверку стоимости; отправленная генерация не повторяется при восстановлении воркера. Поддержка потока зависит от опубликованного профиля модели.

ID передаётся сразу: заголовок X-Generation-ID и первый data-объект с собственным id и ai_service.status_url, для текста choices=[]; для изображений type=image_generation.accepted. Это подтверждение принятия, а не текст модели. Комментарии SSE : keep-alive поддерживают соединение во время ожидания и игнорируются сборщиком data-событий. При отключении клиента обработка и сохранение результата продолжаются. GET /requests/{id} возвращает полный собранный result после completed. Повтор с тем же ключом воспроизводит сохранённые части и следует за ещё выполняемой операцией без новой генерации. Prefer: respond-async вместе со stream=true возвращает JSON 202; поток поставщика сохраняется в фоне.

cURL · POST /chat/completions

curl "https://formula-ai.ru/api/v1/chat/completions" \
  -H "Authorization: Bearer $AI_SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: example-stream-1" \
  --data-binary "{
    \"model\": \"$AI_SERVICE_MODEL\",
    \"messages\": [
        {
            \"role\": \"user\",
            \"content\": \"Reply OK\"
        }
    ],
    \"stream\": true,
    \"stream_options\": {
        \"include_usage\": true
    }
}" \
  --no-buffer

cURL · POST /images

curl "https://formula-ai.ru/api/v1/images" \
  -H "Authorization: Bearer $AI_SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: example-image_stream-1" \
  --data-binary "{
    \"model\": \"$AI_SERVICE_MODEL\",
    \"prompt\": \"A red circle on a white background\",
    \"stream\": true
}" \
  --no-buffer

cURL · POST /chat/completions

curl "https://formula-ai.ru/api/v1/chat/completions" \
  -H "Authorization: Bearer $AI_SERVICE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: example-audio_stream-1" \
  --data-binary "{
    \"model\": \"$AI_SERVICE_MODEL\",
    \"messages\": [
        {
            \"role\": \"user\",
            \"content\": \"Reply OK\"
        }
    ],
    \"modalities\": [
        \"text\",
        \"audio\"
    ],
    \"audio\": {
        \"voice\": \"alloy\",
        \"format\": \"pcm16\"
    },
    \"stream\": true,
    \"stream_options\": {
        \"include_usage\": true
    }
}" \
  --no-buffer

PHP · POST /chat/completions · SSE

<?php
$buffer = '';
$done = false;
$completed = false;
$audioBytes = '';
$handle = curl_init('https://formula-ai.ru/api/v1/chat/completions');
curl_setopt_array($handle, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . getenv('AI_SERVICE_API_KEY'), 'Idempotency-Key: php-stream-1'],
    CURLOPT_POSTFIELDS => json_encode(['model' => getenv('AI_SERVICE_MODEL'), 'messages' => [['role' => 'user', 'content' => 'Reply OK']], 'stream' => true, 'stream_options' => ['include_usage' => true]], JSON_THROW_ON_ERROR),
    CURLOPT_WRITEFUNCTION => function ($handle, string $bytes) use (&$buffer, &$done, &$completed, &$audioBytes): int {
        $buffer .= $bytes;
        while (($position = strpos($buffer, "\n\n")) !== false) {
            $event = substr($buffer, 0, $position);
            $buffer = substr($buffer, $position + 2);
            if (!str_starts_with($event, 'data: ')) {
                continue;
            }
            $data = substr($event, 6);
            if ($data === '[DONE]') {
                $done = true;
                continue;
            }
            $chunk = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
            foreach ($chunk['choices'] ?? [] as $choice) {
                echo $choice['delta']['content'] ?? '';
                if (isset($choice['delta']['audio']['data'])) {
                    $part = base64_decode($choice['delta']['audio']['data'], true);
                    if ($part === false) {
                        throw new RuntimeException('Invalid audio base64');
                    }
                    $audioBytes .= $part;
                }
                echo $choice['delta']['audio']['transcript'] ?? '';
            }
            if (isset($chunk['type'], $chunk['data'])) {
                echo json_encode($chunk, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n";
            }
            if (isset($chunk['ai_service'])) {
                $completed = $chunk['ai_service']['status'] === 'completed';
            }
            if (isset($chunk['error']) || isset($chunk['ai_service'])) {
                fwrite(STDERR, json_encode($chunk, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n");
            }
        }
        return strlen($bytes);
    },
]);
if (getenv('AI_SERVICE_CA')) {
    curl_setopt($handle, CURLOPT_CAINFO, getenv('AI_SERVICE_CA'));
}
$success = curl_exec($handle);
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$error = curl_error($handle);
curl_close($handle);
if (!$success || $status !== 200 || !$done || !$completed) {
    throw new RuntimeException('HTTP ' . $status . ': ' . $error . ' ' . $buffer);
}
if ($audioBytes !== '') {
    file_put_contents(getenv('AI_SERVICE_AUDIO_OUTPUT') ?: 'speech.pcm', $audioBytes);
}

PHP · POST /images · SSE

<?php
$buffer = '';
$done = false;
$completed = false;
$audioBytes = '';
$handle = curl_init('https://formula-ai.ru/api/v1/images');
curl_setopt_array($handle, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . getenv('AI_SERVICE_API_KEY'), 'Idempotency-Key: php-image_stream-1'],
    CURLOPT_POSTFIELDS => json_encode(['model' => getenv('AI_SERVICE_MODEL'), 'prompt' => 'A red circle on a white background', 'stream' => true], JSON_THROW_ON_ERROR),
    CURLOPT_WRITEFUNCTION => function ($handle, string $bytes) use (&$buffer, &$done, &$completed, &$audioBytes): int {
        $buffer .= $bytes;
        while (($position = strpos($buffer, "\n\n")) !== false) {
            $event = substr($buffer, 0, $position);
            $buffer = substr($buffer, $position + 2);
            if (!str_starts_with($event, 'data: ')) {
                continue;
            }
            $data = substr($event, 6);
            if ($data === '[DONE]') {
                $done = true;
                continue;
            }
            $chunk = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
            foreach ($chunk['choices'] ?? [] as $choice) {
                echo $choice['delta']['content'] ?? '';
                if (isset($choice['delta']['audio']['data'])) {
                    $part = base64_decode($choice['delta']['audio']['data'], true);
                    if ($part === false) {
                        throw new RuntimeException('Invalid audio base64');
                    }
                    $audioBytes .= $part;
                }
                echo $choice['delta']['audio']['transcript'] ?? '';
            }
            if (isset($chunk['type'], $chunk['data'])) {
                echo json_encode($chunk, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n";
            }
            if (isset($chunk['ai_service'])) {
                $completed = $chunk['ai_service']['status'] === 'completed';
            }
            if (isset($chunk['error']) || isset($chunk['ai_service'])) {
                fwrite(STDERR, json_encode($chunk, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n");
            }
        }
        return strlen($bytes);
    },
]);
if (getenv('AI_SERVICE_CA')) {
    curl_setopt($handle, CURLOPT_CAINFO, getenv('AI_SERVICE_CA'));
}
$success = curl_exec($handle);
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$error = curl_error($handle);
curl_close($handle);
if (!$success || $status !== 200 || !$done || !$completed) {
    throw new RuntimeException('HTTP ' . $status . ': ' . $error . ' ' . $buffer);
}
if ($audioBytes !== '') {
    file_put_contents(getenv('AI_SERVICE_AUDIO_OUTPUT') ?: 'speech.pcm', $audioBytes);
}

PHP · POST /chat/completions · SSE

<?php
$buffer = '';
$done = false;
$completed = false;
$audioBytes = '';
$handle = curl_init('https://formula-ai.ru/api/v1/chat/completions');
curl_setopt_array($handle, [
    CURLOPT_POST => true,
    CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . getenv('AI_SERVICE_API_KEY'), 'Idempotency-Key: php-audio_stream-1'],
    CURLOPT_POSTFIELDS => json_encode(['model' => getenv('AI_SERVICE_MODEL'), 'messages' => [['role' => 'user', 'content' => 'Reply OK']], 'modalities' => ['text', 'audio'], 'audio' => ['voice' => 'alloy', 'format' => 'pcm16'], 'stream' => true, 'stream_options' => ['include_usage' => true]], JSON_THROW_ON_ERROR),
    CURLOPT_WRITEFUNCTION => function ($handle, string $bytes) use (&$buffer, &$done, &$completed, &$audioBytes): int {
        $buffer .= $bytes;
        while (($position = strpos($buffer, "\n\n")) !== false) {
            $event = substr($buffer, 0, $position);
            $buffer = substr($buffer, $position + 2);
            if (!str_starts_with($event, 'data: ')) {
                continue;
            }
            $data = substr($event, 6);
            if ($data === '[DONE]') {
                $done = true;
                continue;
            }
            $chunk = json_decode($data, true, 512, JSON_THROW_ON_ERROR);
            foreach ($chunk['choices'] ?? [] as $choice) {
                echo $choice['delta']['content'] ?? '';
                if (isset($choice['delta']['audio']['data'])) {
                    $part = base64_decode($choice['delta']['audio']['data'], true);
                    if ($part === false) {
                        throw new RuntimeException('Invalid audio base64');
                    }
                    $audioBytes .= $part;
                }
                echo $choice['delta']['audio']['transcript'] ?? '';
            }
            if (isset($chunk['type'], $chunk['data'])) {
                echo json_encode($chunk, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n";
            }
            if (isset($chunk['ai_service'])) {
                $completed = $chunk['ai_service']['status'] === 'completed';
            }
            if (isset($chunk['error']) || isset($chunk['ai_service'])) {
                fwrite(STDERR, json_encode($chunk, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) . "\n");
            }
        }
        return strlen($bytes);
    },
]);
if (getenv('AI_SERVICE_CA')) {
    curl_setopt($handle, CURLOPT_CAINFO, getenv('AI_SERVICE_CA'));
}
$success = curl_exec($handle);
$status = curl_getinfo($handle, CURLINFO_RESPONSE_CODE);
$error = curl_error($handle);
curl_close($handle);
if (!$success || $status !== 200 || !$done || !$completed) {
    throw new RuntimeException('HTTP ' . $status . ': ' . $error . ' ' . $buffer);
}
if ($audioBytes !== '') {
    file_put_contents(getenv('AI_SERVICE_AUDIO_OUTPUT') ?: 'speech.pcm', $audioBytes);
}

Примеры cURL используют Bash. Задайте AI_SERVICE_API_KEY и AI_SERVICE_MODEL; модель выберите из /models. В PHP для опроса/файла задайте AI_SERVICE_REQUEST_ID/AI_SERVICE_MEDIA_ID, для правки — AI_SERVICE_SOURCE_URL. Для локального сертификата используйте доверенный CA через --cacert, для PHP — AI_SERVICE_CA. Проверку TLS сохраняйте включённой.