Комбо: отказоустойчивость и балансировка нагрузки
Combo — это одна виртуальная модель, за которой стоит упорядоченный список реальных целей
provider/model. Клиент запрашивает combo/<id>, opencodex выбирает цель, переписывает запрос на
конкретный provider/model и при сбое, допускающем повтор, может попробовать следующую цель.
Это полезно, если вам нужно одно из двух:
- Failover: предпочитать одну модель, но держать резервные варианты наготове.
- Балансировка нагрузки: распределять успешные запросы между моделями или провайдерами взвешенными партиями.
Combo работают поверх обычной маршрутизации провайдеров. Если синтаксис provider/model для вас
новый, сначала прочитайте Маршрутизацию моделей.
Быстрый старт за 60 секунд
Заголовок раздела «Быстрый старт за 60 секунд»Этот пример создаёт combo/main, где первым идёт Anthropic, а вторым OpenAI. Оба провайдера уже
должны существовать и быть включены.
ocx combo set main --targets anthropic/claude-opus-4-8,openai/gpt-5.6-solСтратегия по умолчанию — failover, поэтому обычный запрос уйдёт в
anthropic/claude-opus-4-8. Если эта попытка завершится retryable-сбоем, opencodex сможет
переключиться на openai/gpt-5.6-sol.
Используйте виртуальную модель везде, где вы обычно передаёте id модели:
{ "model": "combo/main", "input": "Explain why the sky looks blue."}Проверьте сохранённое определение:
ocx combo show mainКак работают имена combo
Заголовок раздела «Как работают имена combo»Идентификатор combo в ocx combo set <id> должен начинаться с буквы или цифры. Дальше он может
содержать буквы, цифры, ., _ или -, общей длиной до 64 символов. Канонический id модели
всегда имеет вид combo/<id>; например, id main превращается в combo/main.
Пространство имён combo/ резервируется, пока настроены combo. Провайдер с именем combo не
может его занять, и id combo не может дублировать имя уже настроенного провайдера.
Необязательный alias даёт combo другое публичное имя модели. Alias:
- использует тот же набор символов, что и id;
- может быть без слэша, например
daily-fast, либо содержать один/, напримерteam/daily-fast; - не может быть
comboи не может начинаться сcombo/; - не может дублировать alias другой combo; и
- не может быть «голым» нативным именем семейства OpenAI, начинающимся с
gpt-,o1-,o3-,o4-илиcodex-.
Даже если alias задан, каноническая форма combo/<id> всё равно разрешается. Канонический поиск
выполняется раньше сопоставления alias, поэтому alias не может перехватить канонический id другой
combo.
Выбор стратегии
Заголовок раздела «Выбор стратегии»Failover: основной вариант и резерв в заданном порядке
Заголовок раздела «Failover: основной вариант и резерв в заданном порядке»failover выбирает первую подходящую цель в порядке конфигурации. Цель считается подходящей,
когда провайдер существует, включён, не находится в cooldown и может обработать специальные
ограничения запроса. Веса и stickyLimit на эту стратегию не влияют.
Если порядок такой:
anthropic/claude-opus-4-8openai/gpt-5.6-solgoogle/gemini-3-pro
то каждый запрос начинается с Anthropic. Retryable-сбой Anthropic переводит этот запрос на OpenAI; retryable-сбой OpenAI может перевести его на Google. Terminal-ошибка сразу останавливает обработку и не даёт пробовать оставшиеся цели.
Round-robin: сглаженные взвешенные партии
Заголовок раздела «Round-robin: сглаженные взвешенные партии»round-robin использует smooth weighted round-robin. Чем больше вес цели, тем большую долю она
получает со временем, но эта доля не отправляется одним длинным блоком. stickyLimit управляет
тем, сколько успешных запросов остаются на выбранной цели, прежде чем выполнится следующий
взвешенный выбор.
Создайте combo 2:1 с партиями по два успешных запроса:
ocx combo set balanced \ --targets anthropic/claude-opus-4-8:2,openai/gpt-5.6-sol:1 \ --strategy round-robin \ --sticky 2Если назвать цели A (вес 2) и B (вес 1), то первые шесть взвешенных выборов будут
A, B, A, A, B, A. Поскольку stickyLimit равен 2, каждый выбор удерживается на два успешных
запроса:
| Успешный запрос | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Цель | A | A | B | B | A | A | A | A | B | B | A | A |
В долгосрочном итоге доля всё равно будет 2:1. Retryable-сбой завершает текущую sticky-партию, переводит эту цель в cooldown и выбирает другую подходящую цель для того же запроса.
Что происходит, когда цель сбоит
Заголовок раздела «Что происходит, когда цель сбоит»Сбои в combo делятся на hop-сбои и terminal-сбои.
| Результат | Поведение |
|---|---|
| HTTP 401, 403, 404, 408, 429, или любой 5xx | Перевести цель в cooldown и перейти к следующей подходящей цели. |
| Классифицированная ошибка аутентификации, подписки, квоты, rate-limit, перегрузки или upstream-server | Перевести цель в cooldown и переключиться, даже если одного статуса недостаточно. |
Отмена клиентом (499), origin_rejected, отказ из-за cyber-policy, переполнение контекста или некорректный запрос |
Остановиться и вернуть ошибку; другая цель не сделает такой запрос корректным. |
| Любая другая неклассифицированная ошибка | Остановиться и вернуть ошибку. |
Цель, по которой произошёл hop, по умолчанию уходит в cooldown на 60 секунд. Если ответ upstream
содержит корректный Retry-After, opencodex использует его. Поддерживаются и числовые секунды, и
значения в формате HTTP-date; любой cooldown ограничивается 10 минутами.
Текущий запрос никогда не повторяет уже опробованную цель. Более поздние запросы пропускают её,
пока не истечёт cooldown. Если подходящих целей больше не осталось, прокси возвращает HTTP 503 с
error.code = "combo_unavailable".
Effort по умолчанию
Заголовок раздела «Effort по умолчанию»defaultEffort подставляет reasoning.effort только если одновременно выполняются все условия:
- у combo задано ненулевое значение по умолчанию;
- вызывающая сторона сама не указала effort; и
- каталог выбранной цели объявляет поддержку именно этого effort.
Если в запросе нет объекта reasoning, opencodex создаёт его. Если reasoning есть, но в нём нет
свойства effort, остальные поля сохраняются, а значение по умолчанию добавляется. Effort,
заданный вызывающей стороной, никогда не перезаписывается.
Если возможности цели неизвестны или не включают настроенный effort, opencodex опускает значение
по умолчанию и оставляет нативное поведение цели без изменений. Поддерживаются low, medium,
high, xhigh, max и ultra; опустите поле или задайте null, чтобы полностью оставить выбор
effort вызывающей стороне и цели.
Шифрованные задачи подагентов v2
Заголовок раздела «Шифрованные задачи подагентов v2»Есть одно важное ограничение для подагентов Codex v2 (issue #92). Нативный родитель может отправить задачу новому воркеру только как ciphertext, выпущенный для нативного backend ChatGPT. Внешний провайдер не может прочитать эту нагрузку.
Для такого запроса combo фильтрует подходящие цели до канонических нативных маршрутов ChatGPT, в том числе после retryable-сбоя. Если в combo нет цели, способной расшифровать задачу, opencodex останавливается ещё до dispatch и возвращает HTTP 400:
{ "error": { "type": "invalid_request_error", "code": "unreadable_encrypted_agent_task" }}Это защищает задачу от отправки провайдеру, который всё равно не получил бы читаемых инструкций. Обычные незашифрованные задачи используют стандартную стратегию combo.
Есть четыре варианта восстановления:
- Выбрать для потомка нативную модель ChatGPT.
- Добавить в combo каноническую нативную цель ChatGPT.
- Использовать поверхность v1 для делегирования между разными провайдерами.
- Если вы управляете вызывающей стороной, повторно отправить задачу как plaintext в содержимом
v2
agent_message.
Подробности о режимах v1/base/v2 и полном потоке encrypted task см. в Поверхности подагентов.
Управление combo
Заголовок раздела «Управление combo»Дашборд
Заголовок раздела «Дашборд»Откройте локальный дашборд и выберите Combos. Рабочая область умеет создавать, редактировать, переименовывать и удалять combo, а селектор целей исключает отключённые модели и вложенные combo.
Основные команды:
ocx combo listocx combo show <id>ocx combo set <id> --targets provider/model[:weight],...ocx combo remove <id> --yesset также принимает --strategy, --sticky, --effort, --alias и --rename-from. Чтобы
очистить поле, используйте - в качестве значения для --effort или --alias. create и
update — это alias для set; delete — alias для remove; те же подкоманды доступны и через
ocx route combo.
Management API
Заголовок раздела «Management API»Headless-клиенты используют GET, PUT и DELETE на /api/combos. GET возвращает список
нормализованных определений combo, PUT создаёт или заменяет одну combo (и умеет переименовывать),
а DELETE принимает id в query-параметре. Аутентификация и детали контрактов запросов/ответов
описаны в справочнике Management API.
Полную сохранённую конфигурацию см. в Конфигурации.
Справочник конфигурации
Заголовок раздела «Справочник конфигурации»Combo хранятся в объекте верхнего уровня combos, по ключу id combo:
{ "combos": { "balanced": { "targets": [ { "provider": "anthropic", "model": "claude-opus-4-8", "weight": 2 }, { "provider": "openai", "model": "gpt-5.6-sol", "weight": 1 } ], "strategy": "round-robin", "stickyLimit": 2, "defaultEffort": "high", "alias": "team/balanced" } }}| Поле | Обязательно | По умолчанию | Правила |
|---|---|---|---|
targets |
Yes | — | Непустой упорядоченный массив настроенных целей { provider, model, weight? }. Дубли пар provider/model запрещены. |
targets[].weight |
No | 1 |
Целое число от 1 до 10 000. Используется в round-robin; при failover игнорируется. |
strategy |
No | "failover" |
"failover" или "round-robin". |
stickyLimit |
No | 1 |
Целое число от 1 до 100 успешных запросов на один выбор round-robin. |
defaultEffort |
No | null |
low, medium, high, xhigh, max или ultra; применяется только когда вызывающая сторона не указала effort, а цель объявляет поддержку. |
alias |
No | none | Необязательный обрезанный публичный id модели; используйте правила alias выше. Пустое значение хранится как отсутствие alias. |
Устранение неполадок
Заголовок раздела «Устранение неполадок»Почему combo/<id> возвращает 404?
Заголовок раздела «Почему combo/<id> возвращает 404?»Id combo неизвестен. Ответ — HTTP 404 с типом invalid_request_error. Запустите ocx combo list,
проверьте написание и регистр, а также убедитесь, что management-команда записывала в тот же
запущенный экземпляр opencodex, который принимает запросы к моделям.
Почему я получаю combo_unavailable?
Заголовок раздела «Почему я получаю combo_unavailable?»Сейчас ни одна цель не подходит: например, провайдер отключён, находится в cooldown, уже был
испробован для этого запроса или исключён из-за шифрованной задачи v2. Проверьте состояние
провайдеров цели и недавние upstream-ошибки. Для cooldown подождите стандартные 60 секунд или
период из upstream Retry-After (но не более 10 минут), затем повторите запрос.
Почему alias был отклонён?
Заголовок раздела «Почему alias был отклонён?»Сначала проверьте грамматику alias и резервные имена. Дублирующийся alias или некорректная форма отклоняются как HTTP 400. Alias со слэшем, у которого первый сегмент совпадает с namespace уже настроенного аккаунта Codex, отклоняется как HTTP 409; выберите другое пространство имён alias. CLI и дашборд показывают точное сообщение валидации от сервера.
Почему failover остановился после первой ошибки?
Заголовок раздела «Почему failover остановился после первой ошибки?»Ошибка была terminal, а не специфичной для конкретной цели. Исправьте некорректный ввод, сократите слишком большой контекст, обработайте отказ политики или исправьте отклонённое происхождение запроса. В таких случаях combo не выполняют hop.

