spicyapiДокументация
Основное содержимое

Текст и потоковые ответы

Вызывайте текстовые модели в официальном формате OpenAI, Anthropic или Google Gemini и правильно обрабатывайте диалоги, инструменты, потоки и оплату.

Выберите доступную текстовую модель

Получите список моделей из /api/v1/models?modality=text&includeSchema=1 с аутентификацией. Отправляйте запрос, только если и enabled, и available равны true. Инструменты, мультимодальные сообщения, поля рассуждений и структурированный вывод должны при этом поддерживаться inputSchema выбранной модели. Совместимость протокола не означает, что любая модель умеет всё.

Один и тот же ID модели работает с любым из протоколов ниже, независимо от того, кто разработал модель: можно вызвать модель другой компании в формате Anthropic или модель не от Google в формате Gemini.

Поддерживаемые протоколы

Все четыре текстовых протокола используют один API-ключ и один каталог моделей, а также общие лимиты частоты запросов, проверку параметров и тарификацию. Различаются только форматы запросов и ответов. Выберите тот протокол, с которым уже работает ваш код или SDK.

ПротоколEndpointЗаголовок для ключа
OpenAI Chat CompletionsPOST /v1/chat/completionsAuthorization: Bearer
OpenAI ResponsesPOST /v1/responsesAuthorization: Bearer
Anthropic MessagesPOST /v1/messagesx-api-key или Authorization: Bearer
Google GeminiPOST /v1beta/models/{model}:generateContentx-goog-api-key или Authorization: Bearer
Google Gemini, потоковый режимPOST /v1beta/models/{model}:streamGenerateContentx-goog-api-key или Authorization: Bearer
Нативный потокPOST /api/v1/jobs/streamAuthorization: Bearer

Ответы и ошибки /v1 и /v1beta имеют формат своего протокола и приходят без нативной обёртки {code,msg,data,request_id}, поэтому не разбирайте их клиентом, который читает только body.data. В формате Gemini ID модели передаётся в пути URL; слеши в ID оставляйте как есть, кодировать их не нужно.

Передавайте ключ только в заголовке запроса. x-api-key и x-goog-api-key действуют только на /v1 и /v1beta, а нативный /api/v1 принимает только Authorization: Bearer. Параметр ?key= в строке запроса URL не принимается никогда: попав в URL, ключ остаётся в истории браузера, на прокси-серверах и в журналах доступа. Официальные SDK передают ключ в заголовке, поэтому их это не касается.

Подключение через официальные SDK

Замените только базовый URL и ключ, в остальном работайте с каждым SDK как обычно.

SDKБазовый URL
OpenAI (openai)https://api.spicyapi.ai/v1
Anthropic (anthropic)https://api.spicyapi.ai
Google GenAI (google-genai)https://api.spicyapi.ai

SDK Anthropic и Google сами добавляют /v1 или /v1beta, поэтому их базовый URL указывается без версии в пути, а базовый URL для OpenAI SDK должен включать /v1. Все три примера на Python ниже берут из переменных окружения SPICY_API_KEY и SPICY_MODEL — ID модели, выбранной в актуальном каталоге.

OpenAI
import os
from openai import OpenAI

