Как объединить личные AI подписки в единый пул

Представим ситуацию: вы - руководитель средней 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% remaining
Account B 70% remaining
Account 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 tomorrow
B: 79% left → reset in 4 days
C: 65% left → reset in 6 days
traffic
↓
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.
Все персонажи и события вымышлены, любые совпадения с реальными людьми или событиями случайны
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.