English | 简体中文
Route prompts by difficulty, not habit.
UncommonRoute is a local LLM router that sits between your client and your upstream API. Easy turns go cheap, hard turns go strong, and fallback chains are ready when the first choice fails.
Built for Codex, Claude Code, Cursor, the OpenAI SDK, and OpenClaw.
Most AI tools make one bad assumption: every request deserves the same model.
That works until your workflow starts spending premium-model money on:
- "what is 2+2?"
- tool selection
- log summarization
- boring middle turns in an agent loop
UncommonRoute is the small local layer that changes that default.
Your client
(Codex / Claude Code / Cursor / OpenAI SDK / OpenClaw)
|
v
UncommonRoute
(runs on your machine)
|
v
Your upstream API
(Commonstack / OpenAI / Ollama / vLLM / Parallax / ...)
It does not host models. It makes a fast local routing decision, forwards the request to your chosen upstream, and keeps enough fallback logic around to recover when upstream model names or availability do not line up cleanly.
The pitch is simple: keep one local endpoint, let the router decide when a strong model is actually worth paying for.
- ~90-95% cost savings in real Claude Code / OpenClaw sessions versus always using premium models
- Zero keyword lists — the classifier uses structural features + n-gram learning, no hardcoded patterns
- Benchmark-driven quality — model quality from PinchBench replaces price-based assumptions
- Thompson Sampling — natural exploration-exploitation balance across the model pool
- 3 feedback clicks to change routing — user feedback takes effect immediately
- 341 passing tests
That is the core story of the project: spend premium-model money where it changes the answer, not where it just burns the budget.
If you are brand new, do these in order.
pip install uncommon-routeOptional convenience installer. Review the script first if you are security-conscious:
curl -fsSL https://anjieyang.github.io/uncommon-route/install | bashThis step does not need a real upstream or API key.
uncommon-route route "write a Python function that validates email addresses"
uncommon-route debug "prove that sqrt(2) is irrational"What this proves:
- the package is installed
- the local classifier works
- the router can produce a tier, model choice, and fallback chain
What this does not prove:
- your upstream is configured
- your client is connected through the proxy
Pick one example and export the variables.
# Commonstack: one key, many providers
export UNCOMMON_ROUTE_UPSTREAM="https://api.commonstack.ai/v1"
export UNCOMMON_ROUTE_API_KEY="csk-..."# OpenAI direct
export UNCOMMON_ROUTE_UPSTREAM="https://api.openai.com/v1"
export UNCOMMON_ROUTE_API_KEY="sk-..."# Local OpenAI-compatible servers (Ollama, vLLM, etc.)
export UNCOMMON_ROUTE_UPSTREAM="http://127.0.0.1:11434/v1"# Parallax scheduler endpoint (experimental)
export UNCOMMON_ROUTE_UPSTREAM="http://127.0.0.1:3001/v1"If your upstream does not need a key, you can skip UNCOMMON_ROUTE_API_KEY.
Parallax is still best treated as experimental here: public docs clearly expose POST /v1/chat/completions, but public /v1/models support is less obvious, so discovery-driven routing may be limited.
uncommon-route serveIf the upstream is configured, the startup banner shows:
- the upstream host
- the local proxy URL
- the dashboard URL
- a quick health-check command
If the upstream is missing, the banner tells you exactly which environment variables to set next.
Pick the path that matches your workflow.
Codex · OpenAI-compatible local routing
uncommon-route setup codexManual version:
export OPENAI_BASE_URL="http://localhost:8403/v1"
export OPENAI_API_KEY="not-needed"Then:
uncommon-route serve
codexFor smart routing, set:
model = "uncommon-route/auto"
Claude Code · Anthropic-style local routing
uncommon-route setup claude-codeManual version:
export ANTHROPIC_BASE_URL="http://localhost:8403"
export ANTHROPIC_API_KEY="not-needed"Then:
uncommon-route serve
claudeClaude Code talks to /v1/messages. UncommonRoute accepts Anthropic-style requests, routes them, and converts the response shape back transparently.
OpenAI SDK / Cursor · One local OpenAI-compatible base URL
uncommon-route setup openaiPython example:
from openai import OpenAI
client = OpenAI(
base_url="http://127.0.0.1:8403/v1",
api_key="not-needed",
)
response = client.chat.completions.create(
model="uncommon-route/auto",
messages=[{"role": "user", "content": "hello"}],
)For Cursor, point "OpenAI Base URL" to http://localhost:8403/v1.
OpenClaw · Plugin-based integration
openclaw plugins install @anjieyang/uncommon-route
openclaw gateway restartThe plugin starts the proxy for you, registers a local OpenClaw provider, and syncs the discovered upstream pool into OpenClaw once /v1/models/mapping is available.
The config-patch fallback is static by nature, so it only registers the virtual routing IDs.
Example plugin config:
plugins:
entries:
"@anjieyang/uncommon-route":
port: 8403
upstream: "https://api.commonstack.ai/v1"
spendLimits:
hourly: 5.00
daily: 20.00If your upstream needs authentication, set UNCOMMON_ROUTE_API_KEY in the environment where OpenClaw runs.
uncommon-route doctor
curl http://127.0.0.1:8403/healthWhen something feels off, uncommon-route doctor should almost always be the first command you run.
You do not need to understand every internal detail to use the project, but the mental model matters.
The classifier estimates a continuous difficulty score (0.0–1.0) from structural features and n-gram patterns. No keyword lists, no hardcoded rules. The score drives model selection through a quality prediction formula — there are no fixed tier boundaries in the routing logic.
Tiers (SIMPLE / MEDIUM / COMPLEX) still appear in logs, headers, and the dashboard, but they are display labels derived from the continuous score, not routing decisions.
| Mode | What it optimizes for |
|---|---|
auto |
balanced — best quality-per-dollar, adapts with difficulty |
fast |
cost-dominant — cheapest acceptable model |
best |
quality-dominant — highest quality, cost nearly ignored |
These show up as virtual model IDs:
uncommon-route/autouncommon-route/fastuncommon-route/best
Only these virtual IDs trigger routing. Explicit real model IDs still pass through unchanged.
The quality-vs-cost weight automatically increases with task difficulty: harder tasks prioritize quality more, even in auto mode.
Model quality comes from real benchmark data (PinchBench agent task scores), not from price assumptions. Quality scores are blended with observed experience through Bayesian updating — the system starts from benchmark data and adapts to real-world performance over time.
The selector uses Thompson Sampling (Beta distribution per model) for natural exploration-exploitation balance. Models with fewer observations have wider distributions, giving them chances to prove themselves.
| Layer | Source | What it learns |
|---|---|---|
| Benchmark prior | PinchBench API + seed data | Model quality baselines (refreshed periodically) |
| Implicit feedback | HTTP failures, retrial detection, logprob confidence | Automatic quality signals from every request |
| Explicit feedback | User ok/weak/strong signals | Direct quality corrections (3 clicks to change routing) |
Having tools in the request body does not inflate difficulty. A "hello" through Claude Code still routes as SIMPLE. The classifier evaluates the user's prompt on its own structural merits, not on whether tools happen to be available.
After starting the proxy, open:
http://127.0.0.1:8403/dashboard/
The dashboard shows:
- request counts, latency, cost, and savings
- mode, tier, and model distribution
- upstream transport and cache behavior
- selector state, default mode, and stored override rows
- primary upstream and BYOK provider connections
- recent traffic, spend limits, and usage
- recent feedback state and submitted feedback results
Useful commands around the dashboard:
uncommon-route doctor
uncommon-route serve --daemon
uncommon-route stop
uncommon-route logs
uncommon-route logs --follow
uncommon-route config show
uncommon-route stats
uncommon-route stats historyBackground mode writes to:
~/.uncommon-route/serve.pid~/.uncommon-route/serve.log
| Variable | Meaning |
|---|---|
UNCOMMON_ROUTE_UPSTREAM |
Upstream OpenAI-compatible API URL |
UNCOMMON_ROUTE_API_KEY |
API key for the upstream provider |
UNCOMMON_ROUTE_PORT |
Local proxy port (8403 by default) |
UNCOMMON_ROUTE_COMPOSITION_CONFIG |
Path to a composition-policy JSON file |
UNCOMMON_ROUTE_COMPOSITION_CONFIG_JSON |
Inline composition-policy JSON |
The effective primary upstream is resolved in this order:
- CLI flags like
uncommon-route serve --upstream ... - Environment variables like
UNCOMMON_ROUTE_UPSTREAMandUNCOMMON_ROUTE_API_KEY - File-backed settings saved from the dashboard or
PUT /v1/connections
Dashboard/API-managed primary connection values are stored at:
~/.uncommon-route/connections.json
If you want the router to prefer models backed by your own provider keys:
uncommon-route provider add openai sk-your-openai-key
uncommon-route provider add anthropic sk-ant-your-key
uncommon-route provider list
uncommon-route provider modelsProvider config is stored at:
~/.uncommon-route/providers.json
Important behavior today:
provider addstores a known model set for that provider- key verification uses
/modelswhen possible GET /v1/modelsstill exposes only UncommonRoute virtual models, not your full upstream catalog
If you need a specific upstream model right now, do one of these:
- send that explicit non-virtual model ID directly
- inspect the provider-backed set with
uncommon-route provider models - preview what the live scorer would pick with
GET /v1/selectoror the dashboard
Only the first option forces the request immediately. The others help you inspect what the live selector can see.
The optional --plan field is metadata only. It is shown in provider list, but it does not replace an API key or unlock models by itself.
uncommon-route config show
uncommon-route config set-default-mode fast
# Stored override rows for inspection / preview:
uncommon-route config set-tier auto SIMPLE moonshot/kimi-k2.5 --fallback google/gemini-2.5-flash-lite,deepseek/deepseek-chat
uncommon-route config set-tier best COMPLEX anthropic/claude-opus-4.6 --fallback anthropic/claude-sonnet-4.6 --strategy hard-pin
uncommon-route config reset-tier auto SIMPLEThe default mode is used when a request omits model. Explicit model IDs still pass through unchanged.
Tier overrides are persisted, surfaced in the dashboard/API, and shown in selector previews.
Important current behavior: the live pool-based request path still scores the discovered model pool at request time and does not yet enforce primary, fallback, or --strategy hard-pin as request-time routing controls.
If you need to force a model immediately, send that explicit non-virtual model ID directly.
Routing overrides are stored at:
~/.uncommon-route/routing_config.json
uncommon-route spend set per_request 0.10
uncommon-route spend set hourly 5.00
uncommon-route spend set daily 20.00
uncommon-route spend set session 3.00
uncommon-route spend status
uncommon-route spend historyWhen a limit is hit, the proxy returns HTTP 429 with reset_in_seconds.
Spend data is stored at:
~/.uncommon-route/spending.json
This is the compact lookup section for SDK authors, agent builders, and people wiring UncommonRoute into other tools.
| Client type | Base URL |
|---|---|
| OpenAI-compatible clients | http://127.0.0.1:8403/v1 |
| Anthropic-style clients | http://127.0.0.1:8403 |
| Model ID | Meaning |
|---|---|
uncommon-route/auto |
balanced default |
uncommon-route/fast |
lighter and faster |
uncommon-route/best |
highest quality |
| Endpoint | Why you would use it |
|---|---|
GET /health |
liveness, config status, model-discovery status |
GET /v1/models |
virtual models exposed by the router |
GET /v1/models/mapping |
internal-to-upstream model mapping and pool view |
GET /v1/connections / PUT /v1/connections |
inspect or update the primary runtime connection |
GET /v1/routing-config / POST /v1/routing-config |
inspect or update stored default-mode and mode/tier override rows |
GET /v1/stats / POST /v1/stats |
routing summary or reset |
GET /v1/stats/recent |
recent routed requests with feedback state |
GET /v1/selector / POST /v1/selector |
inspect selector state or preview a routing decision |
GET /v1/feedback / POST /v1/feedback |
inspect feedback state, submit signals, or rollback |
GET /dashboard/ |
human-friendly monitoring UI |
On routed requests that use a virtual model, headers can include:
x-uncommon-route-modelx-uncommon-route-tierx-uncommon-route-modex-uncommon-route-stepx-uncommon-route-reasoning
On passthrough requests with explicit non-virtual model IDs, do not assume all of those routing headers will exist.
from uncommon_route import classify, route
decision = route("explain the Byzantine Generals Problem")
print(decision.model)
print(decision.tier)
print(decision.confidence)
result = classify("hello")
print(result.tier)
print(result.signals)Different upstreams use different model IDs. UncommonRoute fetches /v1/models, builds a live pool when possible, maps internal IDs to what the upstream actually serves, and records learned aliases when fallbacks prove a better match.
Useful commands:
uncommon-route doctor
curl http://127.0.0.1:8403/v1/models/mappingVery large tool outputs are not always forwarded verbatim.
The proxy can:
- compact oversized text and JSON
- offload large tool results into local artifacts
- create semantic side-channel summaries
- checkpoint long histories
- rehydrate
artifact://...references on demand
Artifacts are stored under:
~/.uncommon-route/artifacts/
Useful headers for these flows:
x-uncommon-route-input-beforex-uncommon-route-input-afterx-uncommon-route-artifactsx-uncommon-route-semantic-callsx-uncommon-route-semantic-fallbacksx-uncommon-route-checkpointsx-uncommon-route-rehydrated
When routing lands on an Anthropic-family model and the upstream supports it, UncommonRoute can preserve Anthropic-native transport and caching semantics while still serving OpenAI-style clients normally.
The classifier is local and keyword-free — it uses structural features and character n-grams only. You can retrain it on your own benchmark data:
python -c "from uncommon_route.router.classifier import train_and_save_model; train_and_save_model('bench/data/train.jsonl')"Model experience and feedback data are stored at:
~/.uncommon-route/model-experience.json
~/.uncommon-route/benchmark_cache.json
uncommon-route route ... is a local routing decision. It does not call your upstream.
If real requests fail, check:
UNCOMMON_ROUTE_UPSTREAMUNCOMMON_ROUTE_API_KEYif your provider needs oneuncommon-route doctor
For OpenAI-style tools, OPENAI_BASE_URL must end with /v1:
export OPENAI_BASE_URL="http://localhost:8403/v1"For Anthropic-style tools, ANTHROPIC_BASE_URL should point at the router root, not /v1:
export ANTHROPIC_BASE_URL="http://localhost:8403"Some local or experimental servers expose POST /chat/completions but not a clean /models endpoint. In that case, passthrough may still work while live discovery stays limited. uncommon-route doctor will tell you whether discovery succeeded.
Run:
uncommon-route doctorThat one command usually tells you what is missing.
The v2 classifier uses structural features + n-grams only (no keyword lists). Trained on 1,904 examples, tested on a held-out set of 1,077 prompts:
| Metric | Value |
|---|---|
| Training accuracy | 99.2% |
| Held-out accuracy | 88.5% |
The classifier's role in v2 is to provide a continuous difficulty signal, not to make the final routing decision. Benchmark quality data and Thompson Sampling compensate for classification errors.
In end-to-end testing through Claude Code with Commonstack upstream:
- ~90-95% cost reduction versus always using premium models
- 28/28 requests successful with quality maintained across all difficulty levels
- 15 different models selected via Thompson Sampling
- 0 expensive model waste on simple tasks
- 3 feedback clicks sufficient to change routing behavior
Quality is maintained because the system uses PinchBench benchmark data to select models by measured agent-task performance, not by price.
python -m bench.rununcommon_route/is the shipped runtime package: proxy, router, CLI, calibration.bench/contains offline evaluation datasets and benchmark scripts.demo/contains local comparison/demo apps. The comparison server now lives indemo/compare_api.py.frontend/contains dashboard/demo frontends.
The root-level api.py now exists only as a compatibility shim for the comparison demo, so the package boundary stays clear.
If you want to stop using UncommonRoute, there are three different levels:
- stop the local proxy
- clear all local records and state
- fully uninstall and restore your client config
# If you started it in background mode
uncommon-route stopIf you started uncommon-route serve in the foreground, stop it with Ctrl+C.
By default, UncommonRoute stores local state under:
~/.uncommon-route
If you set UNCOMMON_ROUTE_DATA_DIR, it uses that directory instead.
That local data directory can contain:
- route stats and spending history
- dashboard-saved primary connection and routing overrides
- BYOK provider keys
- online-learning weights and feedback buffers
- learned aliases, model-experience memory, logs, and local artifacts
To clear all local records, stop the proxy first and then move or delete the active data directory:
# Show the active data directory
echo "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}"
# Recommended: move it aside as a backup first
mv "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}" \
"${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}.backup-$(date +%Y%m%d-%H%M%S)"
# Or permanently delete it if you are sure
# rm -rf "${UNCOMMON_ROUTE_DATA_DIR:-$HOME/.uncommon-route}"If you only want to clear routing analytics, uncommon-route stats reset resets stats and pending feedback. It does not remove the rest of the local state.
If you installed the OpenClaw integration, remove that first:
openclaw plugins uninstall @anjieyang/uncommon-route
# If you used the config-patch fallback instead of the plugin:
uncommon-route openclaw uninstallStopping serve or uninstalling the package only stops the local proxy layer. It does not automatically restore your previous client config. If your client still points at http://localhost:8403 or http://localhost:8403/v1, it will keep trying localhost until you restore the original settings.
Typical client rollback commands:
unset UNCOMMON_ROUTE_UPSTREAM
unset UNCOMMON_ROUTE_API_KEY
unset OPENAI_BASE_URL
unset ANTHROPIC_BASE_URLThen remove the Python package with the same tool you used to install it:
pipx uninstall uncommon-route
# or
python -m pip uninstall uncommon-route
# or
pip uninstall uncommon-routegit clone https://github.com/CommonstackAI/UncommonRoute.git
cd UncommonRoute
pip install -e ".[dev]"
python -m pytest tests -vThe current test suite is 341 passed on the latest local run.
MIT — see LICENSE.