client = OpenAI(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai/v1")
reply = client.chat.completions.create(
    model=os.environ["SPICY_MODEL"],
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(reply.choices[0].message.content)
Anthropic
import os
import anthropic

client = anthropic.Anthropic(api_key=os.environ["SPICY_API_KEY"], base_url="https://api.spicyapi.ai")
message = client.messages.create(
    model=os.environ["SPICY_MODEL"],
    max_tokens=256,
    messages=[{"role": "user", "content": "Explain a rainbow in one sentence."}],
)
print(message.content[0].text)
Google GenAI
import os
from google import genai
from google.genai import types

client = genai.Client(
    api_key=os.environ["SPICY_API_KEY"],
    http_options=types.HttpOptions(base_url="https://api.spicyapi.ai"),
)
response = client.models.generate_content(
    model=os.environ["SPICY_MODEL"],
    contents="Explain a rainbow in one sentence.",
)
print(response.text)

Чтобы повторы при сетевых сбоях не приводили к двойному списанию, создавайте для каждого действия пользователя один Idempotency-Key, сохраняйте его в постоянном хранилище и передавайте через параметр SDK для дополнительных заголовков (в SDK OpenAI и Anthropic это extra_headers).

Соответствие параметров в разных протоколах

Поля протокола сначала приводятся к единым именам параметров платформы, а затем проверяются по inputSchema выбранной модели. Если модель не поддерживает параметр, возвращается 400: такой параметр не отбрасывается молча с обычным списанием. Таблица показывает только соответствие полей; «—» означает, что в протоколе нет подходящего поля. Принимает ли конкретная модель тот или иной параметр и какие значения допустимы, определяет её inputSchema.

Единый параметрChat CompletionsResponsesMessagesGemini
messagesmessagesinstructions, inputsystem, messagessystemInstruction, contents
max_tokensmax_tokens, max_completion_tokensmax_output_tokensmax_tokensgenerationConfig.maxOutputTokens
temperaturetemperaturetemperaturetemperaturegenerationConfig.temperature
top_ptop_ptop_ptop_pgenerationConfig.topP
toolstoolstools (тип function)tools (пользовательские инструменты)tools[].functionDeclarations
tool_choicetool_choicetool_choicetool_choicetoolConfig.functionCallingConfig
response_formatresponse_formattext.formatgenerationConfig.responseMimeType плюс responseSchema или responseJsonSchema
reasoning_effortreasoning_effortreasoning.effortgenerationConfig.thinkingConfig

Поля, которых нет в таблице, тоже приводятся к единым параметрам и проверяются по схеме модели; если поля нет в inputSchema, возвращается 400. Например, если модель не принимает stop, то 400 вернут и stop в Chat, и stop_sequences в Messages, и generationConfig.stopSequences в Gemini. Сейчас ни одна текстовая модель не объявляет seed в inputSchema, поэтому seed в Chat и generationConfig.seed в Gemini тоже вернут 400.

Несколько исключений:

  • Поля, которые имеют смысл только на исходной платформе протокола, игнорируются и на генерацию не влияют: user, metadata, store в Chat; user, metadata, store, include в Responses; metadata в Messages.
  • Responses не поддерживает previous_response_id: передавайте всю историю в input.
  • В Gemini safetySettings принимается, но ни на что не влияет. cachedContent, а также инструменты, которые выполняются на стороне сервера, например googleSearch и codeExecution, не поддерживаются и возвращают 400. Изображения можно передать через inlineData (base64) или fileData.fileUri (https-URL); принимает ли модель изображения, определяет её inputSchema.

Отправьте Chat-поток

На сервере задайте SPICY_API_KEY, SPICY_MODEL из актуального каталога и сохранённый SPICY_IDEMPOTENCY_KEY для операции. Пример требует jq. Промпт можно заменить; поля запроса должны соответствовать текущей схеме.

jq -n --arg model "$SPICY_MODEL" '{
  model: $model,
  messages: [{role: "user", content: "Explain a rainbow in one sentence."}],
  stream: true
}' > chat-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  https://api.spicyapi.ai/v1/chat/completions \
  -H "Authorization: Bearer $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @chat-request.json

Отправьте запрос Gemini

Переменные окружения те же, что и в предыдущем разделе. ID модели указывается в URL, поэтому в теле запроса поле model не передаётся. Заключите URL в двойные кавычки и запишите переменную как ${SPICY_MODEL}, иначе shell воспримет следующее за ней двоеточие как модификатор переменной.

jq -n '{
  systemInstruction: {parts: [{text: "Answer in one sentence."}]},
  contents: [{role: "user", parts: [{text: "Explain a rainbow."}]}],
  generationConfig: {temperature: 0.7, maxOutputTokens: 256}
}' > gemini-request.json

curl --fail-with-body --no-buffer --max-time 120 \
  "https://api.spicyapi.ai/v1beta/models/${SPICY_MODEL}:streamGenerateContent?alt=sse" \
  -H "x-goog-api-key: $SPICY_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $SPICY_IDEMPOTENCY_KEY" \
  --data-binary @gemini-request.json

