LLM API и интеграция

Ошибки API нейросетей: коды, повторы и fallback

Ошибки API нейросетей: коды, повторы и fallback

Ошибка от API модели — это не одно событие, а несколько разных, и обрабатывать их одинаково дорого. Часть отказов приходит до того, как запрос вообще ушёл к модели, и денег не стоит. Часть означает, что стоит подождать и повторить. Часть — что повторять бессмысленно, пока вы не измените код. А отдельные отказы вообще приходят успешным ответом, а не ошибкой, и невнимательный обработчик примет их за нормальный результат. Разбираем, какие бывают отказы, что за каждым стоит и как построить переключение на запасную модель так, чтобы оно сработало в тот момент, когда понадобится.

Три группы отказов

Разложим по тому, что с ними делать, а не по кодам — так практичнее.

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

Отказы, связанные с нагрузкой. Превышен лимит запросов, сервис модели временно недоступен. Здесь повтор осмыслен, но не сразу и не бесконечно.

Отказы из-за запроса. Некорректный запрос, неподдерживаемый параметр. Повторять бесполезно: пока вы не поправите код, ответ не изменится.

Отдельно стоит четвёртая категория, которая формально не отказ: успешный ответ без полезного содержимого. У моделей Claude классификатор безопасности может отклонить запрос, и это придёт HTTP-статусом 200 с полем stop_reason, а не кодом ошибки. Мы разбирали это, когда Fable 5 появилась в каталоге. Обработчик, который смотрит только на код ответа, такой отказ пропустит и отдаст пользователю пустоту.

Что означает каждый код

Разберём стабильный набор по смыслу, потому что похожие названия скрывают разные причины.

invalid_request — запрос сформирован неправильно. Проблема в вашем коде, повтор не поможет.

unsupported_parameter — параметр передан корректно, но эта модель его не принимает. Типичный случай при переезде между моделями: то, что работало у одной, у другой не поддерживается.

model_not_found — идентификатора не существует. Чаще всего это следствие переименования: модель никуда не делась, у неё сменился публичный идентификатор, а старый перестал отвечать. Именно так было с одной из моделей Grok, о чём мы писали в разборе роста каталога.

model_under_review — модель есть, маршрут закрыт. Отличается от предыдущего принципиально: ждать имеет смысл.

invalid_api_key — ключ недействителен. Отдельно стоит проверить, не отозван ли он и тот ли это ключ, что вы думаете.

insufficient_balance — денег не хватает на выполнение запроса.

paid_topup_required — доступ к модели требует подтверждённого платного пополнения. Это не то же самое, что нехватка баланса: средства на счету могут быть, но не того происхождения. Разбирали на примере бесплатной генерации изображений.

rate_limit_exceeded — превышена частота запросов. Единственный случай, где повтор с паузой — правильная реакция.

model_service_unavailable — сервис модели временно недоступен. Повтор осмыслен, но стоит сразу рассматривать переключение.

not_found — запрошенного объекта нет; относится к обращениям, не связанным с генерацией.

Практическая ценность этого списка не в заучивании, а в том, чтобы развести обработку. Три ветки — «чинить код», «подождать», «сменить маршрут» — покрывают весь набор и радикально сокращают время разбора инцидента.

Почему статус маршрута — отдельная история

Самый недооценённый класс отказов, потому что он не про ваш код и не про нагрузку.

В ответе GET /v1/models у каждой модели есть объект protocols со статусом по каждому маршруту: available или review. Закрытый маршрут отклоняет запрос до резервирования средств — вы получите отказ, но не счёт.

Ключевое: статус меняется без предупреждения и в обе стороны. За последние десять дней мы наблюдали, как протокол открывался у восьми моделей Claude за несколько часов, как Responses впервые стал доступен у одной модели, а через сутки у второй. Позиции появлялись, исчезали и возвращались.

Отсюда правило, которое стоит зашить в код: читайте статус протокола перед запросом, а не полагайтесь на то, что работало вчера. Одна проверка избавляет от разбора непонятных отказов на проде.

И проверять надо именно тот протокол, который вы зовёте. Поле availability верхнего уровня и статус конкретного маршрута — разные вещи, и они расходились: модель числилась доступной, а нужный маршрут был закрыт.

Как строить переключение на запасной вариант

