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
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 CLIeOpenCode
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:
round-robinnao e “desce do melhor para o pior”round-robine “todos os modelos do pool sao candidatos legitimos”- 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 fortesoverflow-stable: quando o premium fica ruim, lento ou saturadocheap-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.4egpt-5.3-codexseguram codigo e raciocinio muito bemclaude-sonnet-4-6continua sendo excelente para leitura e refinogemini-3-pro-previewadiciona outra familia forteqwen3-coder-plusamplia throughput sem derrubar demais a qualidadegemini-3-flash-previewehaikuentram como alivio rapido, mas ainda aceitaveis
Por que esse pool nao tem ollama:
ollamae 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:
- usa o top ate cansar
- depois cai no medio
- 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:
0interruptionspara sessao principaloverflow-stablequando o premium degradacheap-survivalquando quero continuar mesmo com latencia maior ou qualidade menor
Uma heuristica simples para troca:
- fique no
0interruptionspor padrao - se comecarem erros repetidos, timeouts ou alta variacao, troque para
overflow-stable - 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_urlcom/v1 - deixe o
apiKeylocal comosk_9router - o nome do
modelpode 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
/v1se 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
/v1noANTHROPIC_BASE_URL - se o cliente montar
/v1/messagessozinho, 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:
CodexeOpenCode:http://127.0.0.1:20128/v1Claude: testar primeirohttp://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