Post

9router local para trabalhar com multi-LLM sem interrupcao

Como configurar um 9router local para usar varios modelos com round-robin, quality gate pragmatico, combos por faixa de custo e integracao com Claude CLI, Codex CLI e OpenCode

9router local para trabalhar com multi-LLM sem interrupcao

Se voce usa mais de um CLI de IA no terminal, cedo ou tarde cai no mesmo problema: um provider limita, outro oscila, um terceiro tem melhor qualidade mas custa mais, e voce acaba alternando manualmente entre ferramentas, chaves, modelos e hacks de ambiente.

Foi exatamente por isso que comecei a usar o 9router local como endpoint unico na minha maquina. A ideia e simples:

  • um endpoint local em http://127.0.0.1:20128
  • varios providers e modelos por tras
  • combos separados por qualidade, overflow e sobrevivencia
  • clientes diferentes apontando para o mesmo lugar
  • servico persistente no Linux via systemd --user

O objetivo deste artigo nao e apresentar uma “lista de melhores modelos”. O objetivo e documentar uma configuracao operacional, pensada para quem quer:

  • trabalhar por horas sem ficar trocando de modelo manualmente
  • preservar qualidade media alta
  • distribuir carga entre modelos bons
  • manter um plano B quando o topo comeca a falhar
  • integrar tudo com Claude CLI, Codex CLI e OpenCode

O problema real

Muita gente monta um “combo gigante” com 20 ou 30 modelos e espera que o roteador resolva tudo sozinho.

Na pratica, isso quase sempre degrada a experiencia:

  • modelos excelentes convivem com modelos medianos no mesmo pool
  • um provider com erro recorrente contamina a cadeia inteira
  • modelos locais lentos entram no mesmo round-robin dos premium
  • a qualidade percebida fica inconsistente de uma request para outra

Se a estrategia e round-robin, o seu quality gate nao e a ordem da lista. O quality gate e quem voce aceita colocar dentro do pool.

Essa mudanca de mentalidade e a parte mais importante:

  1. round-robin nao e “desce do melhor para o pior”
  2. round-robin e “todos os modelos do pool sao candidatos legitimos”
  3. portanto, so devem entrar no pool principal modelos que voce aceitaria receber em qualquer request

A arquitetura que funciona

No meu caso, a stack ficou assim:

flowchart LR
    A[Claude CLI] --> R[9router local]
    B[Codex CLI] --> R
    C[OpenCode] --> R
    D[outros clientes OpenAI-compatible] --> R

    R --> C1[combo premium]
    R --> C2[combo overflow]
    R --> C3[combo cheap survival]

    C1 --> P1[Codex]
    C1 --> P2[Claude]
    C1 --> P3[Gemini]
    C1 --> P4[Qwen]

    C2 --> P5[o3 / Gemini 2.5 / GPT-4.1]
    C2 --> P6[Qwen flash / Ollama]

    C3 --> P7[Gemini flash]
    C3 --> P8[Haiku]
    C3 --> P9[Ollama]

Com isso, cada cliente aponta para um endpoint so, e eu escolho a combo de acordo com o contexto:

  • 0interruptions: trabalho serio, qualidade alta, round-robin entre modelos fortes
  • overflow-stable: quando o premium fica ruim, lento ou saturado
  • cheap-survival: continuidade barata e resiliente

Como pensar em combos

Regra 1: combo principal curto e forte

Se voce quer consistencia, o combo principal deve ser pequeno.

No meu uso, o principal ficou assim:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "name": "0interruptions",
  "models": [
    "cx/gpt-5.4",
    "cx/gpt-5.3-codex",
    "cc/claude-sonnet-4-6",
    "gc/gemini-3-pro-preview",
    "qw/qwen3-coder-plus",
    "gc/gemini-3-flash-preview",
    "cc/claude-haiku-4-5-20251001"
  ],
  "strategy": "round-robin",
  "propagate_errors": false
}

Por que esse pool funciona:

  • gpt-5.4 e gpt-5.3-codex seguram codigo e raciocinio muito bem
  • claude-sonnet-4-6 continua sendo excelente para leitura e refino
  • gemini-3-pro-preview adiciona outra familia forte
  • qwen3-coder-plus amplia throughput sem derrubar demais a qualidade
  • gemini-3-flash-preview e haiku entram como alivio rapido, mas ainda aceitaveis

