API мовних моделей — це віддалені сервіси з незвичними властивостями: запити тривають від секунд до хвилин, потужності розподілені між тисячами клієнтів, ліміти рахуються не лише в запитах, а й у токенах, а збої популярних провайдерів потрапляють у новини. Функція, що ідеально працює в демо, у продакшені може падати щодня. Добра новина: патерни надійності давно відомі з розподілених систем. У гайді адаптуємо їх до інтеграцій з LLM.

Знайте свої режими збоїв

Збій Типовий сигнал Повторювати?
Перевищено ліміт HTTP 429, часто із заголовком retry-after Так, після вказаної затримки
Провайдер перевантажений HTTP 529 / 503 або специфічна помилка «overloaded» Так, з backoff
Помилка сервера HTTP 500, 502, 504 Так, з backoff, обмежена кількість спроб
Таймаут / розрив з'єднання Таймаут на клієнті, обірваний стрім Так, якщо операцію безпечно повторити
Некоректний запит HTTP 400 (погані параметри, задовгий контекст) Ні — виправте запит
Автентифікація / права HTTP 401, 403 Ні — алерт
Відмова чи блокування політикою Успішна відповідь із відмовою або відповідною stop reason Ні — обробляйте в логіці застосунку
Обрізаний вивід Причина завершення max_tokens / length Можливо — продовжити чи збільшити ліміт
Невалідний структурований вивід Помилка розбору чи валідації Так, один раз, із повідомленням про помилку

Документація провайдерів містить точні коди й заголовки (помилки Anthropic, ліміти Anthropic, ліміти OpenAI). Явно класифікуйте помилки у своєму клієнті: якщо все обробляти як «повторити тричі», ви або довбатимете зламаний запит, або здаватиметеся на тимчасовому збої.

Таймаути: встановлюйте свідомо

Таймаути HTTP-клієнтів за замовчуванням рідко підходять для LLM:

  • Таймаут з'єднання: короткий (5–10 с). Неможливість з'єднатися — не проблема LLM.
  • Час до першого токена: для стрімінгу — окремий дедлайн (наприклад, 20–30 с), що рано ловить завислі запити.
  • Таймаут простою між фрагментами: стрім, що 30–60 с не надсилає даних, імовірно мертвий.
  • Загальний дедлайн: визначається сценарієм — відповідь у чаті може мати 60–90 с, фоновий звіт — кілька хвилин.

Передавайте дедлайни далі: якщо ваш HTTP-ендпоінт має бюджет 30 секунд, виклик моделі не може мати 120. І завжди скасовуйте вихідний запит, коли клієнт від'єднався, — інакше платите за токени, які ніхто не прочитає; див. AI-чат на Next.js.

Повторні спроби з експоненційним backoff і jitter

Повторюйте тимчасові помилки з експоненційно зростаючими затримками й випадковістю, щоб тисячі клієнтів не повторювали синхронно. Підхід «full jitter» — випадкова затримка від нуля до експоненційної межі — добре працює під навантаженням (AWS Architecture Blog).

import random, time

RETRYABLE = {429, 500, 502, 503, 504, 529}

def call_with_retries(fn, max_attempts=4, base=1.0, cap=30.0):
    for attempt in range(1, max_attempts + 1):
        try:
            return fn()
        except ApiError as e:
            if e.status not in RETRYABLE or attempt == max_attempts:
                raise
            retry_after = e.headers.get("retry-after")
            delay = float(retry_after) if retry_after else random.uniform(0, min(cap, base * 2 ** attempt))
            time.sleep(delay)

Правила, що не дають повторам погіршити ситуацію:

  • Поважайте retry-after, коли провайдер його надсилає.
  • Обмежуйте кількість спроб (3–5) і загальний час повторів межами дедлайну запиту.
  • Повторюйте лише на одному рівні. Якщо повторює SDK, і ваш код навколо нього, і черга навколо коду, один збій перетворюється на десятки викликів. Більшість офіційних SDK повторюють автоматично — налаштуйте їх, а не нашаровуйте власні повтори.
  • Використовуйте бюджет повторів — наприклад, повтори не можуть перевищувати 10% запитів за хвилину, — щоб збій провайдера не множив ваш трафік. Детально про це — у книзі Google SRE.

