Routing DSL: compor um painel de modelos que pensa como o Fable 5

Routing DSL: compor um painel de modelos que pensa como o Fable 5

Data de publicação

Voltar para todas as publicações

Por dois anos, o manual para 'mais inteligência' tem sido 'esperar pelo próximo modelo.' Achamos que essa é a unidade errada de progresso. A fronteira não é um único ponto de verificação — é um painel. Dê a três bons modelos o mesmo problema difícil, deixe-os discordar e arbitre entre as respostas, e o painel supera qualquer um de seus membros. Frequentemente, ele supera o próximo modelo na tabela de preços.

O DSL de Roteamento é como você constrói esse painel. É uma estratégia de roteamento programável — YAML + CEL — que transforma seu endpoint OrcaRouter em um grafo de inferência: rotear por dificuldade, rotear por tarefa, distribuir para vários modelos de uma vez, julgar ou votar em suas saídas, fazer fallback quando a confiança é baixa, e ajustar tudo para custo, latência ou qualidade. Você escreve regras; o gateway as compila e executa em cada requisição em ~5 ms.

Este post é o tour de engenharia: a gramática, as variáveis nas quais você pode ramificar, os quatro árbitros, a cascata e um conjunto completo de regras de produção no final.


O resultado primeiro

Dois benchmarks ilustrativos. (Os números são ilustrativos — destinam-se a mostrar a forma do efeito, não para serem citados como pontuações oficiais.)

Comparação do Frontier — um endpoint DSL roteado por dificuldade vs. a fronteira solo:


Painéis Fusion vs. modelos solo — avaliado em 93 de 100 tarefas (do OpenRouter):


Três coisas que valem a pena contemplar:

Todo painel de fusão supera cada um de seus próprios membros. Opus 4.8 + GPT-5.5 (~67.5%) supera tanto Opus solo (~58.5%) quanto GPT-5.5 solo (~60%) por 7 a 9 pontos. Desacordo é sinal; a arbitragem o colhe.

Fusion atinge o próximo nível. Três painéis diferentes cruzam Fable 5 solo (~65.5%) usando apenas os modelos abaixo dele.

Você não precisa de membros caros. Opus + Opus autofusão (~65.5%) corresponde ao Fable 5 com um modelo e um amostrador. Um painel de baratos modelos — Gemini 3 Flash + Kimi K2.6 + DeepSeek V4 Pro (~64.5%) — fica um pouco abaixo do Fable 5 por uma fração do custo por token. Essa é a tese: compre inteligência com topologia, não com o próximo nível de preço.

O Routing DSL é a superfície de controle que permite gastar essa topologia apenas onde compensa — modelos baratos nos fáceis 80%, um painel de fusão na cauda difícil.


A gramática em 30 segundos

Um conjunto de regras é versão, uma lista de regras, e um padrão obrigatório. As regras são avaliadas de cima para baixo; o primeiro quando: que é verdadeiro vence. Nenhum quando: significa "sempre corresponde".

versão: 1

rules:
  - id: only_rule
    use: { model: "claude-sonnet-4-6" }
default:
  delegate: balanced


O when: é uma CEL expressão booleana — isolado, regex somente RE2, sem loops, sem I/O, avaliação em microssegundos, com um único prazo de 5 ms compartilhado em todo o conjunto de regras. O use: é o efeito: para onde a solicitação vai e como ela é ajustada. Os limites são deliberadamente pequenos (≤30 regras, ≤16 KiB de código fonte, ≤200 caracteres por when:) para que um conjunto de regras permaneça auditável.


Primitivo 1 — rota por dificuldade e tarefa

O distribuidor classifica cada solicitação antes do roteamento e expõe as funcionalidades ao CEL. Você ramifica diretamente nelas:

versão: 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: balanced


As variáveis que você pode ler em when: (abreviado — veja a referência completa na documentação):

Exemplos de Grupo

Forma da solicitação

request.input_tokens, request.output_max_tokens, request.stream, request.vision, request.message_count, request.has_tools


Classificação

task_class (chat/code/agent/vision/audio/rag/creative), difficulty (0.0–1.0), code_keyword_density, reasoning_cue_count, log_prompt_tokens, tool_count


Sessão 

agent_state.turn, agent_state.tools_used, agent_state.has_edited, agent_state.last_test_failed, agent_state.consecutive_errors, agent_state.models_tried


Contexto

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).


Qualquer destino pode carregar botões por chamada, traduzidos para parâmetros nativos de cada provedor pelo adaptador de retransmissão: reasoning_effort (low/medium/high), thinking_budget_tokens (1024–64000), samples (1–16), temperature (0.0–2.0), além de param_override / header_override protegidos por lista de negação. Isso já é suficiente para construir o endpoint roteado por dificuldade da Tabela A: modelo barato na cauda fácil, Opus com um orçamento de pensamento na cauda difícil.


Primitivo 2 — distribuir em leque para um painel (fusão)

É daqui que vem o ganho do benchmark. Um efeito paralelo: despacha a requisição para 2–5 ramos simultaneamente, então um árbitro decide o que o cliente realmente vê:

- 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"


Quatro estratégias de árbitro, cada uma uma resposta diferente para "qual saída vence?":

primeiro — corra as pernas, sirva o primeiro sucesso, cancele os perdedores. Otimiza a latência (você obtém o mais rápido de N).

maioria — voto estruturado entre as saídas dos ramos, sem chamada extra de modelo. Quando os ramos divergem sem maioria estrita, o branch opcional on_disagreement: reenvia uma nova tentativa mais forte em vez de servir um desempate. Otimiza robustez em tarefas com uma resposta canônica.