Por que esse pool nao tem ollama:

  • ollama e util, mas eu nao quero que ele participe do pool premium como se fosse equivalente aos modelos de topo

Regra 2: overflow nao deve competir com o premium

Quando o principal comeca a falhar, o ideal e trocar para um combo que ainda seja bom, mas com outra distribuicao de custo e risco:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "name": "overflow-stable",
  "models": [
    "kc/openai/o3",
    "kc/google/gemini-2.5-pro",
    "kc/openai/gpt-4.1",
    "kc/google/gemini-2.5-flash",
    "qw/qwen3-coder-flash",
    "ollama/gpt-oss:120b",
    "ollama/qwen3.5"
  ],
  "strategy": "round-robin",
  "propagate_errors": false
}

Regra 3: cheap survival e outro produto

Nao coloque no combo principal modelos que voce usaria apenas para “continuar operando”.

Esse papel e do terceiro combo:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "name": "cheap-survival",
  "models": [
    "gc/gemini-3-flash-preview",
    "cc/claude-haiku-4-5-20251001",
    "qw/qwen3-coder-flash",
    "kc/google/gemini-2.5-flash",
    "ollama/qwen3.5",
    "ollama/gpt-oss:120b"
  ],
  "strategy": "round-robin",
  "propagate_errors": false
}

Esse combo existe para preservar:

  • continuidade
  • custo menor
  • independencia parcial de providers premium

Nao para maximizar qualidade.

O erro classico: confundir ordem com quality gate

Se a estrategia for round-robin, isto:

1
["modelo-top", "modelo-medio", "modelo-fraco"]

nao significa:

  1. usa o top ate cansar
  2. depois cai no medio
  3. por ultimo usa o fraco

Significa apenas que os tres estao no mesmo conjunto ativo.

Entao, para ter um quality gate pragmatico em round-robin:

  • monte um pool pequeno
  • remova tudo que voce nao aceitaria receber “agora”
  • crie combos separados para premium, overflow e survival

Instalacao basica do 9router

Subindo via CLI

1
2
npm install -g 9router
9router

O dashboard local normalmente fica em:

1
http://127.0.0.1:20128/dashboard

No meu caso, o endpoint compativel que interessa para os clientes e:

1
http://127.0.0.1:20128/v1

Teste rapido

1
curl -sS http://127.0.0.1:20128/v1/models | jq -r '.data[].id' | sed -n '1,40p'

Se estiver tudo certo, voce vera seus aliases, combos e modelos disponiveis.

Estrutura recomendada de operacao

No meu ambiente, a organizacao ficou:

  • 0interruptions para sessao principal
  • overflow-stable quando o premium degrada
  • cheap-survival quando quero continuar mesmo com latencia maior ou qualidade menor

Uma heuristica simples para troca:

  1. fique no 0interruptions por padrao
  2. se comecarem erros repetidos, timeouts ou alta variacao, troque para overflow-stable
  3. se estiver em situacao de contingencia, use cheap-survival

Integrando com Codex CLI

O Codex CLI foi o mais simples de integrar, porque ele aceita provider customizado em ~/.codex/config.toml.

Exemplo minimo:

1
2
3
4
5
6
7
8
9
10
model = "0interruptions"
model_provider = "9router"
personality = "pragmatic"
model_reasoning_effort = "medium"

[model_providers.9router]
name = "9Router"
base_url = "http://localhost:20128/v1"
wire_api = "responses"
env_key = "OPENAI_API_KEY"

E no shell:

1
export OPENAI_API_KEY=sk_9router

Esse padrao funciona bem porque o Codex conversa com um endpoint OpenAI-compatible, e o 9router entrega isso localmente.

Observacoes praticas para Codex

  • use base_url com /v1
  • deixe o apiKey local como sk_9router
  • o nome do model pode ser um modelo especifico ou uma combo

Exemplos:

1
model = "0interruptions"

ou

1
model = "cx/gpt-5.4"

Integrando com OpenCode

No meu ambiente, o OpenCode usa provider customizado em ~/.config/opencode/opencode.json.

