Обработка ошибок: таймауты, повторы, backoff

Сеть ненадёжна, а провайдер иногда перегружен. Устойчивый код это предусматривает.

Экспоненциальный backoff — стратегия повторов, где задержка между попытками растёт по экспоненте (1с, 2с, 4с...), часто с добавлением случайного «джиттера».

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

КодПричинаПовторять?
400ошибка в запросе (формат, параметры)нет — чините запрос
401неверный ключнет — чините ключ
429превышен rate limitда — после паузы
500 / 529сбой/перегрузка серверада — с backoff
таймаут соединениясеть/долгий ответда — с backoff

Главное: повторять стоит только «временные» ошибки (429, 5xx, таймауты). Ошибки запроса (4xx, кроме 429) повторять бессмысленно — они не пройдут и со второго раза.

SDK уже это умеют

Официальные клиенты Anthropic и OpenAI автоматически повторяют 429 и 5xx с backoff. Часто достаточно настроить число повторов и таймаут — свой цикл не нужен.

import anthropic, httpx

client = anthropic.Anthropic(
    max_retries=5,           # сколько раз повторить
    timeout=httpx.Timeout(30.0),
)

try:
    resp = client.messages.create(
        model="claude-opus-4-8",
        max_tokens=512,
        messages=[{"role": "user", "content": "..."}],
    )
except anthropic.RateLimitError as e:
    # сюда попадём, если повторы исчерпаны
    print("Лимит исчерпан:", e)
except anthropic.APIStatusError as e:
    print("Ошибка API:", e.status_code)

Расчёт задержек backoff (запускаемо)

Посмотрим, как растут паузы между попытками. Джиттер делаем детерминированным (фиксируем seed) ради воспроизводимого вывода — чистый Python:

import random
random.seed(42)  # детерминированный вывод для урока

def backoff_delays(max_retries=5, base=1.0, cap=60.0):
    delays = []
    for attempt in range(max_retries):
        raw = base * (2 ** attempt)      # 1, 2, 4, 8, 16...
        jitter = random.uniform(0, 1)    # случайная добавка
        delays.append(round(min(raw + jitter, cap), 2))
    return delays

for i, d in enumerate(backoff_delays()):
    print(f"Попытка {i+1}: ждём {d} c")

Вывод:

Попытка 1: ждём 1.64 c
Попытка 2: ждём 2.03 c
Попытка 3: ждём 4.28 c
Попытка 4: ждём 8.22 c
Попытка 5: ждём 16.74 c

Джиттер (случайная добавка) важен: без него тысячи клиентов, упёршихся в лимит одновременно, повторят запрос в одну и ту же секунду и снова создадут пик. Разброс «размазывает» нагрузку.

Итог

  • Повторяйте только временные ошибки: 429, 5xx, таймауты; запросные (4xx) — чините.
  • Backoff: задержка растёт экспоненциально, плюс джиттер против синхронных пиков.
  • SDK делают повторы сами — настройте max_retries и timeout.
Проверьте себя
1. Какие ошибки имеет смысл повторять с backoff?
AЛюбые, включая 400 и 401
BВременные: 429, 5xx и таймауты соединения
CТолько 400 (ошибка запроса)
DНикакие — повторы бесполезны
2. Зачем в backoff добавляют джиттер (случайную задержку)?
AЧтобы запросы шли быстрее
BЧтобы множество клиентов не повторяли запрос одновременно и не создавали новый пик
CЧтобы обойти rate limit
DЭто требование протокола
3. Нужно ли обычно писать собственный цикл повторов?
AДа, всегда — SDK этого не умеют
BЧасто нет: официальные SDK повторяют 429 и 5xx автоматически, достаточно настроить max_retries
CНет, повторы вообще не нужны
DДа, потому что SDK повторяют только 400
Поддержать проект