Обработка ошибок LLM API: retry, 429, 503 и запасная модель

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

5 мин · · ошибки · разработка

Бот или пайплайн, который падает на первом же 503, приходится перезапускать руками. А скрипт, который бесконечно повторяет запрос с неверным ключом, просто греет сервер. Грамотная обработка ошибок LLM API — это три решения: какие коды повторять, сколько ждать между попытками и когда переключаться на другую модель. Ниже — таблица кодов Zukko и готовый код на Python.

Коды ошибок и что с ними делать

Zukko отвечает на ошибки обычным HTTP-статусом и JSON-телом в формате OpenAI; уточняющий код лежит в поле error.code. Разбирайте ошибку по статусу и коду, а не по тексту сообщения.

СтатусЧто значитПовторять?
400Некорректный запрос: параметры, формат сообщенийНет, исправьте запрос
401Неверный или отозванный ключНет
402Не хватает балансаНет, сначала пополните баланс
403 model_not_allowed_for_keyМодели нет в списке разрешённых для ключаНет, поправьте allowed_models или возьмите разрешённую модель
404 model_not_foundНеизвестное имя моделиНет, проверьте id по GET /v1/models
413Слишком большой запросНет, уменьшите запрос
429Исчерпан лимит ключа или ограничение у провайдера модели (общего rate limit у Zukko нет)Да, с паузой — кроме лимитов ключа (см. ниже)
503Провайдер модели недоступенДа, с паузой или на другой модели
503 model_disabledМодель выключенаНет, возьмите другую модель из GET /v1/models

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

Особый случай: 429 из-за лимита ключа

У каждого ключа в кабинете можно задать дневной и месячный потолок расхода. Когда он исчерпан, приходит 429 с кодом key_daily_limit_reached или key_monthly_limit_reached. Повторять такой запрос через пару секунд бессмысленно: лимит сбросится только в начале следующих суток или месяца по UTC, либо когда вы поднимете его в кабинете. Подробнее — в разделе о лимитах ключа.

Обработка ошибок LLM API: правила повторов

  1. Экспоненциальная пауза со случайной добавкой. 1, 2, 4 секунды плюс случайные доли секунды — чтобы сотня воркеров не ударила в сервер одновременно.
  2. Заголовок Retry-After. Если сервер прислал его в ответе, ждите столько, сколько сказано.
  3. Ограниченное число попыток. Две-три попытки на одной модели, потом — переключение на резерв или ошибка наверх.
  4. Защита от двойной оплаты. Если запрос упал по таймауту, он мог дойти до модели. Zukko принимает заголовок Idempotency-Key: повтор с тем же значением вернёт сохранённый успешный ответ и не спишет деньги второй раз. Генерируйте одно значение на логический запрос и передавайте его во всех повторах. Два одновременных запроса с одним ключом идемпотентности оба уйдут к модели, поэтому повторяйте только после того, как предыдущая попытка завершилась.
  5. Отключите встроенные повторы SDK, если пишете свои. OpenAI SDK по умолчанию сам повторяет 408, 409, 429 и 5xx два раза. Вместе с вашим циклом получится до девяти запросов вместо трёх.

Как встроить такие повторы в партию из тысяч запросов — в статье об асинхронной обработке на Python.

Резервная модель

Ошибка 503 обычно означает проблему у конкретного провайдера, поэтому резерв разумно брать у другого вендора. Пример цепочки от сильной модели к дешёвой (цены на 6 октября 2026, $ за 1M токенов, индекс силы — Intelligence Index от Artificial Analysis из каталога Zukko):

МодельВход / выходИндекс силыКартинки на вход
claude-opus-5-50.4 / 258да
gpt-6.1-sol0.27 / 1.3252да
gemini-3.8-flash0.025 / 0.08841нет

Три практических замечания:

  • Резерв должен уметь то же, что основная модель. Если в запросах есть картинки, gemini-3.8-flash не подойдёт — возьмите, например, gpt-6-sol.
  • Добавьте резервные модели в allowed_models ключа. Иначе вместо ответа резерва вы получите 403.
  • Проверьте резерв заранее. Прогоните на нём 20–30 типичных запросов: держит ли он формат ответа, который ждёт ваш код. Узнавать это во время сбоя — поздно.

Актуальный список и цены — GET https://api.zukko.pro/v1/models или страница «Модели» в кабинете. Сам список отдаётся без ключа и бесплатно, см. справочник по /v1/models.

Код на Python

bash
import os
import random
import time
import uuid

import openai
from openai import OpenAI

client = OpenAI(
    base_url="https://api.zukko.pro/v1",
    api_key=os.environ["ZUKKO_API_KEY"],
    max_retries=0,  # повторами управляем сами
    timeout=120,
)

Что здесь происходит:

  • call_model повторяет запрос на одной модели только при 429 (кроме лимитов ключа), 5xx и сетевых сбоях. Всё остальное сразу превращается в FatalAPIError: 401, 402 и 403 на другой модели не исправятся.
  • 503 model_disabled уходит наверх без повторов, и complete сверяет оставшуюся цепочку со свежим списком GET /v1/models. Если из цепочки ничего не осталось, берётся первая доступная модель из запасного списка EMERGENCY.
  • У исключений OpenAI SDK поле code берётся из error.code в теле ответа, поэтому строковые сравнения с текстом сообщения не нужны.
  • Функция возвращает имя модели, которая ответила, — логируйте его, чтобы видеть, как часто срабатывает резерв.

Стриминг и агенты

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

В агентах с длинными сессиями смена модели посреди задачи меняет и стоимость, и поведение. Для Claude учитывайте ещё и кэш: при переключении на другую модель накопленный кэш промптов не переносится, и следующий запрос пойдёт по цене свежего входа.

Отдельный ключ под каждый сервис с дневным лимитом в кабинете на странице API-ключи ограничивает ущерб, если цикл повторов всё-таки пойдёт вразнос.

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

Почему бы не повторять все ошибки подряд?

400, 401, 402 и 403 вызваны самим запросом, ключом или балансом. Повтор вернёт ту же ошибку, а в логах появится шум, за которым не видно настоящей причины.

Как отличить свой лимит ключа от 429 провайдера?

По полю error.code: key_daily_limit_reached или key_monthly_limit_reached означают, что исчерпан потолок, который вы сами задали ключу. Своего общего ограничения частоты у Zukko нет, поэтому любой другой 429 пришёл от провайдера модели. Подробнее о 429 — в статье про ошибку 429.

Что делать при 503 model_disabled?

Не повторять: модель выключена. Возьмите другую из GET https://api.zukko.pro/v1/models — код выше делает это автоматически.

Сколько попыток ставить?

Две-три на одну модель с паузой 1–4 секунды и не больше трёх моделей в цепочке. Если и это не помогло, лучше вернуть понятную ошибку, чем держать пользователя минуту в ожидании.

zukko

Платите в 25×
меньше

за Claude Code & ChatGPT

Один ключ fetch_* и base URL api.zukko.pro — в Cursor, Claude Code, Codex и любом SDK.