Обработка ошибок
Запрос, завершившийся сбоем, возвращает HTTP-код статуса и JSON-тело с типом ошибки и сообщением. На этой странице перечислены все статусы, показано тело ошибки в каждом формате API и указано, какие ошибки стоит повторять.
SDK выбрасывают объект ошибки, который несет код статуса и поля тела. Этот запрос указывает несуществующую модель:
import openai
from openai import OpenAI
client = OpenAI(
api_key="YOUR_API_KEY",
base_url="https://api.shannon-ai.com/v1",
)
try:
client.chat.completions.create(
model="no-such-model",
messages=[{"role": "user", "content": "Hello"}],
)
except openai.APIStatusError as error:
print(error.status_code) # 400
print(error.type) # invalid_request_error
print(error.body["message"]) # unknown model: no-such-model
print(error.response.headers.get("x-request-id")) # the id of this request import OpenAI from "openai";
const client = new OpenAI({
apiKey: "YOUR_API_KEY",
baseURL: "https://api.shannon-ai.com/v1",
});
try {
await client.chat.completions.create({
model: "no-such-model",
messages: [{ role: "user", content: "Hello" }],
});
} catch (error) {
if (!(error instanceof OpenAI.APIError)) throw error;
console.log(error.status); // 400
console.log(error.type); // invalid_request_error
console.log(error.error.message); // unknown model: no-such-model
} curl -i https://api.shannon-ai.com/v1/chat/completions \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "no-such-model",
"messages": [{"role": "user", "content": "Hello"}]
}' Он возвращает статус 400 и такое тело:
{
"error": {
"type": "invalid_request_error",
"message": "unknown model: no-such-model"
}
} Тело ошибки
Есть два вида тела. /v1/messages отвечает в формате Anthropic; все остальные эндпоинты отвечают в формате OpenAI. В обоих подробности находятся в error.type и error.message, поэтому один обработчик подходит для всех эндпоинтов.
{
"error": {
"type": "authentication_error",
"message": "Invalid API key",
"code": "invalid_api_key",
"param": null
}
} {
"type": "error",
"error": {
"type": "authentication_error",
"message": "Invalid API key"
}
} | Поле | Тип | Описание |
|---|---|---|
error.type | string | Вид ошибки. См. ниже «Типы ошибок». |
error.message | string | Предложение для человека. Тексты в таблицах ниже стабильны с точностью до переменных частей, обозначенных как <id>, <n> или …. |
error.code | string | null | Присутствует в некоторых ошибках формата OpenAI: invalid_api_key при 401, request_too_large при 413, context_length_exceeded при слишком длинном разговоре, иначе null или отсутствует. Сначала ориентируйтесь на статус и error.type, а code считайте дополнением. |
error.param | null | Присутствует в тех же ошибках, что и code. Всегда null. |
type | string | Только формат Anthropic. Всегда error. |
Типы ошибок
| Тип | Статусы | Описание |
|---|---|---|
invalid_request_error | 400, 413, 415, 422 | Сам запрос неверен: поле, тело, id модели или ввод, который модель не принимает. Измените запрос, прежде чем отправлять его снова. |
authentication_error | 401 | API-ключ отсутствует, неизвестен или отозван. |
not_found_error | 404 | Такого пути не существует. |
rate_limit_error | 429 | Баланса не хватает на запрос, защита от флуда остановила его либо модель занята. Сообщение указывает, что именно. |
api_error | 500, 502, 503, 504 | Запрос был корректным, а сбой произошел на нашей стороне. Отправьте его снова. |
server_error | 500 | Запрос был корректным, а сбой произошел на нашей стороне. Отправьте его снова. |
Коды статуса
| Статус | Тип | Сообщение | Когда |
|---|---|---|---|
400 | invalid_request_error | Failed to parse the request body as JSON: … | Тело не является корректным JSON. |
400 | invalid_request_error | unknown model: <id> | model не является опубликованным id. На /v1/responses также если model отсутствует. |
400 | invalid_request_error | No user message provided | Уровни Shannon: в запросе нет пользовательского текста. |
400 | invalid_request_error | <id> does not accept image input | Изображение отправлено размещенной модели с открытыми весами, не поддерживающей ввод изображений. |
400 | invalid_request_error | audio and video input are not supported by any hosted model | Аудио- или видеочасть отправлена размещенной модели с открытыми весами. |
400 | invalid_request_error | <id> does not accept response_format | Структурированный вывод запрошен у размещенной модели с открытыми весами, которая его не поддерживает. |
400 | invalid_request_error | <id> does not accept response_format json_schema; use json_object | JSON-схема отправлена размещенной модели с открытыми весами, которая принимает только json_object. |
400 | invalid_request_error | unknown reasoning effort '<value>'; expected off, low, medium or high | Усилие рассуждения не входит в список допустимых значений. Размещенные модели с открытыми весами. |
400 | invalid_request_error | count_tokens is available for the hosted open models; unknown model: <id> | Подсчет токенов запрошен для модели, не входящей в размещенные модели с открытыми весами. |
401 | authentication_error | Missing authentication | API-ключ не был отправлен. |
401 | authentication_error | Invalid API key | Ключ неизвестен, заменен более новым или отозван. |
404 | not_found_error | Not Found | Такого пути не существует, например /v1/embeddings. |
405 | api_error | Method Not Allowed | Путь существует, но не с этим HTTP-методом. |
413 | invalid_request_error | Failed to buffer the request body: … | Тело запроса больше 32 MiB. |
415 | invalid_request_error | Expected request with `Content-Type: application/json` | Заголовок Content-Type отсутствует или не равен application/json. |
422 | invalid_request_error | Failed to deserialize the JSON body into the target type: … | Отсутствует обязательное поле, например messages, или у поля неверный тип JSON. |
429 | rate_limit_error | Quota exceeded. Upgrade your plan at shannon-ai.com/plan | Лимит вывода запроса больше, чем остаток вашего баланса. |
429 | rate_limit_error | Too many requests. Retry in <n>s. | Защита от флуда: более 120 запросов в минуту с вашего аккаунта. <n> — число секунд до конца минуты. |
429 | rate_limit_error | Shannon Coder call quota reached. Upgrade your plan at shannon-ai.com/plan | shannon-coder-1 на /v1/chat/completions и /v1/messages: вызовы Shannon Coder по вашему плану в текущем 4-часовом окне исчерпаны. |
429 | rate_limit_error | Shannon routes are temporarily busy. Please retry. | Модель в данный момент занята. |
500 | server_error | The model backend failed to answer. Please retry. | Модель не сформировала ответ. |
500 | server_error | An internal error occurred. Please retry. | Внутренний сбой при подготовке запроса. |
502 | api_error | The model backend failed to answer. Please retry. | Модель не сформировала ответ. Семейство Shannon 3 и размещенные модели с открытыми весами. |
503 | api_error | Could not verify your quota right now. Please retry. | Ваш баланс в данный момент не удалось прочитать. |
503 | api_error | token counting is temporarily unavailable for this model | Подсчет токенов для этой модели в данный момент недоступен. |
504 | api_error | upstream timed out | Размещенная модель с открытыми весами превысила ограничение по времени для запроса без стриминга. |
Проверки выполняются в таком порядке: API-ключ, тело запроса, id модели, защита от флуда, баланс. Поэтому запрос с неверным ключом и неверным телом возвращает 401. Запрос, отклоненный со статусом 4xx, не тарифицируется.
Баланс и защита от флуда
Все запросы обслуживаются одинаково: нет тарифных уровней скорости и отдельной квоты API. Ответить 429 все же могут две вещи, обе с типом rate_limit_error. Различить их позволяет сообщение.
Баланс
Ваш баланс — это лимит плана на сегодня, который обновляется в 00:00 UTC, а после него — купленные кредиты. Чат и API списывают с одного и того же баланса.
Каждый запрос сначала резервирует свой лимит вывода: max_tokens или max_output_tokens на /v1/responses, 4,096, если вы не отправили ни того, ни другого. Когда ответ завершен, зарезервированная сумма заменяется тем, что запрос использовал. Если сумма не помещается в оставшийся баланс, запрос возвращает Quota exceeded до запуска модели.
Запросы, выполняемые одновременно, резервируют каждый свою сумму. Поэтому множество параллельных запросов с большим max_tokens может на время исчерпать баланс, даже если каждый ответ короткий. Меньший max_tokens резервирует меньше.
Защита от флуда
Защита от флуда допускает 120 запросов в минуту на аккаунт. Она считает аккаунт, а не ключ, по /v1/chat/completions, /v1/responses и /v1/messages вместе. Минута начинается с вашего первого запроса.
Запрос номер 121 в течение этой минуты возвращает Too many requests. Retry in <n>s. и ничего не стоит. Отклоненные запросы тоже считаются, поэтому клиент, который повторяет запрос без ожидания, остается отклоненным до конца минуты. Подождите <n> секунд, указанных в сообщении.
Параллельно отправленные запросы не отклоняются за параллельность. Они ждут в очереди и обрабатываются по очереди.
GET /v1/models и эндпоинты подсчета токенов не учитываются.
Подробнее о лимитах и балансе и о том, где посмотреть свой баланс и использование: Лимиты и баланс Keys & usage
Что повторять
Повторяйте запрос только тогда, когда тот же запрос может завершиться успешно. SDK OpenAI и Anthropic уже сами дважды повторяют ответы 429 и 5xx; чтобы изменить это, задайте max_retries (maxRetries в JavaScript).
| Ошибка | Повторять? | Как |
|---|---|---|
400, 404, 405, 413, 415, 422 | Нет. Сначала исправьте запрос. | Сообщение называет неверное поле, модель или ввод. |
401 | Нет. Используйте действительный ключ. | Создайте или замените ключ на странице Keys & usage. |
429 Too many requests | Да, после ожидания. | Подождите число секунд из сообщения, затем отправьте тот же запрос. |
429 Quota exceeded | Не в таком виде. | Уменьшите max_tokens, дождитесь ежедневного сброса в 00:00 UTC или пополните кредит. |
429 Shannon Coder call quota reached | Да, в следующем окне. | Окна начинаются каждые 4 часа от 00:00 UTC. Другая модель ответит сразу. |
429 … temporarily busy | Да. | Подождите несколько секунд, а при каждой следующей попытке — дольше. |
500, 502, 503 | Да. | Подождите 1 секунду, затем 2, 4 и 8. Остановитесь после нескольких попыток и сообщите id запроса. |
504 | Да. | Отправьте его с stream, равным true: ограничение по времени действует для ответов без стриминга. |
Цикл повторов, который следует этой таблице:
import re
import time
import openai
from openai import OpenAI
# max_retries=0: this loop does the retrying, the SDK does not add its own.
client = OpenAI(api_key="YOUR_API_KEY", base_url="https://api.shannon-ai.com/v1", max_retries=0)
def create_with_retry(attempts=5, **request):
for attempt in range(attempts):
try:
return client.chat.completions.create(**request)
except openai.RateLimitError as error:
text = str(error)
flood = re.search(r"Retry in (\d+)s", text)
if flood:
time.sleep(int(flood.group(1))) # flood protection: wait out the minute
elif "temporarily busy" in text:
time.sleep(2 ** attempt)
else:
raise # balance: a wait of seconds does not help
except (openai.InternalServerError, openai.APIConnectionError):
time.sleep(2 ** attempt) # 1, 2, 4, 8 seconds
raise RuntimeError("the request failed after all attempts")
response = create_with_retry(
model="shannon-3",
messages=[{"role": "user", "content": "Hello"}],
)
print(response.choices[0].message.content) import OpenAI from "openai";
// maxRetries: 0 — this loop does the retrying, the SDK does not add its own.
const client = new OpenAI({ apiKey: "YOUR_API_KEY", baseURL: "https://api.shannon-ai.com/v1", maxRetries: 0 });
const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));
async function createWithRetry(request, attempts = 5) {
for (let attempt = 0; attempt < attempts; attempt++) {
try {
return await client.chat.completions.create(request);
} catch (error) {
const text = String(error.message);
const flood = text.match(/Retry in (\d+)s/);
if (error.status === 429 && flood) {
await sleep(Number(flood[1])); // flood protection: wait out the minute
} else if (error.status === 429 && text.includes("temporarily busy")) {
await sleep(2 ** attempt);
} else if (error.status >= 500 || error instanceof OpenAI.APIConnectionError) {
await sleep(2 ** attempt); // 1, 2, 4, 8 seconds
} else {
throw error; // wrong request, wrong key or balance
}
}
}
throw new Error("the request failed after all attempts");
}
const response = await createWithRetry({
model: "shannon-3",
messages: [{ role: "user", content: "Hello" }],
});
console.log(response.choices[0].message.content); Ошибки внутри потока
Поток имеет статус 200 с первого байта. Сбой после этого приходит как событие внутри потока: чанк с полем error на /v1/chat/completions, событие error на /v1/messages, событие response.failed или error на /v1/responses. Завершающие события потока все равно следуют. Считайте такой ответ неудачным и отправьте запрос снова, как при статусе 5xx.
Разговор, превышающий контекстное окно модели, сообщается именно так, с типом invalid_request_error и кодом context_length_exceeded. Сократите разговор, прежде чем отправлять его снова.
Точные фреймы описаны в разделе о стриминге. Потоковая передача
Id запросов
Каждый ответ, включая ошибки, имеет заголовок x-request-id. Это значение, которое вы отправили в собственном заголовке x-request-id, либо 12 шестнадцатеричных символов, если вы ничего не отправляли. Записывайте его в журнал и приводите, когда просите помощи по запросу.