Обработка ошибок
Как различать ошибки Responses API по HTTP-статусу и слою, повторять временные сбои и диагностировать обрыв потока.
Ошибки Responses API нужно разбирать в два этапа: сначала проверить HTTP-статус и Content-Type, затем осторожно читать тело в том формате, который действительно пришёл. Не рассчитывайте на одну схему ошибки для шлюза и всех поставщиков. Для временных 429, 502 и 503 допустимы ограниченные повторы, а 400, 401, 402, 403, 404 и 405 сначала требуют исправления причины.
Адрес Responses API — POST https://route.smartaipack.ru/v1/responses. Модель и точное значение request_model_id выбирайте в каталоге моделей, у варианта с опубликованным путём /v1/responses. Общий контракт маршрута описан в обзоре Responses API, а минимальный запрос — в руководстве по базовому использованию.
Каждый запрос обрабатывается отдельно
Каждый вызов /v1/responses обрабатывается отдельно. AI Gateway не заявляет собственного серверного хранения истории диалога для последующих запросов. Если приложению нужен контекст, храните его на своей стороне и формируйте следующий input из той истории, которую действительно хотите передать модели.
Поля store: true и previous_response_id не следует считать гарантированным контрактом AI Gateway. Шлюз может передать их выбранному поставщику, но поддержка, смысл и срок хранения будут зависеть от этого поставщика. AI Gateway также не гарантирует, что такие поля обязательно будут отклонены локально. Для переносимого сценария храните историю у себя и начинайте интеграцию с проверенной формой model + input.
Это особенно важно для повторов. Новый HTTP-запрос — новая попытка выполнения. Даже если предыдущий ответ не дошёл до клиента, поставщик мог уже принять запрос и начать вычисление.
Формат ответа с ошибкой
Часть локальных проверок AI Gateway возвращает JSON с объектом error. Например, некорректное JSON-тело может получить ответ такого вида:
{
"error": {
"code": "INVALID_JSON",
"message": "Request body must be valid JSON"
}
}Но это пример одного слоя, а не единая схема всех ошибок. Внутренняя проверка допуска и внешний маршрутизатор используют собственные ответы, а поставщик может вернуть свой JSON, обычный текст или тело другого формата. Регистр кодов тоже различается: локально встречаются и INVALID_JSON, и model_not_published.
Поэтому клиенту нужен безопасный порядок:
- Получить HTTP-статус и заголовки.
- Проверить
Content-Type. - Разбирать JSON только при подходящем типе содержимого и с обработкой ошибки разбора.
- Искать
code,typeиmessageтолько как доступные диагностические поля, а не как обязательные. - Сохранить ограниченный фрагмент непонятного тела без секретов, если это разрешено правилами вашего приложения.
Пример для fetch:
async function readResult(response) {
const contentType = response.headers.get("content-type") ?? "";
let body;
if (contentType.includes("application/json")) {
try {
body = await response.json();
} catch {
body = { parse_error: true };
}
} else {
body = { text: await response.text() };
}
if (!response.ok) {
const error = new Error("Responses API request failed");
error.status = response.status;
error.contentType = contentType;
error.body = body;
throw error;
}
return body;
}Не выводите в пользовательский интерфейс тело поставщика целиком: оно может содержать технические детали или отражённые части входных данных.
Коды ошибок
Ниже перечислены коды, подтверждённые в локальных слоях AI Gateway. Конкретный ответ зависит от того, где запрос остановился: во внешнем прокси, маршрутизации, проверке допуска, ограничении частоты, проверке ключа и баланса или при обращении к поставщику.
| HTTP | Код | Что означает и что делать |
|---|---|---|
400 | INVALID_JSON | Тело нельзя разобрать как JSON. Исправьте синтаксис и Content-Type. |
400 | MODEL_REQUIRED | Нет непустого строкового поля model. Скопируйте request_model_id из каталога. |
400 | model_not_published | Для указанной модели и пути нет опубликованного маршрута. Проверьте модель и /v1/responses в каталоге. |
400 | PROVIDER_ENDPOINT_NOT_SUPPORTED | Явно выбранный вариант поставщика не поддерживает запрошенный протокол. Этот локальный код подтверждён на проверке совместимости пути, но не является обязательной формой каждого отказа /v1/responses; здесь чаще возможен model_not_published. |
401 | unauthorized | Bearer-ключ отсутствует или не распознан. Проверьте заголовок и используемый секрет. |
401 | api_key_expired | Срок ключа истёк. Выпустите или выберите действующий ключ. |
402 | insufficient_balance | Ключ приостановлен из-за недостаточного баланса. Проверьте биллинг и не повторяйте запрос до пополнения. |
403 | model_not_allowed | Выбранная модель не входит в разрешения ключа. Измените модель или ограничения ключа. |
403 | api_key_scope_not_allowed | У ключа нет требуемой области доступа. Используйте ключ с подходящими правами. |
404 | ENDPOINT_NOT_SUPPORTED | Путь /v1/* не опубликован. Проверьте адрес; Responses API находится на /v1/responses. |
405 | METHOD_NOT_ALLOWED | Для опубликованного вызываемого пути нужен POST. |
429 | код может зависеть от ограничителя, в том числе TOO_MANY_REJECTED_REQUESTS | Слишком много запросов или повторных отклонений. Снизьте параллелизм и примените ограниченные повторы. |
502 | UPSTREAM_UNAVAILABLE | Локальный промежуточный слой не смог получить ответ от следующего узла до начала ответа клиенту. Код подтверждён для одного из маршрутизирующих слоёв и не гарантируется каждым поставщиком Responses API. |
503 | ADMISSION_UNAVAILABLE | Сервис предварительного допуска временно недоступен, а пригодного сохранённого решения нет. |
503 | ADMISSION_INVALID_DECISION | Предварительный допуск вернул ответ, из которого нельзя получить корректный маршрут. |
503 | SERVER_BUSY | Локальный промежуточный слой достиг предела одновременной работы. Код зависит от задействованного слоя. |
503 | route_blocked | Опубликованный маршрут временно заблокирован защитой AI Gateway; внутренний отказ 409 преобразуется публичным маршрутизатором в 503. |
Эта таблица не заменяет чтение фактического ответа. Поставщик может вернуть другой статус, другой регистр кода, собственное поле или вообще не JSON. Более общий разбор отказов шлюза есть в статье «Ошибки API», а ограничения частоты и баланса — в руководстве по лимитам API.
Поле типа ошибки
AI Gateway не гарантирует единое верхнеуровневое поле error_type. В одном ответе тип может находиться в error.type, в другом будет только error.code, а в ответе поставщика структура может быть иной. Не стройте ветвление приложения вокруг обязательного error_type и не ожидайте фиксированный словарь его значений.
Практичная классификация начинается с HTTP-статуса. Доступные code, type и message используйте для уточнения причины и диагностики. Если поля нет, клиент должен продолжать корректно работать по статусу и типу содержимого.
Также не полагайтесь на обязательный metadata: null, единый идентификатор запроса или наличие Retry-After. Идентификатор записывайте только тогда, когда он реально присутствует в заголовках или теле. Retry-After учитывайте только при фактическом наличии и корректном значении.
Ошибки до начала потока
При stream: true ошибка до начала успешной передачи приходит обычным HTTP-ответом. Клиент ещё может увидеть статус 400, 401, 402, 403, 404, 405, 429, 502 или 503, проверить Content-Type и разобрать тело по правилам выше.
Не начинайте разбор SSE, пока не проверили успешный статус и тип text/event-stream. Даже ответ со статусом 200 нельзя автоматически считать SSE, если Content-Type указывает на другой формат.
Для такого отказа полезно сохранять признак stream_started: false. Он отделяет обычную ошибку запроса от сбоя после получения первых событий.
Ошибка после начала потока
После отправки успешного HTTP-статуса сервер уже не может заменить его новым статусом. Ошибка может прийти отдельным событием SSE по схеме поставщика, проявиться исключением клиента или простым обрывом соединения. Единой структуры такого события AI Gateway не гарантирует.
Считайте ответ завершённым только после штатного признака окончания, предусмотренного выбранным поставщиком. Если поток оборвался раньше:
- пометьте накопленный результат как неполный;
- не склеивайте его автоматически с ответом новой попытки;
- запишите, что поток уже начался;
- сохраните последнее корректно разобранное событие и доступную ошибку без чувствительных данных;
- перед повтором проверьте, допустим ли второй результат для этой операции.
Обрыв соединения не доказывает, что поставщик не принял запрос. Вычисление и расход могли уже начаться. AI Gateway не обещает идемпотентность повторного вызова /v1/responses, поэтому после неопределённого завершения проверьте событие в журнале использования и стоимость в аналитике. Подробная работа с SSE описана в статье «Потоковые ответы AI Gateway».
Какие запросы повторять
Не повторяйте без исправления запросы со статусами 400, 401, 402, 403, 404 и 405. Задержка не исправит JSON, ключ, баланс, разрешения, путь или HTTP-метод.
Для 429, 502 и 503 разрешайте небольшое конечное число попыток с растущей задержкой и случайным разбросом. Если ответ содержит корректный Retry-After, не отправляйте повтор раньше указанного времени. При отсутствии заголовка используйте собственную политику.
Пример решения без привязки к схеме тела:
const retryableStatuses = new Set([429, 502, 503]);
const maxAttempts = 4;
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
const response = await sendRequest();
if (!retryableStatuses.has(response.status)) {
return readResult(response);
}
if (attempt === maxAttempts) {
return readResult(response);
}
const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
const fallbackMs = 500 * 2 ** (attempt - 1);
const jitterMs = Math.floor(Math.random() * 250);
await wait(Math.max(retryAfter ?? 0, fallbackMs + jitterMs));
}sendRequest(), parseRetryAfter() и wait() здесь условные функции вашего приложения. Повторы должны иметь общий предел времени и попыток. Сначала уменьшите параллелизм, иначе несколько процессов могут одновременно повторять один и тот же всплеск.
Для потока автоматический повтор безопаснее разрешать только до получения первого события. После начала потока повтор может создать второй ответ и дополнительный расход. Для операций с внешним эффектом — например, когда результат модели запускает отправку сообщения или изменение записи — сначала введите собственный идентификатор операции и защиту от повторного выполнения в приложении. Это защита бизнес-действия на вашей стороне, а не обещание идемпотентности Responses API.
Что записать и передать поддержке
Для каждой неуспешной попытки записывайте:
- точное время с часовым поясом;
- путь, например
/v1/responses; - значение
model, отправленное в запросе; - HTTP-статус;
- фактический
Content-Type; - доступные
code,typeи безопасную частьmessage; - номер попытки;
- был ли уже начат поток;
- идентификатор запроса, только если он реально пришёл в заголовках или теле.
Никогда не записывайте и не передавайте полный API-ключ, заголовок Authorization, полное чувствительное тело запроса, персональные данные или секреты инструментов. Если текст ошибки может повторять входные данные, очистите его перед сохранением.
Перед обращением проверьте запрос в журнале использования, сводку по модели и ключу в аналитике, состояние баланса в биллинге и доступность выбранного пути в каталоге моделей. Затем передайте безопасную диагностику в поддержку. Так можно отличить локальный отказ до поставщика, временный сбой маршрута, обрыв после принятия запроса и проблему конкретного варианта модели.