An OpenAI-compatible LLM router that saves cost without losing quality.
Project description
switchboard
An OpenAI-compatible LLM router that saves cost without losing quality. Point any OpenAI client at it and it routes each request to the cheapest model that can handle it — easy prompts to a small model, hard ones to a parallel Mixture-of-Agents — trading a little latency for large savings while holding (or beating) frontier-model quality on a representative workload.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="anything")
client.chat.completions.create(model="router-cost", messages=[{"role": "user", "content": "..."}])
It works on top of any OpenAI-compatible gateway that fronts multiple providers
behind one key (e.g. a LiteLLM proxy) — so one client can reach OpenAI, Anthropic,
and Google models just by changing the model field. The router is a thin policy
on top of that.
Install
pip install switchboard-llm # or: uv add switchboard-llm
Configure your gateway (any OpenAI-compatible endpoint):
export OPENAI_API_KEY=... # your gateway key
export OPENAI_BASE_URL=https://.../v1 # your endpoint
Use it
As a server (drop-in for any OpenAI client):
switchboard serve # http://localhost:8000/v1 (use --port to change)
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="anything")
r = client.chat.completions.create(model="router", messages=[{"role": "user", "content": "Hi"}])
print(r.model_extra["switchboard"]) # route, cost, savings telemetry
As a library:
import asyncio
from switchboard import Engine
async def main():
eng = Engine()
rr = await eng.answer([{"role": "user", "content": "What is 17 * 23?"}], mode="cost")
print(rr.content, f"${rr.cost:.6f}", f"{rr.savings_pct:.0f}% cheaper than Opus")
await eng.aclose()
asyncio.run(main())
From the CLI:
switchboard ask "Prove sqrt(2) is irrational" --mode quality
switchboard models # probe which gateway models are actually live
The honest thesis (read this first)
The goal is a router that is cheaper than a frontier model (e.g. Opus) and matches-or-beats it on benchmarks. That is achievable — but only as a portfolio result over a realistic workload, not a per-query miracle. The iron law:
On a single hard query, you cannot both beat the frontier model and be cheaper than it on that same query.
What you can do, and what this does:
| Traffic | What the router does | Outcome |
|---|---|---|
| Easy queries (most real traffic) | route to a cheap model | quality ties Opus, 5–50× cheaper |
| Hard queries (the minority) | Mixture-of-Agents: several cheap/mid models answer in parallel, a synthesizer fuses them | quality can match or exceed a single Opus call, still < Opus cost |
| Repeats | exact-match cache | free |
Averaged over the workload, total spend is well below always-Opus and mean accuracy is equal-or-better. Grounded in RouteLLM, FrugalGPT (cascade with a judge), and Mixture-of-Agents.
Modes
Pick the strategy via the model field:
model |
strategy |
|---|---|
router / router-balanced |
triage → single cheap (easy) / single mid (moderate) / Mixture-of-Agents (hard) |
router-cost |
FrugalGPT cascade — answer cheap, a judge scores it, escalate only if low |
router-quality |
bias one tier up — best quality while staying under Opus cost |
Any real model id (claude-opus-4-8, gpt-5.5, …) passes straight through, so
this also works as a plain multi-provider proxy.
How it works
request ─► [cache] ─► [triage: how hard?] ─► [policy] ──► single cheap model (easy)
└─► single mid model (moderate)
└─► Mixture-of-Agents (hard)
proposers ∥ ─► synthesizer
- Triage (
src/switchboard/classify.py) — free heuristics (length, code/math markers, multi-step verbs) decide obvious cases; a tiny LLM classifier scores the ambiguous middle. Output: difficulty 1–5 → tier. - Policy / execution (
src/switchboard/engine.py) —single,moa(parallel proposers + synthesizer), orcascade(cheap → judge → escalate). - Cost accounting — every response carries its internal cost, an estimate of
what always-Opus would have cost, and the savings %, under a
switchboardkey.
Results
On GSM8K (50 items, exact numeric grading), baseline = always claude-opus-4-8:
| config | accuracy | total cost | vs Opus |
|---|---|---|---|
| always-Opus | 100.0% | $0.3674 | baseline |
router-cost |
100.0% | $0.0064 | 57× cheaper — Pareto win |
router-quality |
100.0% | $0.2781 | 1.3× cheaper |
router-balanced |
92.0% | $0.0611 | 6× cheaper but lost accuracy |
Reproduce: python -m bench.run_gsm8k --n 50 --seed 0. Full write-up and honest
caveats in RESULTS.md. (The verifier is what makes routing safe —
router-balanced has none and lost 8 points; router-cost's judge is the fix.)
Limitations & next steps
- Pricing is a list-price proxy (
src/switchboard/config.py). Drop your real rate card intopricing.json({"model": [in_per_1M, out_per_1M]}) to override. - Triage under-detects "deceptively simple" trap questions —
router-cost/router-qualitycompensate via the judge/MoA. - Streaming is simulated (full answer computed, then chunked) — MoA can't token-stream; only the single-model path could truly stream.
- Semantic cache (embed prompt → nearest neighbour) is not yet wired.
- The gateway's
/v1/modelslist may be stale — trustswitchboard models.
License
MIT — see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file switchboard_llm-0.1.0.tar.gz.
File metadata
- Download URL: switchboard_llm-0.1.0.tar.gz
- Upload date:
- Size: 18.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e03bb4ad725dc37bef18d6149fb7c8d1cdaea0c1e08f98da2674fd3de3ffeb5
|
|
| MD5 |
fa70ba7fc33200f923e64a6a6a60f08d
|
|
| BLAKE2b-256 |
2752d37c68040cdccc65f30aa2c374d2a5ccd27ff7bbeaa1aa867df49d73858c
|
File details
Details for the file switchboard_llm-0.1.0-py3-none-any.whl.
File metadata
- Download URL: switchboard_llm-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3bd1215cf5dcc2314eeb6d3fdd91c23b55a9d7f00fe49dca7a85129638391521
|
|
| MD5 |
3b9c4f63c6112072329836bb3363e837
|
|
| BLAKE2b-256 |
399f15db694f99068a7e02f38dde8fbb4052bf7f105bee0ae069f6635e4452f8
|