Почему мы отказались от 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 тег до сих пор в коде. Как напоминание.