Exemplo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
  "provider": {
    "9router": {
      "npm": "@ai-sdk/openai-compatible",
      "options": {
        "baseURL": "http://localhost:20128/v1",
        "apiKey": "sk_9router"
      },
      "models": {
        "0interruptions": {
          "name": "0interruptions"
        },
        "overflow-stable": {
          "name": "overflow-stable"
        },
        "cheap-survival": {
          "name": "cheap-survival"
        }
      }
    }
  },
  "model": "9router/0interruptions"
}

Alternando pelo terminal

Tambem da para subir uma sessao ja escolhendo a combo:

1
opencode -m 9router/0interruptions

ou:

1
opencode -m 9router/overflow-stable

ou:

1
opencode -m 9router/cheap-survival

Integrando com Claude CLI

A integracao com Claude CLI pede mais cuidado, porque ele usa wire format Anthropic.

O ponto mais importante aqui e:

  • para clientes OpenAI-compatible, use http://127.0.0.1:20128/v1
  • para clientes Anthropic-compatible, nao adicione /v1 se o cliente ja construir o path por conta propria

Eu bati exatamente nesse problema: quando o cliente concatenava o path final, a URL virava algo como:

1
http://localhost:20128/v1/v1/messages

ou seja, v1 duplicado.

Exemplo por ambiente

Se o seu fluxo do Claude CLI respeitar ANTHROPIC_BASE_URL:

1
2
3
export ANTHROPIC_BASE_URL=http://127.0.0.1:20128
export ANTHROPIC_API_KEY=sk_9router
claude

Regra pratica para Claude

  • teste primeiro sem /v1 no ANTHROPIC_BASE_URL
  • se o cliente montar /v1/messages sozinho, isso evita duplicacao
  • valide com logs do proprio CLI quando der erro de conexao

Quando eu prefiro usar Claude via 9router

Eu uso quando quero:

  • manter um endpoint local unico
  • variar modelos por combo
  • reaproveitar o mesmo roteamento e observabilidade

Eu nao uso quando:

  • quero depurar especificamente um problema do provider Anthropic puro
  • preciso isolar se o problema esta no cliente ou no roteador

Configuracao minima de shell

No shell, costumo padronizar:

1
2
export OPENAI_API_KEY="${OPENAI_API_KEY:-sk_9router}"
export OPENAI_BASE_URL="${OPENAI_BASE_URL:-http://127.0.0.1:20128/v1}"

E, quando for usar Claude via endpoint Anthropic-compatible:

1
2
export ANTHROPIC_API_KEY="${ANTHROPIC_API_KEY:-sk_9router}"
export ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL:-http://127.0.0.1:20128}"

Isso nao substitui configuracao nativa de cada CLI, mas reduz atrito.

Rodando o 9router como servico no Linux

Iniciar 9router manualmente no terminal toda hora e desperdicador. A solucao correta em desktop Linux e systemd --user.

Service principal

Arquivo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# ~/.config/systemd/user/9router.service
[Unit]
Description=9router local AI proxy
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/home/SEU_USUARIO/.nvm/versions/node/v22.19.0/bin/9router --no-browser --host 127.0.0.1 --port 20128
Restart=always
RestartSec=5
WorkingDirectory=/home/SEU_USUARIO
Environment=HOME=/home/SEU_USUARIO
Environment=PATH=/home/SEU_USUARIO/.nvm/versions/node/v22.19.0/bin:/usr/local/bin:/usr/bin:/bin

[Install]
WantedBy=default.target

Habilitando

1
2
systemctl --user daemon-reload
systemctl --user enable --now 9router.service

Verificando

1
2
systemctl --user status 9router.service
curl -fsS http://127.0.0.1:20128/v1/models | jq -r '.data[0].id'

Health check automatico

Se voce quer reduzir intervencao manual, vale colocar um timer simples que testa o endpoint e reinicia o servico quando necessario.

Script

1
2
3
4
5
6
#!/bin/zsh
set -euo pipefail
endpoint="http://127.0.0.1:20128/v1/models"
if ! curl -fsS --max-time 10 "$endpoint" >/dev/null; then
  systemctl --user restart 9router.service
fi

Unit

