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

Dimensionroute-switchLiteLLMPortkeyOpenRouter
ArchitectureLearned routerGatewayGatewayManaged gateway
Routing logicMIPROv2-tuned policy on your trafficConfigured rulesConfigured rules + observabilityConfigured rules + provider choice
SetupSelf-hosted Go servicePython library or proxySelf-hosted Node serviceManaged (no infra)
ObservabilityFirst-class (per-routing-decision metrics)LimitedFirst-classFirst-class
CachingBring your ownBring your ownYesYes
Cost optimisationEmpirical (MIPROv2)Manual (per-model price config)Manual (budget controls)Manual (provider choice)
FallbacksYes (configurable)Yes (configurable)Yes (configurable)Yes (provider failover)
Custom policiesYes (any Go function)LimitedLimitedNo
Multi-providerYesYes (100+)YesYes
LicenseMITMITMITProprietary
CostFree (self-host)Free (self-host)Free (self-host) + paid cloudPay-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 quality
  • claude-3-5-sonnet ($3/1M input, $15/1M output) — high quality
  • gpt-4o-mini ($0.15/1M input, $0.60/1M output) — low quality
  • claude-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.