route-switch vs LiteLLM vs Portkey vs OpenRouter: LLM Routing in 2026
A practical comparison of route-switch, LiteLLM, Portkey, and OpenRouter for LLM routing — when you need a gateway, when you need a learned router, and when you need both.
The question
Should I use route-switch, LiteLLM, Portkey, or OpenRouter for LLM routing?
LLM routing has split into two distinct architectures: gateways (LiteLLM, Portkey, OpenRouter) that route requests to the configured provider, and learned routers (route-switch) that learn the routing policy. This post is the comparison we wish we had when we started route-switch.
The 60-second version: gateways are for routing to a configured provider. Learned routers are for routing that learns the policy from your traffic. They solve different problems.
What each option is
route-switch is a cost-quality LLM router in Go that uses MIPROv2 to automatically tune per-model prompts against your live traffic. It treats routing as a learning problem and solves it empirically.
LiteLLM is a Python library / proxy that provides a unified OpenAI-compatible interface to 100+ LLM providers. The de-facto choice for “just call any LLM from any client.”
Portkey is an LLM gateway with first-class observability, caching, fallbacks, and budget controls. Production-grade gateway for teams that need observability, not learning.
OpenRouter is a managed LLM router that aggregates multiple providers with a unified API, billing, and failover. The de-facto choice for “I want one API key for many providers.”
The five dimensions
| Dimension | route-switch | LiteLLM | Portkey | OpenRouter |
|---|---|---|---|---|
| Architecture | Learned router | Gateway | Gateway | Managed gateway |
| Routing logic | MIPROv2-tuned policy on your traffic | Configured rules | Configured rules + observability | Configured rules + provider choice |
| Setup | Self-hosted Go service | Python library or proxy | Self-hosted Node service | Managed (no infra) |
| Observability | First-class (per-routing-decision metrics) | Limited | First-class | First-class |
| Caching | Bring your own | Bring your own | Yes | Yes |
| Cost optimisation | Empirical (MIPROv2) | Manual (per-model price config) | Manual (budget controls) | Manual (provider choice) |
| Fallbacks | Yes (configurable) | Yes (configurable) | Yes (configurable) | Yes (provider failover) |
| Custom policies | Yes (any Go function) | Limited | Limited | No |
| Multi-provider | Yes | Yes (100+) | Yes | Yes |
| License | MIT | MIT | MIT | Proprietary |
| Cost | Free (self-host) | Free (self-host) | Free (self-host) + paid cloud | Pay-per-token |
When to use which
Use route-switch when:
- You have enough traffic (say, > 1K routing decisions per day) to train a learned router.
- The cost-quality frontier matters and you want to empirically optimise it against your actual workload.
- You have multiple models (small, medium, large) and the right choice depends on the query content.
- You want the routing policy to improve as your traffic grows.
Use LiteLLM when:
- You want one OpenAI-compatible API for many providers.
- You are prototyping and want the lowest setup cost.
- The routing logic is “send this request to this model” (no learned policy needed).
Use Portkey when:
- You need a production-grade gateway with first-class observability.
- You have a team that wants to monitor LLM costs and quality per request.
- You are not yet ready for learned routing.
Use OpenRouter when:
- You want a managed service and don’t want to run infrastructure.
- You are willing to pay per-token for the operational simplicity.
The cost-quality frontier
The core problem LLM routing solves: you have multiple models with different costs and qualities. For each query, you want to pick the model that gives the right quality at the lowest cost. The mapping from query to model is the routing policy.
- A gateway (LiteLLM, Portkey, OpenRouter) implements the routing policy in configuration. You set “this request type goes to this model” by hand. The policy is static; it doesn’t improve.
- A learned router (route-switch) learns the routing policy from your traffic. The policy improves as more queries flow through it. The empirical cost-quality frontier is discovered by the optimiser, not specified by the engineer.
The learned approach wins when the routing is non-trivial (multiple query types, multiple model options, time-varying cost-quality) and the traffic volume is sufficient to train. It loses when the routing is trivial (one model for everything) or the traffic is too low to learn from.
A concrete example: cost-quality on a RAG workload
Say you have a RAG application with 50K requests/day, and you want to route them to one of three models:
gpt-4o($5/1M input, $15/1M output) — high qualityclaude-3-5-sonnet($3/1M input, $15/1M output) — high qualitygpt-4o-mini($0.15/1M input, $0.60/1M output) — low qualityclaude-3-5-haiku($0.80/1M input, $4/1M output) — low quality
The naive approach: send everything to gpt-4o, get high quality, pay top dollar.
A gateway approach: hand-write a rule “simple queries go to mini, complex queries go to gpt-4o”. The rule is brittle and stale.
A learned router approach: route-switch looks at your last 30 days of traffic, fits a MIPROv2 policy that picks the model based on query features, and optimises against your quality metric (e.g. user thumbs-up rate, or LLM-as-judge score). The empirical result: same quality as gpt-4o-everything, but 60-80% of the cost.
When NOT to use a learned router
- Low traffic. route-switch needs ~1K decisions to train. Below that, the policy is high-variance.
- Single model. If you only call one model, there’s no routing problem.
- No quality signal. Learned routing optimises against a quality metric. If you don’t have one (user feedback, LLM-as-judge, evals), the optimisation target is undefined.
- Compliance constraints. If the routing decision must be explainable (“this query went to this model because of rule X”), a learned policy is harder to audit than a configured rule.
A 5-minute route-switch eval
// Embed route-switch as a Go library
import "github.com/Skelf-Research/route-switch"
router, err := route_switch.New(
route_switch.WithModels([]string{"gpt-4o", "claude-3-5-sonnet", "gpt-4o-mini"}),
route_switch.WithQualitySignal(route_switch.UserThumbsUpSignal{}),
route_switch.WithMIPROv2Config(route_switch.MIPROv2Config{
NumCandidates: 50,
NumTrials: 100,
}),
)
if err != nil { log.Fatal(err) }
// Route a request
decision, err := router.Route(route_switch.Request{
Query: "What is the capital of France?",
Metadata: map[string]any{"user_tier": "free"},
})
// decision.Model == "gpt-4o-mini" (cheap, this is a simple query)
After 1K requests, run router.Report() to see the
empirical cost-quality frontier. Adjust the quality
signal or the model list to explore the trade-off.
What to read next
- Intelligent LLM Routing: Spending Compute Where It Matters — the full route-switch architecture
- Formalising Prompts as First-Class Research Objects — the promptel side of the same architecture
- route-switch repository
- LiteLLM
- Portkey
- OpenRouter