1
2
3
4
5
6
7
8
# ~/.config/systemd/user/9router-healthcheck.service
[Unit]
Description=Health check 9router and restart if needed
After=9router.service

[Service]
Type=oneshot
ExecStart=%h/.local/bin/9router-healthcheck

Timer

1
2
3
4
5
6
7
8
9
10
11
12
# ~/.config/systemd/user/9router-healthcheck.timer
[Unit]
Description=Periodic 9router health check

[Timer]
OnBootSec=2min
OnUnitActiveSec=2min
Unit=9router-healthcheck.service
Persistent=true

[Install]
WantedBy=timers.target

Habilitando

1
2
systemctl --user daemon-reload
systemctl --user enable --now 9router-healthcheck.timer

Atualizacao automatica via npm

Se o 9router e parte da sua stack diaria, tambem vale automatizar update.

Script de update

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/bin/zsh
set -euo pipefail

PATH="$HOME/.nvm/versions/node/v22.19.0/bin:/usr/local/bin:/usr/bin:/bin"

current="$(npm list -g 9router --depth=0 2>/dev/null | sed -n 's/.*9router@//p' | head -n1 | tr -d ' ')"
latest="$(npm view 9router version 2>/dev/null | tail -n1 | tr -d ' ')"

if [[ -z "$latest" ]]; then
  echo "Could not determine latest 9router version" >&2
  exit 1
fi

if [[ "$current" != "$latest" ]]; then
  npm install -g "9router@$latest"
  systemctl --user restart 9router.service
fi

Unit

1
2
3
4
5
6
7
8
9
# ~/.config/systemd/user/9router-update.service
[Unit]
Description=Update 9router to latest npm release and restart service if changed
After=network-online.target
Wants=network-online.target

[Service]
Type=oneshot
ExecStart=%h/.local/bin/9router-update-check

Timer

1
2
3
4
5
6
7
8
9
10
11
12
13
# ~/.config/systemd/user/9router-update.timer
[Unit]
Description=Periodic 9router update check

[Timer]
OnActiveSec=15min
OnUnitActiveSec=6h
RandomizedDelaySec=30min
Unit=9router-update.service
Persistent=true

[Install]
WantedBy=timers.target

Habilitando

1
2
systemctl --user daemon-reload
systemctl --user enable --now 9router-update.timer

Um detalhe importante sobre reboot e sessao

systemd --user normalmente sobe junto com a sua sessao. Se voce quiser que o servico continue mesmo sem login interativo, habilite linger:

1
sudo loginctl enable-linger "$USER"

Esse passo faz sentido em workstation que voce quer tratar como ambiente de automacao local.

Erros comuns

1. Misturar /v1 em clientes diferentes

Use esta regra:

  • Codex e OpenCode: http://127.0.0.1:20128/v1
  • Claude: testar primeiro http://127.0.0.1:20128

2. Botar modelos ruins no combo premium

Se o combo principal esta em round-robin, tudo que entra nele deve ser aceitavel para qualquer request.

3. Misturar local lento com premium caro no mesmo pool

Modelos ollama sao uteis. So nao devem competir no mesmo pool do topo se voce quer consistencia.

4. Achar que ordem em round-robin e fallback top-down

Nao e.

Se voce quer top-down de verdade, precisa de outra estrategia.

Se quer round-robin, separe os pools.

5. Atualizar o 9router sem reiniciar o servico

Se o binario mudou e o processo continua antigo, voce fica com metade da stack atualizada e metade nao. O script de update precisa reiniciar o servico principal.

Configuracao de referencia

Se eu fosse montar do zero hoje para uso diario local, faria assim:

0interruptions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "name": "0interruptions",
  "models": [
    "cx/gpt-5.4",
    "cx/gpt-5.3-codex",
    "cc/claude-sonnet-4-6",
    "gc/gemini-3-pro-preview",
    "qw/qwen3-coder-plus",
    "gc/gemini-3-flash-preview",
    "cc/claude-haiku-4-5-20251001"
  ],
  "strategy": "round-robin",
  "propagate_errors": false
}

