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.
- Кешування промптів зменшує тиск токенів для повторюваних префіксів; деякі провайдери враховують кешовані токени в лімітах інакше.
- Моніторте запас: заголовки відповіді часто повідомляють залишок запитів і токенів — експортуйте їх як метрики.
Фолбеки: моделі й провайдери
Коли основна модель недоступна чи впирається в ліміти, фолбек зберігає функцію живою:
- Той самий провайдер, інша модель — часто менша модель з окремою потужністю.
- Та сама модель через іншу платформу — багато моделей доступні через кілька хмар (наприклад, власний API провайдера й маркетплейси великих хмар), що дає різноманітність провайдерів з ідентичною поведінкою.
- Модель іншого провайдера — максимальна незалежність, але інша поведінка; промпти й розбір виводу треба тестувати з кожним фолбеком.
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 працюють.
Чи складніше зробити надійним стрімінг? Так. Стрім може обірватися посередині; вирішіть, чи показувати часткову відповідь, відновлювати чи генерувати заново, і ніколи мовчки не повторюйте наполовину застрімлену відповідь.
Джерела
- AWS Architecture Blog. Exponential Backoff And Jitter.
- Google SRE Book. Addressing Cascading Failures і Handling Overload.
- Martin Fowler. CircuitBreaker.
- Anthropic. API errors і rate limits.
- OpenAI. Rate limits guide.
- Stripe. Idempotent requests.