Combos : basculement et équilibrage de charge
Un combo est un modèle virtuel placé devant une liste ordonnée de cibles fournisseur/modèle réelles. Le client
demande combo/<id> ; opencodex choisit une cible, réécrit la requête vers le sélecteur concret
provider/model et peut essayer une autre cible si la première rencontre un échec autorisant une nouvelle tentative.
Ceci est utile lorsque vous souhaitez :
- Basculement : privilégier un modèle tout en gardant des solutions de repli disponibles.
- Équilibrage de charge : répartir les requêtes réussies entre plusieurs modèles ou fournisseurs par lots pondérés.
Les combos interviennent en amont du routage normal des fournisseurs. Consultez d’abord Routage des modèles
si vous ne connaissez pas encore les sélecteurs provider/model.
Démarrage rapide en 60 secondes
Section intitulée « Démarrage rapide en 60 secondes »Cet exemple crée combo/main, avec Anthropic en premier et OpenAI en second. Les deux fournisseurs doivent
déjà exister et être activés.
ocx combo set main --targets anthropic/claude-opus-4-8,openai/gpt-5.6-solLa stratégie par défaut est le basculement, donc une requête normale est envoyée à
anthropic/claude-opus-4-8. Si cette tentative rencontre un échec autorisant une nouvelle tentative, opencodex peut basculer vers
openai/gpt-5.6-sol.
Utilisez le modèle virtuel partout où vous fourniriez normalement un identifiant de modèle :
{ "model": "combo/main", "input": "Explain why the sky looks blue."}Confirmez la définition enregistrée :
ocx combo show mainComment fonctionnent les noms de combos
Section intitulée « Comment fonctionnent les noms de combos »L’identifiant du combo dans ocx combo set <id> doit commencer par une lettre ou un chiffre. Il peut alors contenir
lettres, chiffres, ., _ ou -, jusqu’à 64 caractères au total. Son identifiant de modèle canonique est toujours
combo/<id> ; par exemple, id main devient combo/main.
L’espace de noms combo/ est réservé pendant la configuration des combos. Un fournisseur nommé combo ne peut pas
l’occuper, et un identifiant combo ne peut pas dupliquer un nom de fournisseur configuré.
Un alias facultatif donne au combo un nom de modèle public différent. Un pseudonyme :
- utilise les mêmes caractères qu’un identifiant ;
- peut être nu, comme
daily-fast, ou contenir un/, commeteam/daily-fast; - ne peut pas être
comboni commencer parcombo/; - ne peut pas dupliquer un autre alias de combo ; et
- ne peut normalement pas être un simple nom de famille OpenAI natif commençant par
gpt-,o1-,o3-,o4-, oucodex-. Le mode de compatibilité explicite du bureau ci-dessous est la seule exception.
Même lorsqu’un alias est défini, la forme canonique combo/<id> est toujours résolue. Exécutions de recherche canonique
avant la correspondance d’alias, donc un alias ne peut pas reprendre l’identifiant canonique d’un autre combo.
Compatibilité avec la liste d’autorisation native de Codex Desktop
Section intitulée « Compatibilité avec la liste d’autorisation native de Codex Desktop »Certaines versions de Codex Desktop appliquent une liste d’autorisation available_models réservée aux modèles natifs après que
le serveur d’application a déjà chargé model_catalog_json. Des identifiants routés normaux tels que
Nova1/codex-gpt-5.6-sol restent alors utilisables dans la CLI, mais sont absents du sélecteur Desktop. Il s’agit du
bogue de Codex Desktop en amont, suivi dans
opencodex #241.
Lorsque vous contrôlez une cible routée équivalente, un combo peut explicitement reprendre un identifiant natif :
ocx combo set nova-sol \ --targets Nova1/codex/gpt-5.6-sol \ --alias gpt-5.6-sol \ --native-alias \ --display-name 'Nova1 - codex-gpt-5.6-sol'Ce mode est délibérément activation explicite et nécessite à la fois --native-alias et une étiquette d’affichage non vide.
L’alias doit correspondre à l’un des identifiants de modèle natifs pris en charge par cette version ; un simple préfixe de
famille native n’est pas accepté, car la suppression doit pouvoir restaurer des métadonnées faisant autorité.
Lorsque la réponse de découverte de la cible routée ne fournit qu’un identifiant de modèle, la ligne de compatibilité complète
les métadonnées manquantes de contexte, de modalité et de raisonnement à partir de l’identifiant natif remplacé. Les limites explicites
de la cible restent prioritaires : ce mécanisme de repli n’augmente jamais un plafond de contexte et ne remplace aucune capacité déclarée.
Cela modifie la priorité de routage exacte : les demandes de gpt-5.6-sol se résolvent en combo/nova-sol avant
la route canonique de la famille native OpenAI. Le catalogue contient une seule ligne non qualifiée portant le libellé
d’affichage configuré, et non deux lignes, native et combo. Seul l’identifiant non qualifié gpt-5.6-sol est intercepté.
Lignes qualifiées par le compte telles que main/gpt-5.6-sol et lignes qualifiées par le fournisseur telles que
openai-apikey/gpt-5.6-sol restent des itinéraires OpenAI distincts ; l’itinéraire clé API qualifié par le fournisseur
ne tombe jamais dans l’alias natif.
Les clés de visibilité restent sans ambiguïté :
combo/nova-solmasque le combo de compatibilité de la découverte.- L’entrée
gpt-5.6-solnue dansdisabledModelscontinue de signifier la ligne OpenAI native dormante ; cela ne masque pas le combo qui détient actuellement cet identifiant public. - Lorsqu’au moins un alias natif est configuré, les lignes natives nues désactivées sont omises du
catalogue Codex effectif au lieu d’être conservées avec
visibility: "hide". La liste d’autorisation de Desktop ne peut donc pas faire réapparaître des lignes qui devraient rester masquées. La page Modèles continue d’afficher les commutateurs natifs non masqués ; réactiver l’un d’eux restaure ses métadonnées natives préservées ou actuelles.
Choisissez une stratégie
Section intitulée « Choisissez une stratégie »Basculement : commande principale et sauvegardes
Section intitulée « Basculement : commande principale et sauvegardes »failover sélectionne la première cible éligible dans l’ordre de configuration. Une cible est éligible lorsque son
fournisseur existe, est activé, n’est pas en période de refroidissement et peut gérer toute contrainte particulière de la demande.
Les poids et stickyLimit n’affectent pas cette stratégie.
Compte tenu de cet ordre :
anthropic/claude-opus-4-8openai/gpt-5.6-solgoogle/gemini-3-pro
chaque requête commence par Anthropic. Un échec d’Anthropic autorisant une nouvelle tentative fait basculer la requête vers OpenAI ; un échec similaire d’OpenAI peut la faire basculer vers Google. Une erreur terminale interrompt immédiatement le traitement au lieu de essayer les cibles restantes.
Round-robin : lots pondérés lisses
Section intitulée « Round-robin : lots pondérés lisses »round-robin utilise un round-robin pondéré en douceur. Un poids cible plus grand donne à cet objectif un plus grand
partager au fil du temps sans envoyer la totalité de sa part en un seul long bloc. stickyLimit contrôle combien
les demandes réussies restent sur la cible sélectionnée avant la prochaine sélection pondérée.
Créez un combo 2:1 avec des lots de deux requêtes réussies :
ocx combo set balanced \ --targets anthropic/claude-opus-4-8:2,openai/gpt-5.6-sol:1 \ --strategy round-robin \ --sticky 2Appelant les cibles A (poids 2) et B (poids 1), les six premières sélections pondérées sont
A, B, A, A, B, A. Parce que stickyLimit est 2,, chaque sélection reste active pendant deux
demandes :
| Demande réussie | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | | — | — | — | — | — | — | — | — | — | — | — | — | — | — | | Cible | A | A | B | B | A | A | A | A | B | B | A | A |
À long terme, la répartition reste de 2:1. Un échec autorisant une nouvelle tentative met fin au lot persistant en cours et place la cible en période de refroidissement. cible et sélectionne une autre cible éligible pour la même demande.
random : tirage pondéré à chaque requête
Section intitulée « random : tirage pondéré à chaque requête »random tire une cible éligible par requête, avec une probabilité proportionnelle à weight. Chaque requête
constitue un tirage indépendant, de sorte que le trafic se répartit entre les cibles sans le schéma déterministe ni
la persistance de round-robin. stickyLimit n’affecte pas cette stratégie.
least-used : privilégier la cible ayant le moins de réussites
Section intitulée « least-used : privilégier la cible ayant le moins de réussites »least-used achemine chaque requête vers la cible éligible pour laquelle ce processus opencodex a enregistré le moins
de requêtes réussies. Les compteurs repartent de zéro au redémarrage et, en cas d’égalité, l’ordre de configuration est
conservé. weight et stickyLimit n’affectent pas cette stratégie.
reset-window : suivre la réinitialisation de quota la plus proche
Section intitulée « reset-window : suivre la réinitialisation de quota la plus proche »reset-window achemine chaque requête vers la cible éligible dont l’instantané mis en cache du quota du fournisseur
indique la réinitialisation de fenêtre à venir la plus proche (cinq heures, hebdomadaire, mensuelle ou personnalisée).
Le fournisseur dont le quota se renouvelle en premier est ainsi sollicité. Les cibles dépourvues de données de quota
récentes et les égalités conservent l’ordre de configuration. weight et stickyLimit n’affectent pas cette stratégie.
Ce classement et l’exclusion des fournisseurs avant l’envoi exigent des limites récentes d’inférence de modèles applicables dans leur ensemble à l’unique clé API actuelle. Les résumés OAuth ou du compte courant, les routes transmettant les identifiants de l’appelant, les configurations à plusieurs clés et les instantanés dont les identifiants ou la destination ont changé servent uniquement à l’affichage pour cette décision préalable. Il en va de même lorsque les en-têtes Authorization, x-api-key ou x-goog-api-key remplacent les identifiants ; les fenêtres réservées à la recherche ou à MCP sont exclues. Si aucune cible admissible n’a de réinitialisation applicable, l’ordre de configuration prévaut. La sélection des comptes et les nouvelles tentatives appliquent toujours leurs limites habituelles.
Que se passe-t-il lorsqu’une cible échoue
Section intitulée « Que se passe-t-il lorsqu’une cible échoue »Les échecs d’un combo se répartissent entre ceux qui entraînent un basculement et les échecs terminaux.
| Résultat | Comportement |
|---|---|
| HTTP 401, 403, 404, 408, 429, ou n’importe quel 5xx | Refroidissez la cible et passez à la prochaine cible éligible. |
| Erreur classée comme erreur d’authentification, d’abonnement, de quota, de limitation de débit, de surcharge ou de serveur en amont | Place la cible en période de refroidissement et bascule, même si le statut seul ne suffit pas. |
Annulation client (499), origin_rejected, refus de cyber-politique, débordement de contexte ou autre demande invalide |
Arrêtez et renvoyez l’erreur ; une autre cible ne rendrait pas la demande valide. |
Rejet structuré de user, valeur non prise en charge pour reasoning.effort/reasoning_effort, ou rejet d’entrée d’image propre à un modèle (param: input) |
Bascule vers la cible admissible suivante avant le début de la sortie, sans délai de refroidissement ; voir Compatibilité des paramètres facultatifs ci-dessous. |
| Toute autre erreur non classifiée | Arrêtez et renvoyez l’erreur. |
Une cible sautée entre en temps de recharge pendant 60 secondes par défaut. Si la réponse en amont inclut un
valeur Retry-After valide, opencodex l’utilise à la place. Les secondes numériques et les valeurs de date HTTP sont
accepté, et chaque temps de recharge est limité à 10 minutes.
La requête actuelle ne réessaye jamais la même cible tentée. Les demandes ultérieures l’ignorent jusqu’à ce qu’il soit
le temps de recharge expire. S’il ne reste aucune cible éligible, le proxy renvoie HTTP 503 avec
error.code = "combo_unavailable".
Effort de raisonnement par défaut
Section intitulée « Effort de raisonnement par défaut »defaultEffort complète un reasoning.effort absent si le combo possède une valeur par défaut non nulle et si la liste des niveaux acceptés par la cible est connue et non vide. La valeur configurée est conservée si elle est acceptée ; sinon, le niveau accepté le plus élevé ne la dépassant pas est choisi, ou le niveau le plus bas si aucun n’est inférieur. Une liste inconnue ou vide n’ajoute aucune valeur par défaut.
Cette étape conserve un effort existant et les autres champs reasoning. La normalisation des capacités ci-dessous peut supprimer séparément les paramètres effort/thinking non acceptés. Valeurs possibles : low, medium, high, xhigh, max, ultra ; l’absence du champ ou null désactive l’ajout.
Capacités reasoning mixtes
Section intitulée « Capacités reasoning mixtes »reasoningEffortMode vaut "strict" par défaut : le catalogue publie l’intersection des listes effort de toutes les cibles, y compris les listes explicitement vides. "adaptive" exclut ces listes vides pour conserver le sélecteur dans un combo mixte. Une liste inconnue ne limite l’intersection dans aucun des deux modes.
À l’envoi, une liste explicitement vide supprime les paramètres effort et thinking dans les deux modes ; une liste inconnue les supprime uniquement en adaptive. reasoning.summary et les autres champs hors effort sont conservés. La résolution des cibles connues non vides reste inchangée. Les cibles inconnues en strict et les déclarations inconnues du native Chat ordinaire conservent les paramètres de l’appelant. L’ajout d’une valeur par défaut ne remplace pas un effort existant, mais cette normalisation peut supprimer les paramètres non pris en charge.
Capacité d’entrée d’images / multimodale
Section intitulée « Capacité d’entrée d’images / multimodale »Par défaut, une combinaison publie l’intersection des modalités d’entrée de ses cibles : les images ne
sont activées que lorsque toutes les cibles les annoncent. Définissez imageInput: "disabled" pour forcer
le texte seul même si toutes les cibles prennent en charge les images. Le catalogue retire alors image
de inputModalities, et les requêtes contenant des images sont rejetées avec le code HTTP 400 avant tout
appel de cible. La valeur "auto", ou l’absence du champ, conserve l’intersection automatique.
Tâches du sous-agent v2 chiffrées
Section intitulée « Tâches du sous-agent v2 chiffrées »Il existe une limitation importante pour les sous-agents Codex v2 (issue #92). Un parent natif peut envoyer la tâche d’un travailleur nouvellement généré uniquement sous forme de texte chiffré créé pour le natif. ChatGPT back-end. Un fournisseur externe ne peut pas lire cette charge utile.
Pour une telle requête, un combo filtre ses cibles éligibles sur les routes ChatGPT natives canoniques, y compris après un échec autorisant une nouvelle tentative. Si le combo ne comporte aucune cible capable de déchiffrer la tâche, opencodex s’arrête avant expédition et retours HTTP 400 :
{ "error": { "type": "invalid_request_error", "code": "unreadable_encrypted_agent_task" }}Cela empêche la tâche d’être envoyée à un fournisseur qui ne recevrait aucune instruction lisible. Les tâches en texte clair lisibles utilisent la stratégie combo normale.
Vous disposez de quatre options de récupération :
- Sélectionnez un modèle ChatGPT natif pour l’enfant.
- Ajoutez une cible ChatGPT native canonique au combo.
- Utilisez la surface v1 pour la délégation entre différents fournisseurs.
- Si vous contrôlez l’appelant, renvoyez la tâche sous forme de contenu en texte brut v2
agent_message.
Voir Surface sous-agent pour les modes v1/base/v2 et le chiffrement complet flux de travail des tâches.
Gérer les combos
Section intitulée « Gérer les combos »Tableau de bord
Section intitulée « Tableau de bord »Ouvrez le tableau de bord local et choisissez Modèles → Combos. L’espace de travail crée, modifie, renomme et supprime combos, et son sélecteur de cible exclut les modèles désactivés et les combos imbriqués.
Chaque cible affiche aussi un badge de quota en direct : Disponible, Quota épuisé ou Quota inconnu. L’éditeur bloque Enregistrer et Créer pour une raison de quota uniquement lorsque chaque cible utilisable dispose d’une confirmation serveur encore valide indiquant que la limite d’inférence liée à ses identifiants configurés est épuisée. Les quotas de compte, de modèle, de recherche et de MCP fournis uniquement à titre d’affichage, ainsi que les informations de routage absentes ou expirées, ne déclenchent pas ce blocage. Le blocage expire à la réinitialisation applicable ou à l’expiration de la validité des données et fait l’objet d’une nouvelle vérification lorsque la page devient active ou visible ; Actualiser recharge à la fois les données des combos et les quotas.
Les commandes principales sont :
ocx combo listocx combo show <id>ocx combo set <id> --targets provider/model[:weight],...ocx combo remove <id> --yesset accepte également --strategy, --sticky, --effort, --alias, --native-alias,
--display-name et --rename-from. Utilisez - comme valeur de --effort, --alias ou
--display-name pour effacer ce champ. --native-alias exige un alias de modèle natif non qualifié actuellement
pris en charge et un nom d’affichage non vide. create et update sont des alias pour set ; delete est un alias pour
remove ; et les mêmes sous-commandes sont disponibles sous ocx route combo.
Gestion API
Section intitulée « Gestion API »Les clients sans interface utilisent GET, PUT et DELETE sur /api/combos. GET liste les définitions
normalisées, PUT en crée ou en remplace une et peut en renommer une, tandis que DELETE utilise le paramètre
de requête id. L’authentification et les détails des requêtes et réponses se trouvent dans la
Gestion API référence.
Pour la configuration persistante complète, voir Configuration.
Référence de configuration
Section intitulée « Référence de configuration »Les combos sont stockés dans l’objet combos de niveau supérieur, saisi par l’identifiant du 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" } }}| Champ | Obligatoire | Par défaut | Règles |
|---|---|---|---|
targets |
Oui | — | Tableau ordonné non vide de { provider, model, weight? } cibles configurées. Les paires provider/model en double sont rejetées. |
targets[].weight |
Non | 1 |
Entier de 1 à 10 000. Utilisé par round-robin et random ; ignoré par failover, least-used et reset-window. |
strategy |
Non | "failover" |
Valeurs autorisées : "failover", "round-robin", "random", "least-used" et "reset-window". |
stickyLimit |
Non | 1 |
Nombre entier de 1 à 100 requêtes réussies par sélection à tour de rôle. S’applique uniquement à round-robin. |
defaultEffort |
Non | null |
low, medium, high, xhigh, max ou ultra ; appliqué uniquement lorsque l’appelant omet ses efforts et que la cible annonce son soutien. |
reasoningEffortMode |
Non | "strict" |
strict ou adaptive ; choisit l’intersection des capacités et la normalisation par cible. |
imageInput |
Non | "auto" |
"auto" ou "disabled". "auto" publie les images uniquement si toutes les cibles les prennent en charge ; "disabled" impose le texte seul, retire les images des modalités publiées et rejette les requêtes qui en contiennent avant leur distribution. |
alias |
Non | aucun | Identifiant de modèle public tronqué facultatif ; utilisez les règles d’alias ci-dessus. Une valeur vide est stockée sans alias. |
nativeAlias |
Non | false |
Autoriser explicitement un alias natif nu actuellement pris en charge à avoir la priorité sur le routage et le catalogue. Jamais déduit de l’alias. |
displayName |
Non | aucun | Étiquette de catalogue délimitée en affichage uniquement. Obligatoire et non vide lorsque nativeAlias est vrai. |
Dépannage
Section intitulée « Dépannage »Pourquoi combo/<id> renvoie 404 ?
Section intitulée « Pourquoi combo/<id> renvoie 404 ? »L’identifiant du combo est inconnu. La réponse est HTTP 404 de type invalid_request_error. Courir
ocx combo list, vérifiez l’orthographe et la casse, et confirmez que votre commande de gestion a écrit sur le même
exécution d’une instance opencodex qui reçoit des requêtes de modèle.
Pourquoi est-ce que je reçois combo_unavailable ?
Section intitulée « Pourquoi est-ce que je reçois combo_unavailable ? »Chaque cible est actuellement inéligible : par exemple, son fournisseur est désactivé, il est en phase de refroidissement,
elle a déjà été tentée pour cette requête, ou une tâche v2 chiffrée l’exclut. Vérifier la cible
état du fournisseur et erreurs récentes en amont. Pour les temps de recharge, attendez la valeur par défaut de 60 secondes ou la
délai indiqué par Retry-After en amont (jamais plus de 10 minutes), puis réessayez.
Pourquoi mon alias a-t-il été rejeté ?
Section intitulée « Pourquoi mon alias a-t-il été rejeté ? »Vérifiez d’abord la grammaire des alias et les noms réservés. Un alias en double ou une forme non valide est rejeté comme HTTP 400. Un alias barre oblique dont le premier segment est un espace de noms de compte Codex configuré est rejeté comme HTTP 409 ; choisissez un autre espace de noms d’alias. Le CLI et le tableau de bord affichent l’adresse exacte du serveur. message de validation.
Pourquoi le basculement s’est-il arrêté après la première erreur ?
Section intitulée « Pourquoi le basculement s’est-il arrêté après la première erreur ? »L’erreur était terminale plutôt que spécifique à la cible. Corriger une entrée invalide, réduire un contexte surdimensionné, gérer un refus de politique ou corriger l’origine de la demande rejetée. Les combos ne sautent pas dans ces cas-là.
Compatibilité des paramètres facultatifs
Section intitulée « Compatibilité des paramètres facultatifs »Exception aux erreurs 400 terminales : un rejet structuré de user, une valeur non prise en charge pour reasoning.effort/reasoning_effort, ou un rejet d’entrée d’image propre à un modèle (param: input) peut faire passer le combo à la cible admissible suivante avant le début de la sortie, sans délai de refroidissement. Le refus de sécurité, l’annulation et une sortie déjà commencée restent non rejouables.

