Представим ситуацию: вы — руководитель средней IT-команды. В наше время у каждого сотрудника, от технического писателя до архитектора, есть платная подписка на AI-инструменты. Так как это производственная необходимость, компания компенсирует затраты на личные подписки каждому члену команды.
Возьмём типичные ситуации для разных должностей:
-
Василий, технический писатель. Долгое время ему хватало подписки за 20$. Но то ли вендор лимиты втихую урезал, толи эффективность команды повысилась, но хватать ему 20$ подписки перестало. Хочет купить подписку за 100$, но понимает, что большая часть квоты у него всегда будет неиспользованной
-
Артём, QA тестировщик. У него подписка за 100$, но распределение потребления неравномерное. Когда существовали пятичасовые лимиты — было особенно тяжко. Перед релизом всё съедается в ноль, а в остальное время он едва ли 50% использует
-
Аня, Senior backend-разработчица. Обожает оставлять агентов на ночь в режиме цели (goal), работает много и активно. Подписки за 200$ перестало хватать. Пришлось покупать вторую. Но на практике она не используется большую часть времени. Расход квоты всегда в районе 25%

Кто-то может возразить: «Но ведь есть же официальные Business/Enterprise-тарифы! Зачем изобретать велосипед?». Проблема особенно актуальна для IT в России, когда внезапно оказывается, что велосипеды не продают российским юрлицам, а даже если продали, в любой момент могут превратить велосипед в тыкву.
В идеале наша архитектура должна выглядеть следующим образом: объединяем все подписки в единый пул, ставим перед ним балансировщик, выдаём ключи доступа каждому разработчику и начинаем расходовать пул подписок по мере необходимости. С каждого по способностям, каждому по потребностям, прямо как завещали в прекрасном светлом прошлом!
Оказывается, мы далеко не первые, кто додумался до такой архитектуры, и умные люди уже давно наклепали опенсорс-проектов похожей направленности. Самым популярным на данный момент является codex-lb, который набрал уже почти 3к звёзд на гитхабе.
Однако кроличья нора оказывается глубже, чем кажется на первый взгляд. Если присмотреться, то у такой архитектурной идеи есть множество нюансов, которые нужно учесть. Попробуем разобрать, что предлагает нам рассматриваемый выше проект.
Developer A ─┐Developer B ─┤Developer C ─┤... ├──► codex-lb ───► ChatGPT account #1 ($20)Developer O ─┘ │ ├─► ChatGPT account #2 ($100) │ ├─► ChatGPT account #3 ($200) │ ├─► ... │ └─► ChatGPT account #15 │ ├── routing ├── quotas ├── sticky sessions ├── API keys └── usage accounting
Для разработчика архитектура превращается в blackbox. У разработчика есть только ключ: он не выбирает чью подписку использовать, он не знает ничего про лимиты, текущее потребление и нагруженность. Оркестратор сам за него выбирает на какой аккаунт послать запрос, у кого осталось больше квоты, когда у какого аккаунт произойдет reset и какие модели доступны.
Для начала администратору необходимо добавить аккаунты в пул подписок. Делает он это через OAuth-инфраструктуру Codex/OpenAI и хранит access/refresh/id токены аккаунтов. Токены шифруются; в коде есть отдельный механизм обновления refresh-токенов и даже механизмы для горизонтального масштабирования.
Разработчики не получают прямого доступа к OAuth-аккаунтам, они имеют только внутренние ключи оркестратора. При этом, настройки достаточно гибкие, и можно внутри системы ограничивать доступные модели, лимиты по токенам, задать собственные дневные/недельные/месячные лимиты под каждого.
Хорошо, с сетапом разобрались. Но теперь возникает вопрос — а как внутри системы балансируются запросы между подписками? Механизм оказывается сложнее базового round robin. Нам предлагается на выбор несколько стратегий:
Capacity weighted
Account A 20% remainingAccount B 70% remainingAccount C 90% remaining ↓ больше traffic │ ┌──────┴──────┐ B C
При данном режиме выбор падает на тот аккаунт, где больше всего лимитов. При этом учитываются также и ограничения на ключ — выбираются именно те аккаунты, которые могут обслужить текущий запрос по доступности определенной модели/reasoning
Relative availability
Здесь мы пытаемся поймать баланс между доступностью и остатком квоты. Для каждого аккаунта считаем величину, равную:
остаток квоты / количество секунд до reset
В таком формате больший приоритет получают аккаунты, которые уже подходят к reset, но всё ещё имеют достаточный лимит:
Account Aосталось 40%reset через 2 дняAccount Bосталось 40%reset через 6 дней
Аккаунт A получает более высокий приоритет: его оставшуюся квоту нужно быстрее использовать, иначе она пропадёт при reset.
Для команды, где люди работают в разное время суток, это уже довольно умно.
Reset drain
Тут мы уже пытаемся выжать подписку на максимум. На вероятность выбора теперь влияют только секунды до reset, без учёта оставшейся квоты:
A: 46% left → reset tomorrowB: 79% left → reset in 4 daysC: 65% left → reset in 6 daystraffic ↓ A ↓после него B/C
Но это может периодически приводить к неприятным ситуациям, когда лимиты часто заканчиваются прямо в процессе работы.
А теперь перейдём к самому проблемному моменту всей архитектуры.
Пользователь работал через аккаунт А, разрабатывал какую-то фичу. Квота аккаунта А закончилась, фича еще не доделана. Как нам безопасно и эффективно продолжить на аккаунте Б и можно ли в принципе это сделать?
Для этого вводим два понятия: Sticky routing и Hard continuation.
Это не настройки-переключатели, а состояния, которые оркестратор определяет автоматически и от которых зависит, можем ли мы продолжить текущую сессию в другой подписке, или нет?
Возьмём для примера стандартный флоу работы с агентом
THREADВесь чат на несколько часов│├── TURN 1│ User: "Исследуй auth и исправь баг"│ ││ ├── inference #1│ ├── tool call│ ├── inference #2│ ├── tool call│ ├── inference #3│ └── Assistant: "Готово"│├── TURN 2│ User: "Теперь добавь тесты"│ ├── inference #1│ ├── ...│ └── Assistant: "Готово"│└── TURN 3 User: "А теперь отрефактори"
Особенности кодекса состоят в том, что между вызовами внутри одного turn он не передаёт каждый раз полную историю, вместо повторной передачи всего контекста используется продолжение через previous_response_id. Этот идентификатор ссылается на состояние, доступное в рамках соответствующего аккаунта. Между turns агента же, отправляется полная дельта текущего контекстного окна.
Зачем всё это было нужно? Чтобы понять простую вещь: если внутри одного turn закончился лимит, например после inference #2, то мы не сможем безопасно продолжить работу через другой аккаунт, так как находимся в процессе исполнения. То есть для типичного сценария: закончилась подписка А -> перенаправляем на подписку Б, такой кейс не подходит. И нам приходится либо создавать новую сессию, либо ждать, пока сбросятся лимиты. Это и есть Hard continuation.
Если же квота аккаунта A закончилась уже после завершения TURN 1., то мы спокойно передаём контекст через аккаунт Б и продолжаем работу там. Это как раз механизм Sticky routing
Подводя итог, можно отметить, что reset drain стратегия хоть и является наиболее экономически выгодной, тем не менее сильно уступает в удобстве для рядового разработчика.

Но тут все замечают слона в комнате. Официальной политикой OpenAI запрещена передача доступа к аккаунту третьим лицам.
Данная статья не является рекомендацией по обходу официальных правил или нарушению принятых Terms of Use и Account sharing policy.
Все персонажи и события вымышлены, любые совпадения с реальными людьми или событиями случайны
ссылка на оригинал статьи https://habr.com/ru/articles/1073270/