overflow-stable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
  "name": "overflow-stable",
  "models": [
    "kc/openai/o3",
    "kc/google/gemini-2.5-pro",
    "kc/openai/gpt-4.1",
    "kc/google/gemini-2.5-flash",
    "qw/qwen3-coder-flash",
    "ollama/gpt-oss:120b",
    "ollama/qwen3.5"
  ],
  "strategy": "round-robin",
  "propagate_errors": false
}

cheap-survival

1
2
3
4
5
6
7
8
9
10
11
12
13
{
  "name": "cheap-survival",
  "models": [
    "gc/gemini-3-flash-preview",
    "cc/claude-haiku-4-5-20251001",
    "qw/qwen3-coder-flash",
    "kc/google/gemini-2.5-flash",
    "ollama/qwen3.5",
    "ollama/gpt-oss:120b"
  ],
  "strategy": "round-robin",
  "propagate_errors": false
}

Quando eu usaria cada um

Use 0interruptions quando:

  • estiver codando
  • estiver escrevendo documento tecnico importante
  • quiser consistencia

Use overflow-stable quando:

  • o principal comecar a falhar
  • quiser manter qualidade boa com outra distribuicao
  • nao quiser cair direto para o pool barato

Use cheap-survival quando:

  • o objetivo principal for continuidade
  • voce aceitar variacao maior
  • o premium estiver indisponivel, caro ou congestionado

Conclusao

O valor real do 9router nao esta em “ter acesso a muitos modelos”. Isso sozinho so aumenta a aleatoriedade.

O valor esta em transformar varios modelos e varios CLIs em uma operacao local previsivel:

  • endpoint unico
  • combos por nivel de exigencia
  • health check
  • update automatizado
  • clientes diferentes apontando para a mesma infraestrutura

Se eu tivesse que resumir o aprendizado em uma frase, seria esta:

em multi-LLM local, confiabilidade vem mais da curadoria dos pools do que da quantidade de modelos disponiveis.

E isso muda completamente a experiencia de trabalho no terminal.

Prompt pronto para agente local

Se voce quiser delegar a implementacao inteira para um agente local como Codex, Claude ou outro agente de terminal, use o bloco abaixo. A ideia e manter o prompt enxuto e usar este artigo como referencia completa:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Implemente localmente uma stack completa de 9router para uso com CLIs de IA no terminal.

Objetivo:
- subir e manter o 9router local em Linux
- configurar combos de modelos com quality gate pragmatico
- integrar Codex CLI, Claude CLI e OpenCode para usar o endpoint local
- adicionar healthcheck e auto-update com reinicio do servico principal
- validar tudo no final

Referencia completa:
- https://joaocarlos.dev/posts/9router-local-para-multi-llm-sem-interrupcao/

Escopo esperado:
1. Instalar ou atualizar `9router`.
2. Configurar o endpoint local em `http://127.0.0.1:20128`.
3. Criar os combos abaixo no `9router`:
   - `0interruptions`
   - `overflow-stable`
   - `cheap-survival`
4. Configurar `systemd --user` para:
   - iniciar o `9router` no boot da sessao
   - reiniciar automaticamente em caso de falha
   - executar healthcheck periodico
   - executar update periodico com `npm install -g 9router@latest`
   - reiniciar o servico principal se houver update
5. Configurar os clientes:
   - Codex CLI via provider customizado apontando para `http://127.0.0.1:20128/v1`
   - OpenCode via provider `9router`
   - Claude CLI via endpoint Anthropic-compatible, evitando duplicacao de `/v1`
6. Configurar variaveis de ambiente necessarias no shell.
7. Validar com comandos reais:
   - `curl http://127.0.0.1:20128/v1/models`
   - status do `systemd --user`
   - um teste simples por CLI, se disponivel
8. Entregar um resumo final com:
   - arquivos criados/alterados
   - comandos de validacao
   - riscos ou ajustes manuais restantes

Regras de implementacao:
- siga o artigo como fonte de verdade
- preserve uma arquitetura com tres combos separados
- mantenha `round-robin` e `propagate_errors: false`
- nao invente modelos fora dos listados localmente
- prefira configuracao simples, auditavel e resiliente
- se houver conflito entre cliente OpenAI-compatible e Anthropic-compatible, ajuste a base URL corretamente
- execute a implementacao de ponta a ponta sem parar em analise
Esta postagem está licenciada sob CC BY 4.0 pelo autor.