Практическая часть. Здесь чаще всего ошибаются в одном месте — запасной вариант выбирают в момент, когда он понадобился.

Выберите запасную модель заранее и проверьте её. Не «в случае чего переключимся», а конкретная модель, на которой вы прогнали свои реальные запросы и убедились, что результат приемлем. Иначе переключение превратит отказ в тихую деградацию качества, которую никто не заметит неделю.

Держите идентификатор модели в конфигурации. Не в коде вызова. Смена модели должна быть правкой конфига, а не релизом.

Переключайтесь не на всех ошибках. На закрытом маршруте и недоступности сервиса — да. На некорректном запросе — нет, запасная модель отклонит его точно так же, вы просто удвоите расход времени.

Логируйте факт переключения. Молчаливый переход на запасную модель — способ месяц не замечать, что основная не работает.

Не забудьте про разницу в поведении. Модели отличаются форматом ответа, длиной, склонностью к спискам. Если ваш парсер настроен на одну, вторая может ломать его на ровном месте.

Про повторные попытки

Отдельно, потому что здесь легко сделать хуже.

При отказе по нагрузке повтор осмыслен, но с задержкой и с ограничением числа попыток. Повтор сразу же, без паузы, усугубляет ситуацию: сервис и так перегружен, а вы добавляете нагрузки.

Разумный минимум — увеличивающаяся пауза между попытками и жёсткий предел, после которого запрос считается неуспешным и уходит в очередь или к пользователю с честным сообщением. Три попытки покрывают подавляющее большинство временных сбоев; десять не покрывают ничего сверх этого, зато превращают короткий сбой в длинную деградацию.

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

Что проверить в своём коде сегодня

Пять пунктов, каждый занимает минуты.

  1. Найдите, где вы обрабатываете ошибки API. Если там одна ветка на все случаи — это первое, что стоит разделить.
  2. Проверьте, ловите ли вы отказ, пришедший успешным ответом. Поле stop_reason в родном формате Anthropic — самый частый пропуск.
  3. Убедитесь, что идентификатор модели не зашит в вызов.
  4. Прогоните запасную модель на своих запросах. Если этого не делали — переключения у вас, по сути, нет.
  5. Добавьте чтение статуса протокола перед запросом или хотя бы отдельную обработку отказа маршрута, чтобы он не сливался с остальными ошибками в логах.

Чего мы не утверждаем

Не приводим полный контракт ошибок. Стабильный набор кодов перечислен в llms.txt, и он может дополняться. Сверяться стоит там, а не по статье.

Не обещаем, что отказ никогда не будет стоить денег. Отказы до обращения к модели бесплатны; за отказ, случившийся после начала работы модели, счёт возможен.

Не даём готовой библиотеки. Правильная стратегия повторов зависит от вашей нагрузки, и универсального набора настроек не существует.

Не описываем поведение конкретных вендоров за пределами каталога. Речь про контракт нашего API.

Что держать в голове

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

Запасной вариант работает, только если он выбран и проверен заранее. Выбранный в момент сбоя — это не запасной вариант, а вторая авария.

Как мы проверяем факты и почему у каждого стоит источник — на странице о проекте. Один ключ на весь каталог, статусы маршрутов — в живой выдаче: keydealer.ru/login.

Частые вопросы

Какие коды ошибок возвращает API?

Стабильный набор включает invalid_request, unsupported_parameter, model_under_review, invalid_api_key, insufficient_balance, paid_topup_required, model_not_found, rate_limit_exceeded, model_service_unavailable и not_found.

Списываются ли деньги за отклонённый запрос?

Нет, если отказ произошёл до обращения к сервису модели. Отказ по статусу протокола и отказ по балансу происходят раньше резервирования средств.

Чем model_under_review отличается от model_not_found?

Первое означает, что модель есть в каталоге, но маршрут ещё не открыт. Второе — что такого идентификатора нет вовсе, например после переименования модели.

Как правильно выбирать запасную модель?

Заранее и из того же класса задач. Переключение должно быть проверено на своих запросах до того, как понадобится, а не подобрано в момент сбоя.

Где смотреть, открыт ли маршрут?

В ответе GET /v1/models, поле protocols для нужного протокола. Значение available означает, что запрос пройдёт, review — что будет отклонён.

Источники