Чому ми відмовились від Round-Robin між OpenAI та Anthropic
Ми інтегрували OpenAI та Anthropic із round-robin маршрутизацією. На архітектурній діаграмі це виглядало ідеально. У продакшені це ледь не вбило наш продукт. Один і той самий промпт давав різні результати залежно від провайдера. Дебагінг 5-крокового агентного циклу? Це не інженерія — це археологія. Ми все вирізали. Захардкодили одного провайдера. Найкращий рядок коду за рік.
Чому ми відмовились від Round-Robin між OpenAI та Anthropic — і що використовуємо замість
Розробка юридичної AI-платформи навчила нас: мультипровайдерна LLM-маршрутизація чудово виглядає на архітектурних діаграмах, але ламається у продакшені.
Ідея, яка мала ідеальний сенс
Коли ми почали будувати LEX AI — платформу для аналізу мільйонів українських судових рішень — ми зробили те, що робить кожна AI-first команда: інтегрували кілька LLM-провайдерів.
OpenAI для структурованого виводу. Anthropic для глибокого юридичного аналізу. Round-robin між ними для стійкості та оптимізації витрат.
На папері це виглядало елегантно. У продакшені це був кошмар.
Що пішло не так
1. Фрагментація форматів відповідей
Наш агентний пайплайн виконує до 5 ітерацій tool-calling на кожен запит користувача. Кожна ітерація очікує нормалізовану відповідь: tool_calls, finish_reason, структурований JSON.
OpenAI та Anthropic повертають це по-різному. Ми побудували шар нормалізації. Він обробляв 90% випадків. Решта 10% — порожні відповіді, неповний JSON, неочікувані stop reasons — спричиняли тихі збої глибоко в циклі.
Один баг ми шукали 3 дні: Anthropic іноді повертав валідну відповідь зі stop_reason: "end_turn" замість "tool_use", яку наш нормалізатор пропускав далі, але наступна ітерація сприймала як фінальну відповідь. Користувач отримував напівготовий аналіз без жодної індикації, що щось пішло не так.
2. Один промпт — дві різні поведінки
Юридичний AI живе і вмирає від точності промптів. Наш системний промпт інструктує модель діяти як український юридичний асистент, класифікувати наміри, обирати інструменти та відповідати в структурованому форматі.
Claude точніше виконував інструкції українською мовою. GPT генерував чистіші JSON tool calls. Коли модель змінювалась на кожній ітерації агентного циклу, якість результату ставала підкиданням монети.
3. Дебагінг перетворився на археологію
Коли користувач повідомляв про поганий результат, ми дивились на трейс:
- Крок 1: OpenAI (класифікував намір)
- Крок 2: Anthropic (згенерував план пошуку)
- Крок 3: OpenAI (виконав інструменти)
- Крок 4: Anthropic (синтезував відповідь)
Який крок зламався? Модель чи нормалізація? Чи можемо відтворити? Ні — наступний запуск маршрутизує інакше.
4. "Оптимізація" витрат, якої не було
Round-robin мав балансувати витрати. Натомість:
- Ціни Anthropic на глибокі аналітичні запити були в 2-3 рази вищими за еквівалент OpenAI
- Але Anthropic був дешевшим на коротких запитах класифікації
- Round-robin повністю це ігнорував — він просто чергував
5. Два набори всього
Кожен провайдер має своє: rate limits, retry-стратегії, формати помилок, оновлення SDK. Наш "уніфікований" retry-шар насправді був двома retry-шарами у одному тренчкоті.
Що ми робимо зараз
Ми перейшли на strategy-based вибір провайдера з OpenAI як основним та AWS Bedrock як альтернативою — і інвестували зекономлену складність у budget-aware вибір моделі:
| Бюджет | OpenAI | AWS Bedrock | Застосування |
|---|---|---|---|
| quick | gpt-5-nano | Amazon Nova Micro | класифікація, маршрутизація |
| standard | gpt-5-mini | Amazon Nova Lite | виконання інструментів, сумаризація |
| deep | gpt-5.1 | Amazon Nova Pro | юридичний аналіз, витяг патернів |
Змінна LLM_PROVIDER_STRATEGY контролює вибір: openai-first (дефолт) або bedrock-first (якщо є AWS credentials). Один формат API. Одна обробка помилок. Одна retry-логіка. Передбачувані витрати. Відтворювані результати.
Як правильно використовувати кілька провайдерів
Task routing, а не round-robin — призначте кожному провайдеру конкретні типи завдань назавжди.
Fallback, а не чергування — Провайдер Б активується лише коли Провайдер А повертає 429 або 500.
Мультиключ одного провайдера — кілька API-ключів від одного провайдера з ротацією для обходу rate limits.
Чому AWS Bedrock змінює правила гри
| Прямий API ключ | AWS Bedrock | |
|---|---|---|
| Моделі | Один провайдер | Claude + Llama + Mistral через один SDK |
| Безпека | API key в .env | IAM roles, нема ключів у коді |
| Дані | Летять у хмару провайдера | Залишаються у вашому AWS регіоні |
| Білінг | Окремі інвойси | Єдиний рахунок AWS |
| Rate limits | Жорсткі, per-key | Provisioned Throughput |
Тег @deprecated на нашому методі getNextProvider() — найкращий рядок коду, який ми написали за рік.
Епілог: березень 2026
Коли ми писали цю статтю, fallback на Anthropic API був тимчасовим рішенням. У березні 2026 ми нарешті закрили цю главу: PR #722 замінив прямий Anthropic API на AWS Bedrock.
Що це дало на практиці? Один SDK (@aws-sdk/client-bedrock-runtime) замість двох клієнтських бібліотек. IAM-автентифікація замість ротації API-ключів. Дані залишаються в eu-central-1 — наш DPO нарешті перестав нервувати. Єдиний білінг через AWS Cost Explorer замість окремих інвойсів від OpenAI та Anthropic.
Бюджетні тіри, про які ми мріяли, тепер працюють через Bedrock: quick йде на Nova Micro, standard — на Nova Lite, deep — на Nova Pro. OpenAI залишається primary для основного пайплайну, але весь fallback-ланцюг тепер на AWS.
Виходить, рішення відмовитись від round-robin було правильним не лише тактично, а й стратегічно. Ми не просто обрали одного провайдера — ми обрали інфраструктурну платформу, яка масштабується разом з продуктом. Той @deprecated тег досі в коді. Як нагадування.