Если поток не нужен, замените :streamGenerateContent?alt=sse на :generateContent: ответ читайте из candidates[0].content.parts[].text, данные о расходе — из usageMetadata. Без alt=sse потоковый запрос возвращает постепенно дописываемый JSON-массив; официальные SDK добавляют alt=sse сами.

Диалоги и вызовы инструментов

В ответе Chat без потока читайте choices[0].message, в потоке — choices[].delta по завершённым SSE-событиям, а не разбирайте JSON по TCP-фрагментам. Аргументы инструментов могут приходить несколькими частями: соберите полные tool_calls по index и только потом проверяйте и выполняйте их. Сохраняйте сообщение assistant и возвращайте результат с соответствующим tool_call_id. Responses требует всю историю в input и не поддерживает previous_response_id; результаты вызова функций передаются как function_call_output с соответствующим call_id. Messages использует блоки text, tool_use и tool_result в content, и формат сообщений Chat к нему напрямую не применим.

В Gemini contents состоит из элементов, в которых чередуются роли user и model. Когда модели нужно вызвать инструмент, она возвращает часть functionCall. Выполнив инструмент, оформите результат как часть functionResponse и верните его в следующем элементе с ролью user, указав тот же name, что и в вызове.

Завершение, ошибки и разрыв соединения

ПротоколНормальное завершениеДанные о расходе
Chat Completionsdata: [DONE]usage в последнем чанке
ResponsesСобытие response.completedresponse.usage в этом событии
Messagesmessage_stop после message_deltausage в message_delta
GeminiПоследнее событие с finishReason, без [DONE]usageMetadata

До начала потока проверьте HTTP-статус и Content-Type. Ошибки приходят в JSON-формате своего протокола:

ПротоколТело ошибки
Chat Completions, Responses{"error": {"message", "type", "param", "code"}}
Messages{"type": "error", "error": {"type", "message"}}
Gemini{"error": {"code", "message", "status"}}

HTTP-коды статуса во всех четырёх протоколах одинаковы. Поле status в ошибках Gemini содержит названия статусов Google: 400 — INVALID_ARGUMENT, 401 — UNAUTHENTICATED, 402 — FAILED_PRECONDITION, 403 — PERMISSION_DENIED, 404 — NOT_FOUND, 429 — RESOURCE_EXHAUSTED, 503 — UNAVAILABLE. В коде опирайтесь на код статуса и тип ошибки, а не сравнивайте текст message.

Если ошибка возникает после начала потока, Chat и Gemini отправляют событие с объектом error, Responses — событие error, а Messages — event: error. Клиент должен обрабатывать эти события ошибок, таймауты и преждевременный разрыв соединения. Отключите буферизацию ответов на прокси. Закрытие браузера или AbortSignal лишь прекращает приём данных: это не отмена и не обещание возврата средств. Если у сервера есть достоверные данные о расходе, разрыв соединения со стороны клиента всё равно оплачивается по фактически сгенерированному объёму; если таких данных нет, закрытие соединения нельзя считать бесплатным завершением.

Котировки и расчёт

Чтобы пользователь заранее подтвердил точную цену и предел списания, используйте нативную цепочку jobs/quote → jobs/stream с тем же model/input и полями quoteId, expectedCost. /v1 и /v1beta не принимают эти нативные реквизиты расчёта и применяют цену на момент приёма. Окончательная сумма определяется задачей и выпиской, а не маркером конца текста. reasoning_tokens уже включён в completion_tokens и повторно не прибавляется.

Другие форматы запросов

Примеры показывают структуру протокола. Замените MODEL_ID_FROM_CATALOG актуальной моделью с поддержкой указанных полей; не отправляйте сам заполнитель.

POST /v1/responses
{
  "model": "MODEL_ID_FROM_CATALOG",
  "input": "Explain a rainbow in one sentence.",
  "stream": false
}
POST /v1/messages
{
  "model": "MODEL_ID_FROM_CATALOG",
  "max_tokens": 256,
  "messages": [{"role": "user", "content": "Explain a rainbow in one sentence."}],
  "stream": false
}

Другие материалы

Расчёт цены и совместимость · Оплата · Интеграция для продакшена · Диагностика и восстановление

На этой странице