Routing DSL : composer un panel de modèles qui pense comme Fable 5
Depuis deux ans, le plan d'action pour "plus d'intelligence" a été "attendre le prochain modèle." Nous pensons que c'est la mauvaise unité de progrès. La frontière n'est pas un seul point de contrôle — c'est un panel. Donnez trois bons modèles le même problème difficile, laissez-les être en désaccord, et arbitrez entre les réponses, et le panel bat n'importe lequel de ses membres. Souvent, il bat le modèle suivant sur le graphique des prix.
Le Routing DSL est comment vous construisez ce panneau. C'est une stratégie de routage programmable — YAML + CEL — qui transforme votre endpoint OrcaRouter en un graphe d'inférence : routez par difficulté, routez par tâche, déployez vers plusieurs modèles à la fois, jugez ou votez sur leurs sorties, repliez-vous lorsque la confiance est faible, et ajustez le tout pour le coût, la latence ou la qualité. Vous écrivez des règles ; la passerelle les compile et les exécute sur chaque requête en ~5 ms.
Ce post est le tour d'ingénierie : la grammaire, les variables sur lesquelles vous pouvez bifurquer, les quatre arbitres, la cascade, et un ensemble complet de règles de production à la fin.
Le résultat d'abord
Deux benchmarks illustratifs. (Les chiffres sont illustratifs — ils sont destinés à montrer la forme de l'effet, pas à être cités comme scores officiels.)
Comparaison de frontières — un point de terminaison DSL routé par difficulté vs. la frontière solo :

Panneaux Fusion vs modèles solo — noté sur 93 tâches sur 100 (via OpenRouter) :

Trois choses qui valent le coup d’être regardées :
Chaque panneau de fusion bat chacun de ses propres membres. Opus 4.8 + GPT-5.5 (~67,5 %) surpasse à la fois Opus solo (~58,5 %) et GPT-5.5 solo (~60 %) de 7 à 9 points. Le désaccord est un signal ; l'arbitrage le récolte.
Fusion atteint le niveau supérieur. Trois panneaux différents croisent Fable 5 solo (~65.5%) en utilisant seulement les modèles inférieurs à lui.
Vous n'avez pas besoin de membres coûteux. Opus + Opus auto-fusion (~65.5%) égale Fable 5 avec un seul modèle et un échantillonneur. Un panel de peu coûteux modèles — Gemini 3 Flash + Kimi K2.6 + DeepSeek V4 Pro (~64.5%) — se place juste en dessous de Fable 5 pour une fraction du coût par jeton. C'est toute la thèse : achetez de l'intelligence avec la topologie, pas avec le prochain palier de prix.
Le DSL de routage est la surface de contrôle qui vous permet de dépenser cette topologie uniquement là où cela rapporte — des modèles bon marché sur les 80 % faciles, un panneau de fusion sur la partie difficile.
La grammaire en 30 secondes
Un ensemble de règles est version, une liste de règles, et une valeur par défaut requise. Les règles sont évaluées de haut en bas ; la première condition when: qui est vraie gagne. Pas de when: signifie « toujours correspondre ».
version : 1
rules:
- id: only_rule
use: { model: "claude-sonnet-4-6" }
default:
delegate: balancedLe when: est une CELexpression booléenne — sandboxée, regex RE2 uniquement, pas de boucles, pas d'E/S, évaluation en microsecondes, avec un délai unique de 5 ms partagé sur l'ensemble du jeu de règles. Le use: est l'effet : où la requête va et comment elle est réglée. Les limites sont délibérément petites (≤30 règles, ≤16 Ko de source, ≤200 caractères par when:) afin qu'un jeu de règles reste vérifiable.
Primitive 1 — itinéraire par difficulté et tâche
Le distributeur classe chaque requête avant le routage et expose les fonctionnalités à CEL. Vous branchez directement sur elles :
version : 1
rules:
- id: hard_reasoning
when: difficulty > 0.8
use:
model: "claude-opus-4-8"
reasoning_effort: "high"
thinking_budget_tokens: 32000
- id: code_path
when: task_class == "code" && code_keyword_density > 0.5
use: { model: "gpt-5.5" }
- id: cheap_chat
when: difficulty < 0.3
use: { model: "gemini-3-flash" }
default:
delegate: balancedLes variables que vous pouvez lire dans when: (abrégé — voir la référence complète dans la documentation) :
Exemples de groupe
Forme de la requête
request.input_tokens, request.output_max_tokens, request.stream, request.vision, request.message_count, request.has_toolsClassification
task_class (chat/code/agent/vision/audio/rag/creative), difficulty (0.0–1.0), code_keyword_density, reasoning_cue_count, log_prompt_tokens, tool_countSession
agent_state.turn, agent_state.tools_used, agent_state.has_edited, agent_state.last_test_failed, agent_state.consecutive_errors, agent_state.models_triedContexte
headers["x-…"], user.group, token.name, time.hour, workspace.id
…plus six macros for the things regex-over-payload is good at: system_prompt_matches(re), user_message_matches(re), tool_definitions_include(name), tool_calls_present_any([…]), tool_results_from_any([…]), header_matches(name, re).N'importe quelle destination peut comporter paramètres par appel, traduits en paramètres natifs de chaque fournisseur par l'adaptateur relais : reasoning_effort (faible/moyen/élevé), thinking_budget_tokens (1024–64000), samples (1–16), temperature (0.0–2.0), plus param_override / header_override protégés par liste de blocage. Cela suffit déjà pour construire le point de terminaison routé par difficulté de la Table A : modèle bon marché sur la queue facile, Opus avec un budget de réflexion sur le difficile.
Primitive 2 — déploiement en éventail vers un panneau (fusion)
C'est là que provient l'augmentation de performance. Un parallèle : l'effet envoie la requête vers 2 à 5 legs en même temps, puis un arbitre décide ce que le client voit réellement :
- id: hard_tail_panel
when: difficulty > 0.7 && task_class == "agent"
use:
parallel:
- { model: "anthropic/claude-opus-4-8", reasoning_effort: "high" }
- { model: "openai/gpt-5.5", thinking_budget_tokens: 16000 }
- { model: "google/gemini-3.1-pro", temperature: 0.3 }
arbiter:
strategy: best_of_n
model: "anthropic/claude-sonnet-4-6" # the judge
template: judge_code
max_latency_ms: 120000
on_disagreement: # majority-only escape hatch
model: "anthropic/claude-opus-4-8"
reasoning_effort: "high"Quatre stratégies d'arbitre, chacune apportant une réponse différente à la question « quel résultat gagne ? » :
premier — faites courir les jambes, servez le premier succès, annulez les perdants. Optimise latence (vous obtenez le plus rapide de N).
majorité — vote structuré sur les sorties des branches, sans appel de modèle supplémentaire. Lorsque les branches se divisent sans majorité stricte, la branche optionnelle on_disagreement: relance une tentative fraîche et plus forte au lieu de servir un départage. Optimise robustesse sur des tâches avec une réponse canonique.
meilleur_des_n — un juge LLM lit tous les candidats et les classe. C'est la configuration Opus + GPT-5.5 → juge de la Table B. Optimise qualité sur du travail ouvert ; revient au premier réussi si le juge commet une erreur.
tests_pass — ancré dans l'exécution: servir le candidat dont le patch fait réellement passer la suite de tests. Pas de supposition du juge — c'est le harnais qui décide. C'est l'arbitre le plus fort pour le travail de code/agent. Le vérificateur vit en dehors de la passerelle (connecté via un VerifierProvider) ; sans aucun connecté, il dégrade à first-successful.
max_latency_ms (1000–600000, valeur par défaut 120000) limite le fan-out afin qu'une branche lente ne puisse pas bloquer la réponse — les retardataires sont abandonnés. L'imbrication d'un parallel dans un autre parallel est rejetée lors du lint ; le panneau est intentionnellement limité à un seul niveau de profondeur.
Note de disponibilité : le runtime de fan-out N-way est conditionné par le flag serveur ROUTING_DSL_ENSEMBLE_RUNTIME tandis que la facturation per-leg est durcie sur staging — c'est pourquoi fusion est preview, not GA. Avec le flag désactivé, une règle parallel: sert proprement sa première jambe, vous pouvez donc créer et shadow vos panels aujourd'hui et les activer lorsque fusion sera disponible dans votre région.
Primitive 3 — solutions de repli et cascades de confiance
Fan-out dépense N× à l'avance. Une cascade dépense supplémentaire seulement quand la première réponse semble erronée. Après la réponse, on_low_confidence: évalue les signaux et, si l'un se déclenche, ré-expédie vers une destination plus forte:
- id: agent_with_safety_net
when: task_class == "agent"
use:
pool: "@pool:fast"
on_low_confidence:
signals: [patch_invalid, self_doubt, next_turn_test_failed]
threshold: { low_logprob: -1.5 }
use:
model: "claude-opus-4-8"
reasoning_effort: "high"Les signaux : patch_invalid (le diff échoue à git apply --check), self_doubt (un ensemble d'expressions de prudence regex), low_logprob (moyenne du logprob des tokens sous le seuil, là où le fournisseur l'expose), et next_turn_test_failed (un verrou cross-turn — le prompt de ce tour porte la forme des tests échoués du tour précédent). Les cascades sont de profondeur 1 par conception. Associez-les à agent_state.models_tried pour obtenir diversity on retry — n'envoyez jamais la réparation au modèle qui vient d'échouer.
Réglage du cadran : coût, latence, qualité
Le même DSL exprime les trois objectifs ; vous choisissez par règle :
Coût — délégation : le moins cher, garder le modèle bon marché sur la queue facile, et réserver le fan-out pour les difficultés > 0,7. Le panel bon marché du Tableau B (~64,5 % ≈ Fable 5 solo) est la preuve concrète : une fusion de petits modèles peut remplacer un modèle de frontière à une fraction du coût par jeton. Soyez lucide, cependant — la fusion utilise le "facturer chaque jambe" modèle : un panel best_of_n à 3 jambes facture trois candidats plus le juge. L'économie fonctionne parce que vous (a) ne faites du fan-out que sur la minorité difficile des requêtes et (b) fusionnez moins chers que le modèle de frontière que vous remplacez.
Latence — arbitre : { strategy: first } plus un max_latency_ms strict vous donne le plus rapide de N avec un plafond dur.
Qualité — best_of_n pour un travail ouvert, tests_pass quand il y a une suite de tests sur laquelle se baser. samples et thinking_budget_tokens permettent d'obtenir plus dans une seule étape.
L'utiliser sans casser la prod
Les changements de routage sont effrayants, donc le DSL est livré avec les garde-fous qu'un SRE attend :
Lint à chaque sauvegarde — schéma, vérification de type CEL (chaque when: doit s'évaluer en bool), résolution de références, plages de réglage, listes de refus d'en-têtes/paramètres. Les erreurs reviennent sous forme de {ligne, colonne, message, règle} et s'affichent sous forme de pastilles dans la gouttière de l'éditeur.
Simulation — POSTer une requête synthétique (task_class, difficulty, agent_state, …) et obtenir la règle correspondante, l'effet résolu, et le temps d'évaluation avant que quoi que ce soit ne soit envoyé.
Mode fantôme — pendant 24 h après la première sauvegarde, la DSL est évaluée mais pas utilisée ; un journal fantôme enregistre les sélections potentielles et la console affiche une différence (pourcentage de routes modifiées, variation de coût quotidien projetée, nombre d'activations par règle).
Canary — un curseur de trafic de 0 à 100. Augmentez progressivement 5 → 25 → 50 → 100 en surveillant les métriques par tranche ; revenez en arrière en ramenant le curseur à 0.
Audit + rollback — chaque sauvegarde/rollback écrit une ligne d'audit dans la même transaction ; les modifications concurrentes reçoivent un 409 avec la version actuelle afin que vous puissiez réessayer avec un état à jour.
Des cas de test, une relecture de traces et une vue IA « expliquer ce jeu de règles » complètent le tout. Vous le trouvez dans le tableau de bord sous routage → stratégie → DSL.
Un ensemble de règles complet
Pas cher sur facile, moyen sur moyen, un panel de fusion jugé sur la queue agentique dure, avec une cascade de confiance en dessous :
version : 1
rules:
- id: trivial
when: difficulty < 0.3 && !has_tools
use: { model: "gemini-3-flash" }
- id: standard
when: difficulty < 0.7
use:
model: "gpt-5.5"
on_low_confidence:
signals: [self_doubt, low_logprob]
use: { model: "claude-opus-4-8", reasoning_effort: "high" }
- id: hard_agent_panel
when: difficulty >= 0.7 && task_class == "agent"
use:
parallel:
- { model: "anthropic/claude-opus-4-8", reasoning_effort: "high" }
- { model: "openai/gpt-5.5", thinking_budget_tokens: 16000 }
- { model: "google/gemini-3.1-pro" }
arbiter:
strategy: tests_pass # execution-grounded; judged fallback if no harness
max_latency_ms: 180000
on_disagreement:
model: "claude-opus-4-8"
reasoning_effort: "high"
default:
delegate: balancedCe point de terminaison est celui qui se trouve en haut de la Table A — non pas parce qu’il a trouvé un meilleur modèle, mais parce qu’il dépense le bon modèle pour la bonne requête et fusionne un panneau exactement là où le panneau gagne.
Commencer à composer
Le prochain bond en capacité ne doit pas attendre le prochain point de contrôle. C'est un graphe que vous pouvez écrire cet après-midi : itinéraire par difficulté, élargir sur la queue dure, juger ou tester les sorties, cascader lorsque la confiance baisse.
Documentation : https://docs.orcarouter.ai/routing/routing-dsl
UI: routage → Créer un routeur -> Stratégie de routage → DSL (expert)
La frontière est un panneau. Allez construire le vôtre.