best_of_n — um juiz LLM lê todos os candidatos e os classifica. Esta é a configuração Opus + GPT-5.5 → judge da Tabela B. Otimiza qualidade em trabalho aberto; retorna ao primeiro bem-sucedido se o juiz errar.

testes_passamexecução-fundamentada: atende o candidato cujo patch realmente faz a suíte de testes passar. Sem adivinhação de juiz — o harness decide. Este é o árbitro mais forte para trabalho de código/agente. O verificador vive fora do gateway (conectado via um VerifierProvider); sem nenhum conectado, ele degrada para o primeiro bem-sucedido.

max_latency_ms (1000–600000, padrão 120000) limita o fan-out para que uma perna lenta não possa travar a resposta — os retardatários são descartados. Aninhar parallel dentro de parallel é rejeitado no lint; o painel é intencionalmente de um nível de profundidade.

Nota de disponibilidade: o runtime de fan-out N-way é controlado pela flag do servidor ROUTING_DSL_ENSEMBLE_RUNTIME enquanto o faturamento por perna está estabilizado no staging — é por isso que fusion está pré-visualização, não GA. Com a flag desativada, uma regra parallel: serve limpidamente sua primeira perna, então você pode criar e espelhar seus painéis hoje e ativá-los quando fusion chegar em sua região.


Primitivo 3 — fallbacks e cascatas de confiança

Fan-out gasta N× adiantado. Uma cascata gasta extra apenas quando a primeira resposta parece errada. Após a resposta, on_low_confidence: avalia sinais e, se um dispara, re-despacha para um destino mais 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"


Os sinais: patch_invalid (o diff falha no git apply --check), self_doubt (um conjunto de regex de frases de hesitação), low_logprob (logprob médio por token abaixo do limite, desde que o provedor o exponha), e next_turn_test_failed (um trinco entre turnos — o prompt deste turno carrega a forma dos testes que falharam no turno anterior). Cascatas são profundidade-1 por design. Combine-os com agent_state.models_tried para obter diversidade na repetição — nunca envie o reparo para o modelo que acabou de falhar.


Ajustando o dial: custo, latência, qualidade

A mesma DSL expressa todos os três objetivos; você escolhe por regra:

Custo — delegate: cheapest, keep the cheap model on the easy tail, and reserve fan-out for difficulty > 0.7. Table B's cheap panel (~64.5% ≈ Fable 5 solo) is the existence proof: uma fusão de modelos pequenos pode substituir um modelo frontier a uma fração do custo por token. Seja realista, porém — a fusão usa o "cobrar cada perna" modelo: um painel best_of_n de 3 pernas cobra três candidatos mais o juiz. A economia funciona porque você (a) só faz fan-out na minoria difícil das solicitações e (b) funde mais baratos membros do que o modelo frontier que você está substituindo.

Latência — árbitro: { estratégia: first } além de um max_latency_ms restrito fornece o mais rápido de N com um teto rígido.

Qualidade — best_of_n para trabalho aberto, tests_pass quando há um conjunto de testes para fundamentar. samples e thinking_budget_tokens compram mais dentro de uma única etapa.


Operá-lo sem quebrar o prod

Alterações de roteamento são assustadoras, então o DSL vem com os trilhos de segurança que um SRE espera:

Lint em cada salvamento — esquema, verificação de tipo CEL (todo 'when' deve ser avaliado como bool), resolução de referências, intervalos de knob, listas de bloqueio de cabeçalho/param. Erros retornam como {linha, coluna, mensagem, regra} e são exibidos como chips na margem do editor.

Simulação — Faça um POST de uma requisição sintética (task_class, difficulty, agent_state, …) e obtenha de volta a regra correspondente, o efeito resolvido e o tempo de avaliação antes que qualquer coisa seja enviada.

Modo sombra — por 24 h após o primeiro salvamento, o DSL é avaliado mas não usado; um shadow log registra as seleções potenciais e o console mostra um diff (percentual de rotas alteradas, delta de custo diário projetado, contagens de acionamentos por regra).

Canary — um controle deslizante de tráfego de 0 a 100. Aumente de 5 → 25 → 50 → 100 observando métricas por fatia; reverta deslizando para 0.

Auditoria + rollback — cada salvamento/rollback escreve uma linha de auditoria na mesma transação; edições concorrentes recebem um 409 com a versão atual para que você tente novamente com o estado atualizado.

Casos de teste, replicação de traces e uma visão de IA "explique este conjunto de regras" complementam. Você o encontra no painel emroteamento → estratégia → DSL.


Um conjunto completo de regras

Barato no fácil, médio no médio, um painel de fusão julgado na cauda agentiva difícil, com uma cascata de confiança por baixo:

versão: 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: balanced


Esse endpoint é aquele que fica no topo da Tabela A — não porque encontrou um modelo melhor, mas porque gasta o modelo certo na solicitação certa e funde um painel exatamente onde o painel vence.


Iniciar composição

O próximo salto em capacidade não precisa esperar pelo próximo checkpoint. É um gráfico que você pode escrever esta tarde: rotear por dificuldade, espalhar na cauda difícil, julgar ou testar as saídas, cascatear quando a confiança cair.

Documentos: https://docs.orcarouter.ai/routing/routing-dsl

UI: roteamento → Criar roteador -> Estratégia de roteamento → DSL (especialista)

A fronteira é um painel. Vá construir o seu.