Rate limits: керуйте проактивно

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

  • Rate limiting на клієнті через token bucket для кожної моделі з розміром трохи нижче ваших лімітів.
  • Класи пріоритету: спершу інтерактивні запити; пакетна й фонова робота заповнює решту потужності.
  • Черги для фонової роботи з обмеженням конкурентності; див. розділ нижче.
  • Batch API для всього, що може зачекати години, — значно дешевше й з окремими лімітами; див. оптимізація витрат на LLM.
  • Кешування промптів зменшує тиск токенів для повторюваних префіксів; деякі провайдери враховують кешовані токени в лімітах інакше.
  • Моніторте запас: заголовки відповіді часто повідомляють залишок запитів і токенів — експортуйте їх як метрики.

Фолбеки: моделі й провайдери

Коли основна модель недоступна чи впирається в ліміти, фолбек зберігає функцію живою:

  1. Той самий провайдер, інша модель — часто менша модель з окремою потужністю.
  2. Та сама модель через іншу платформу — багато моделей доступні через кілька хмар (наприклад, власний API провайдера й маркетплейси великих хмар), що дає різноманітність провайдерів з ідентичною поведінкою.
  3. Модель іншого провайдера — максимальна незалежність, але інша поведінка; промпти й розбір виводу треба тестувати з кожним фолбеком.
const chain = [
  { provider: "primary", model: process.env.CHAT_MODEL! },
  { provider: "cloud-b", model: process.env.CHAT_MODEL_CLOUD_B! },
  { provider: "fallback", model: process.env.FALLBACK_MODEL! },
]

export async function generate(req: Req) {
  let lastError: unknown
  for (const target of chain) {
    if (breaker.isOpen(target.provider)) continue
    try {
      const res = await callModel(target, req, { timeoutMs: 45_000 })
      breaker.success(target.provider)
      return { ...res, servedBy: target }
    } catch (e) {
      lastError = e
      if (!isRetryableAcrossProviders(e)) throw e // напр. 400 invalid request
      breaker.failure(target.provider)
    }
  }
  throw lastError
}

Логуйте, яка ціль обслужила кожен запит (servedBy), і додавайте це в траси. Проганяйте набір evals на кожній фолбек-моделі, щоб знати, яку якість отримують користувачі під час інциденту; див. evals для LLM. Шлюзи на кшталт проксі в стилі LiteLLM чи хмарні AI-шлюзи можуть централізувати цю логіку для всіх сервісів.

Circuit breakers

Circuit breaker перестає надсилати запити до залежності, що збоїть, швидко повертає помилку і періодично перевіряє відновлення. Для LLM-провайдерів розмикайте ланцюг, коли частка помилок чи затримка перевищують поріг у короткому вікні, і одразу спрямовуйте трафік на фолбек, а не чекайте таймауту кожного запиту. Патерн реалізують Resilience4j для JVM-сервісів, Polly для .NET і бібліотеки на кшталт opossum для Node.

Ідемпотентність для агентів і побічних ефектів

Повтори небезпечні, коли операція має побічні ефекти. Крок агента, що створює рахунок і потім ловить таймаут, може повторитися й створити другий рахунок. Захищайте побічні ефекти:

  • Ключі ідемпотентності для операцій запису, похідні від ідентифікатора запуску й номера кроку. Патерн добре показують API на кшталт Stripe.
  • Перевір, перш ніж діяти: інструменти, що створюють записи, спершу шукають наявний запис від того самого запуску.
  • Контрольні точки стану агента після кожного завершеного кроку, щоб відновлений запуск не повторював виконану роботу.
  • Відокремлюйте рішення від виконання: модель пропонує дію; детермінований код виконує її рівно один раз.

