The LLM router that lives in your shell. One config file, every model, zero dependencies beyond bash and jq.
eval $(model-router standard)
echo $MODEL_ID # claude-sonnet-5When Anthropic ships a new model, you update one JSON file — not 30 scripts.
Every team running AI automation hits the same problem: model names hardcoded across dozens of scripts. When claude-sonnet-4 becomes claude-sonnet-5, you grep through everything, miss one, and it breaks at 2 AM.
Model Router fixes this with two ideas:
- Tiers, not model names. Scripts say
standard, notclaude-sonnet-5. The mapping lives in one config. - Automatic fallback. Provider down? Config corrupt? Safe defaults kick in. Your 2 AM cron job doesn't die.
| Feature | model-router | LiteLLM | OpenRouter | Portkey |
|---|---|---|---|---|
Shell-native (eval output) |
Yes | No | No | No |
| Zero dependencies | bash + jq | Python + pip | SaaS | Node.js |
| Self-hosted | Yes | Yes | No | Yes |
| No server/proxy needed | Yes | No (proxy) | No (API) | No (gateway) |
| Provider health checks | Yes | Yes | N/A | Yes |
| Cost tracking | Yes | Yes | Yes | Yes |
| Latency benchmarks | Yes | No | No | Yes |
| Eval-injection safe | Yes | N/A | N/A | N/A |
| Lines of code | ~300 | 50,000+ | Closed | 20,000+ |
| Setup time | 30 seconds | Minutes | Account | Minutes |
When to use model-router: You write bash. Your AI automation runs in cron jobs, CI pipelines, or shell scripts. You want routing without adding Python, Node.js, or a proxy server to your stack.
When to use something else: You need a unified OpenAI-compatible API proxy, SDK support in Python/JS, or request-level load balancing across model replicas.
git clone https://github.com/FvdHMBAI/model-router.git
cd model-router
./install.shOr just copy two files:
cp model-router.sh /usr/local/bin/model-router
cp model-routing.json ~/.config/model-router/model-routing.json
export MODEL_ROUTER_CONFIG="$HOME/.config/model-router/model-routing.json"# Route a standard task
eval $(model-router standard)
echo "$MODEL_ID" # claude-sonnet-5
echo "$MODEL_PROVIDER" # anthropic
echo "$MODEL_EFFORT" # medium
# Heavy task — complex debugging, architecture
eval $(model-router heavy)
# -> claude-opus-5, effort=high
# Free local model — triage, classification
eval $(model-router local)
# -> qwen3:8b via Ollama
# Use in an API call
eval $(model-router standard)
curl -s https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n \
--arg model "$MODEL_ID" \
--argjson max_tokens "$MODEL_MAX_TOKENS" \
'{model: $model, max_tokens: $max_tokens,
messages: [{role: "user", content: "Hello"}]}')"| Command | Description |
|---|---|
model-router <tier> |
Output eval-safe variables for a tier |
model-router <agent> |
Resolve agent name to tier to model |
model-router info |
Show full configuration |
model-router health |
Check all provider endpoints |
model-router recommend <name> |
Show routing details for an agent or job |
model-router cost-report |
Usage summary by provider, tier, and day |
model-router benchmark [tier] |
Latency test across providers |
model-router init |
Create default config in ~/.config/model-router/ |
model-router --version |
Show version |
model-router --help |
Show usage |
| Tier | Default Model | Provider | Use Case | Cost |
|---|---|---|---|---|
heavy |
claude-opus-5 | Anthropic | Debugging, architecture, security | $$$ |
standard |
claude-sonnet-5 | Anthropic | Reviews, tickets, content, deployments | $$ |
light |
claude-haiku-4-5 | Anthropic | Classification, formatting, health checks | $ |
local |
qwen3:8b | Ollama | Triage, docs, simple analysis | Free |
local-fast |
qwen2.5:1.5b | Ollama | Keyword extraction, fast classification | Free |
gemini |
gemini-2.5-flash | Cross-review, video, large contexts | $ | |
mistral |
mistral-small-latest | Mistral | EU-hosted, GDPR-compliant workloads | $ |
All tiers are customizable. Add your own in model-routing.json.
Every routing call sets these eval-safe shell variables:
| Variable | Example | Description |
|---|---|---|
MODEL_ID |
claude-sonnet-5 |
Full model identifier for API calls |
MODEL_PROVIDER |
anthropic |
Provider name |
MODEL_TIER |
standard |
Resolved tier |
MODEL_COST_PER_1K_IN |
0.003 |
Input cost per 1K tokens (USD) |
MODEL_COST_PER_1K_OUT |
0.015 |
Output cost per 1K tokens (USD) |
MODEL_MAX_TOKENS |
8192 |
Maximum output tokens |
MODEL_CLI_NAME |
sonnet |
Short name for CLI tools |
MODEL_EFFORT |
medium |
Reasoning effort hint |
5 providers supported out of the box:
| Provider | Models | Notes |
|---|---|---|
| Anthropic | Opus 5, Sonnet 5, Haiku 4.5 | Primary |
| Gemini 2.5 Flash/Pro | Cross-review, large context | |
| Mistral | Small, Medium | EU-hosted, GDPR |
| OpenAI | GPT-4o, GPT-4o-mini | Alternative |
| Ollama | Any local model | Free, self-hosted |
Add more by editing the providers section in your config.
Map your agents and cron jobs to tiers:
{
"agents": {
"code-reviewer": "standard",
"architect": "heavy",
"formatter": "light"
},
"autonomous_jobs": {
"nightly-scan": "local",
"weekly-review": "standard"
}
}Then route by name:
eval $(model-router code-reviewer)
# Resolves: code-reviewer -> standard -> anthropic/claude-sonnet-5Model Router is designed for unattended operation:
- Missing config? Safe hardcoded defaults (heavy=opus, standard=sonnet, light=haiku, local=qwen)
- Corrupt JSON? Falls back to defaults, prints
CONFIG_DEGRADED='true'to stderr - Provider down? Follows the
fallbackchain defined per tier, logs the failover to stderr - Eval injection? All output values are sanitized — only alphanumeric, dots, colons, hyphens pass through
Every routing call logs to ~/.model-router/usage.log:
2026-08-02T14:30:00Z tier=standard model=claude-sonnet-5 provider=anthropic cost_in=0.003 cost_out=0.015
View your usage:
model-router cost-report
# === Cost Report ===
# By provider (last 30 days):
# anthropic 142 requests
# ollama 89 requests
# By tier (last 30 days):
# standard 98 requests
# local 89 requests
# heavy 44 requestsmodel-router health
# === Provider Health Check ===
# anthropic OK
# google OK
# mistral OK
# openai NO KEY (set OPENAI_API_KEY)
# ollama OKSee the examples/ directory:
- ci-pipeline.sh — Use model-router in CI/CD
- cron-job.sh — Overnight batch processing with local-first routing
- multi-provider.sh — Provider failover and health demo
Edit model-routing.json to match your setup:
{
"tiers": {
"heavy": {
"provider": "anthropic",
"model": "claude-opus-5",
"fallback": "standard",
"effort": "high",
"cost_per_1k_input": 0.015,
"cost_per_1k_output": 0.075
}
},
"providers": {
"anthropic": {
"status": "active",
"api_key_env": "ANTHROPIC_API_KEY",
"base_url": "https://api.anthropic.com"
}
},
"cost_limits": {
"daily_eur": 50,
"monthly_eur": 800,
"alert_threshold": 0.8
}
}Override config location:
export MODEL_ROUTER_CONFIG="/path/to/your/model-routing.json"bash4+jq(JSON parsing)- API keys for the providers you use (set via environment variables)
Contributions welcome. Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Run
bash tests/test-model-router.shbefore submitting - Open a PR