Це ще важливіше для довготривалих і мультиагентних систем.

Черги для фонової й масової роботи

Для неінтерактивної LLM-роботи — обробка документів, збагачення даних, нічні підсумки — ставте запити в чергу:

  • Воркери беруть задачі з обмеженням конкурентності, що враховує ліміти.
  • Невдалі задачі повторюються з backoff і після N спроб потрапляють у dead-letter чергу.
  • Задачі ідемпотентні й містять усі вхідні дані для повтору.
  • Пропускна здатність адаптується автоматично: під час уповільнень провайдера черга росте, а запити не падають.

Така архітектура також робить вартість передбачуваною й добре поєднується з batch API. На ній працюють наші пайплайни видобування даних; див. обробка документів з LLM.

Плавна деградація

Заздалегідь вирішіть, що бачать користувачі, коли AI недоступний:

  • Чат-асистент: зрозуміле повідомлення з посиланням на підтримку чи пошук, а не вічний спінер.
  • Пошук на основі AI: перехід на пошук за ключовими словами.
  • Класифікація й маршрутизація: перехід на правила або чергу за замовчуванням для ручного тріажу.
  • Генерація контенту: дати користувачам продовжити без неї; поставити генерацію в чергу на потім.

Фіче-прапорці дозволяють швидко вимкнути AI-функції під час інциденту. Найкращі AI-функції — додаткові: продукт працює й без них.

Спостережуваність для надійності

Вимірюйте те, що потрібно для керування всім вище:

  • Частку помилок за типом (429, 5xx, таймаути), за провайдером і моделлю.
  • Кількість повторів і частку фолбеків; час, витрачений на повтори.
  • Перцентилі затримки, включно з часом до першого токена.
  • Запас до лімітів.
  • Зміни стану circuit breaker.

Ставте алерти на активацію фолбеків і стабільно високу частку помилок. Налаштування трасування — у статті спостережуваність LLM.

Чек-лист надійності

  • Помилки класифіковані; повторюються лише тимчасові
  • Таймаути: з'єднання, перший токен, простій і загальний дедлайн
  • Експоненційний backoff з jitter, повага до retry-after, повтори лише на одному рівні
  • Rate limiting на клієнті й пріоритети між інтерактивною та фоновою роботою
  • Щонайменше одна протестована фолбек-модель чи провайдер для критичних функцій
  • Circuit breaker на кожного провайдера
  • Ключі ідемпотентності для кожного побічного ефекту, ініційованого AI
  • Черга з обробкою dead-letter для фонових задач
  • Визначений деградований режим і фіче-прапорець для вимкнення AI-функцій
  • Дашборди й алерти на помилки, фолбеки й затримку

FAQ

Чи завжди потрібен другий провайдер? Для критичних функцій, з якими працюють клієнти, — так, або хоча б та сама модель через другу платформу. Для внутрішніх інструментів може вистачити чіткого деградованого режиму.

Чи обробляють повтори офіційні SDK? Більшість за замовчуванням кілька разів повторюють при помилках з'єднання, 429 і 5xx. Налаштуйте їхні ліміти й не додавайте поверх ще один рівень повторів.

Як тестувати надійність? Вносьте збої на staging: моки 429, повільні стріми й відмови, і перевіряйте, що фолбеки, breakers і деградований UX працюють.

Чи складніше зробити надійним стрімінг? Так. Стрім може обірватися посередині; вирішіть, чи показувати часткову відповідь, відновлювати чи генерувати заново, і ніколи мовчки не повторюйте наполовину застрімлену відповідь.

Джерела

  1. AWS Architecture Blog. Exponential Backoff And Jitter.
  2. Google SRE Book. Addressing Cascading Failures і Handling Overload.
  3. Martin Fowler. CircuitBreaker.
  4. Anthropic. API errors і rate limits.
  5. OpenAI. Rate limits guide.
  6. Stripe. Idempotent requests.