Vibe Engines
YouTube
AI System Design

Design an LLM Router

Step 1 / 9

Learn AI system design by building an LLM router that sends each request to the cheapest model that can handle it step by step.

The numbers to beatportfoliocheap · flagship · specialistoptimizecost under quality barone endpointclients unaware

The whole design, in writing

Learn AI system design by building an LLM router that sends each request to the cheapest model that can handle it step by step. An interactive guide covering why one-model-for-all is wasteful, classifying query difficulty, tiered routing with a cheap-first cascade, semantic/task routing to specialized models, multi-provider serving with fallback and caching, learning routing from outcomes, and the unhappy paths (misrouting, routing latency, calibration).

Every step of the build above, written out: the problem each piece solves, the option that was taken and the ones that were not, the numbers, and how it fails in production.

The big idea

What does an LLM router do?

Not every request needs your biggest, most expensive model. "Reformat this JSON" and "reason step-by-step through this legal argument" cost the same if you send both to the flagship — but the first could be handled by a model 10–50× cheaper and faster with identical quality. Send everything to the big model and you overpay massively; send everything to the small one and hard queries fail.

Clientmixed requests
New in this step: Client.

An LLM router picks, per request, the cheapest model that can still get it right. It classifies the request's difficulty/type, routes easy ones to a small model and hard ones to the flagship (or a specialist), and escalates when the cheap model isn't confident. Same quality, a fraction of the cost — the routing decision is the whole system.

What the new pieces do

Clientclient
A stream of requests of wildly varying difficulty — from "reformat this" to "reason through this proof". Most are easy.

Step 1 · One model for all is wasteful

The cost/quality bind

The two naive options: route everything to the big model, or everything to the small one. Why is each a bad deal?

Clientmixed requests
The system as it stands at this step.

Send all requests to one model. Big model for all, or small for all — what's wrong with each?

  1. Most requests are easy and don't need a flagship, so big-for-all burns money and latency on trivial work; but small-for-all gives wrong/weak answers on the genuinely hard requests. Since difficulty varies per request, the efficient answer is to match each request to the cheapest model that can handle it.

  2. They're not equivalent trade-offs to ignore — big-for-all can be 10–50× more expensive than necessary on easy queries, and small-for-all degrades quality where it matters. Per-request routing captures the savings without the quality loss.

  3. The big model is more capable but far more expensive and slower, and that capability is wasted on the easy majority. Matching model to request difficulty saves most of the cost at the same quality.

Big-for-all overpays — most requests are easy and don't need flagship capability, so you burn 10–50× the necessary cost and latency on trivial work. Small-for-all fails the hard minority with weak or wrong answers. Because difficulty varies per request, neither fixed choice is efficient: the win is matching each request to the cheapest model that can handle it.

Why this piece earns its place

The distribution argument is true in general and unproven for your traffic, which makes the first deliverable a histogram rather than a router. Sample a week of real prompts, hand-label a few hundred by whether the cheap model's answer would have been acceptable, and you have a ceiling on what routing can save before anyone writes routing code. That number decides the project: a product where nearly every request is a long tool-calling chain has almost no easy head, and a router there is added latency plus one more thing to page someone about. It also decides how you pitch it. The savings land on a bill that nobody using the product ever sees, while the same swap shortens the wait on every request it touches — and the wait is what gets the work funded, because it gives someone outside finance a reason to want this. One warning about the accounting: the price of a request is not the price of the call. A weak answer the user rewords and resends costs two calls plus their patience, so cost per resolved request is the honest denominator.

Step 2 · A decision in front of the models

The router

Put a router between clients and a fleet of models of different cost/capability. Its one job is a decision — but on what basis, and what does it optimize?

Clientmixed requestsRouterpick the model
New in this step: Router.

The router takes each request and chooses which model to send it to, optimizing cost subject to a quality bar: use the cheapest model that will produce an acceptable answer. Behind it sits a portfolio of models — small/cheap, large/flagship, and task specialists, possibly across providers. The router hides this from clients (they call one endpoint) and turns "which model?" into an automatic, per-request optimization.

Why this piece earns its place

The single endpoint is cheap to draw and expensive to honour, because models are not drop-in replacements for one another: system prompts land differently, JSON and tool-call support varies, context windows differ, and a prompt tuned against the flagship can come back malformed from the cheap model. If per-model prompt adaptation lives in the client, every client reimplements it and the portfolio freezes — nobody dares add a model. Keep that adaptation inside the router and it stays a portfolio you can change. Two consequences worth saying out loud. The response has to name the model that answered, in metadata rather than in the body, or a quality complaint is unattributable and per-model cost accounting is guesswork. And routing needs a per-request override — a model pin for evals, reproducible runs, or a flow that is contractually required to name its model — while the default stays automatic; that knob belongs in the request, and the policy mapping difficulty to tiers belongs in config. The long payoff: once no client names a model, you can retire a provider without a client migration.

  • portfoliocheap · flagship · specialist
  • optimizecost under quality bar
  • one endpointclients unaware

What the new pieces do

Routergateway
Decides, per request, the cheapest model that can handle it — then forwards, escalating if the cheap one falls short.

Step 3 · Know the request

The routing signal

To route well, the router needs to estimate before answering how hard (or what type) each request is. Guessing wrong is costly. Where does that signal come from?

Routerpick the modelClassifierdifficulty · task
New in this step: Classifier.

How does the router estimate a request's difficulty/type before answering it?

  1. Calling the expensive model to decide whether to call the expensive model defeats the purpose. The signal must come from something much cheaper than the models you're routing to.

  2. Random routing sends hard queries to weak models and easy ones to the flagship — the worst of both. You need an actual difficulty/type estimate.

  3. A lightweight step — keyword/length heuristics, a small classifier model, or embedding-similarity to labeled examples — estimates the request's difficulty and/or task type quickly and cheaply. That score is the signal the routing decision is built on, and it must cost far less than the models it routes to.

Add a fast, cheap classifier that estimates each request's difficulty and/or task type before answering — via heuristics (length, keywords), a small classifier model, or embedding-similarity to labeled examples. Crucially it must cost far less than the models it routes to, or the routing overhead eats the savings. This score is the signal every routing decision stands on: get it right and you route cheaply and correctly; get it wrong and you misroute.

Why this piece earns its place

Most of the accuracy here comes from what you hand the classifier, not from which classifier you pick. The unit is the whole request, not the last user message: a later turn carrying tool results and a long system prompt is not the difficulty of the sentence at the end of it. The cheapest signals sit outside the text entirely — how many turns deep this is, which tools are attached, whether the caller demanded a strict output schema — and they separate easy from hard about as well as the prose does, for nothing. Where the classifier runs matters as much as what it is. Put it behind a network call and every request pays a round trip before any work starts, and its tail latency lands on every request's tail rather than its median — so a blunter classifier with constant cost beats a sharper one that occasionally stalls. A small classifier also has a small context window, so long requests get scored on a prefix: the window forces that, not the bill. Treat having to truncate a request in order to classify it as a difficulty signal in its own right.

  • classifierdifficulty + task
  • cheap« target models
  • signalthe routing basis

What the new pieces do

Classifierservice
A fast, cheap step that estimates the request's difficulty/type — the signal the routing decision is built on.

Step 4 · Cheap first, escalate

Tiered routing & the cascade

With a difficulty signal, you route easy→cheap and hard→flagship. But the classifier isn't perfect, and some "medium" requests are ambiguous. How do you route them without either overpaying or under-serving?

Routerclassify → routeCheap Modelsmall · fastStrong Modelflagship
New in this step: Cheap Model, Strong Model.

Use tiered routing plus a cascade: send a request to the cheapest model that the classifier thinks can handle it, and — for ambiguous cases — try cheap first, then escalate to a stronger model if the cheap answer is low-confidence or fails a check. The cascade catches classifier mistakes: most requests finish cheaply, only the ones that truly need it pay for the flagship. Balance the escalation threshold so you don't escalate everything (cost) or nothing (quality).

Why this piece earns its place

The cascade's real cost is not the second call, it is serial latency landing on the requests that were already hardest: an escalated request waits out the cheap model, then the check, then the strong model. Budget for that in the tail, and let a caller cap it — a request that cannot afford two round trips should say so and go straight to the strong tier. It also collides with streaming. You cannot unsend tokens, so a cheap answer already streaming cannot be withdrawn when the check fails. Either you buffer the cheap answer until the check passes, which pushes time-to-first-token out to the cheap model's full completion time on every request in order to protect the minority that will fail it, or you do not cascade on streaming endpoints and route them on the classifier alone. Deciding that per endpoint is the honest answer. Escalation rate is worth watching for a reason beyond cost, too: it moves when traffic shifts, and it moves when a provider silently repoints an alias at a new build of the cheap model — often the only warning you get that the model underneath you changed.

  • tierscheap → flagship
  • cascadetry cheap, escalate
  • thresholdtune escalate rate

What the new pieces do

Cheap Modelservice
Handles the easy majority at a fraction of the cost/latency of the flagship model.
Strong Modelmint
The expensive, capable model reserved for the genuinely hard requests (and escalations from the cheap tier).

Step 5 · The right specialist

Semantic / task routing

Difficulty isn't the only axis. A coding request is best served by a code model; a medical question by a domain fine-tune; a translation by a multilingual model. How do you route by what the task is, not just how hard it is?

Routerclassify → routeStrong ModelflagshipSpecialistcode · domain
New in this step: Specialist.

Add semantic / task routing: use the classifier (or embeddings) to detect the request's domain/task and route to a specialist model that beats a general model on that slice — a code model for code, a fine-tuned model for your domain, a cheap multilingual model for translation. A specialist can be both cheaper and better on its niche than a general flagship, so task routing improves cost and quality simultaneously where a specialist exists.

Why this piece earns its place

Cheaper and better is a claim about one request, and on its slice it holds. What it does not price is the standing cost of keeping that specialist in the portfolio. Each one is its own prompt template, its own output quirks, its own eval set, its own deprecation notice to absorb. Two are a clean win; eight are eight prompt suites drifting at different rates, and the router's quality becomes the minimum across all of them. Add one when a slice is large enough that its savings pay for that maintenance, not because a good model exists. Where the specialist runs changes the arithmetic as well. A hosted code or translation model bills per token like everything else on this page, so it costs nothing on the days nobody asks for it. A domain fine-tune you host yourself is rented capacity instead — an idle replica costs the same at three in the morning as at peak — so that kind of specialist only pays above a traffic floor for its slice, and that floor is a number worth asking for before you commit. And decide the ambiguous case deliberately: a request that is half code and half prose has no clear owner, so when task detection is not confident, fall back to the general tier rather than the nearest specialist.

  • task detectcode/medical/lang
  • specialistbeats general on niche
  • cheaper + betteron its slice

What the new pieces do

Specialistservice
A model tuned for a specific task/domain (code, medical, a fine-tune) that beats a general model on that slice.

Step 6 · Serve it robustly

Multi-provider, fallback & caching

In production the router fronts many models across providers, any of which can be slow, rate-limited, or down — and many requests repeat. How does the router stay fast, cheap and reliable?

Cacherepeated promptsRouterclassify → route
New in this step: Cache.

The chosen cheap model's provider is rate-limiting you mid-traffic spike. What should the router do?

  1. Queuing adds unbounded latency to a request that could be served right now by an equivalent model elsewhere — the user pays for a provider-side problem they had nothing to do with.

  2. The router already tracks which models are interchangeable for a given tier/task, so a rate-limited or down provider is just one option going temporarily unavailable — route to the next-best equivalent and keep serving.

  3. Pushing the failure back to the caller defeats the point of a router that is supposed to hide model/provider details behind one endpoint — resilience belongs in the routing layer, not in every client.

Make the router a resilient gateway over a model portfolio: cache repeated/identical prompts (the cheapest route — no model call at all), and add fallback so if the chosen model/provider is down or rate-limited, it fails over to an equivalent alternate (another provider or tier). Track per-model cost, latency and health to inform routing, and support multi-provider so you're not locked in and can route to whoever is cheapest/fastest for a given request. Routing + caching + fallback together cut cost and raise reliability.

Why this piece earns its place

Failover is the one routing decision made under pressure, and it quietly bypasses the quality bar everything else is held to. "These two are interchangeable for this tier" is a hand-written claim that rots as both sides ship new versions, so every equivalence pair needs its own eval — otherwise an outage becomes a quality incident that nobody records as one, because the dashboard shows requests served. Mechanically, health tracking has to be a circuit breaker with a half-open probe, not a retry rule. Without the breaker, every request during an outage pays the full timeout before failing over, so a degraded provider costs latency across all traffic instead of losing you one tier; with it, a single probe pays that cost and everything else routes straight past. The cache needs a flag of its own. How repetitive your traffic is sets the ceiling on hit rate; your prompt template decides whether you get to collect any of it. A template that interpolates a user id, a timestamp or a rolling session summary never produces an identical prompt twice, so it zeroes out whatever repetition the traffic had. Confirm the cached text can repeat before tuning anything else about the cache.

  • cacheno-call route
  • fallbackprovider/tier failover
  • multi-providerno lock-in

What the new pieces do

Cachecache
Serves repeated/identical prompts without calling any model — the cheapest route of all.

Step 7 · Get smarter over time

Learn routing from outcomes

A static router is only as good as its initial classifier — and model prices, capabilities and your traffic all change. How does routing stay optimal?

Classifierdifficulty · taskFeedbackcalibrate
New in this step: Feedback.

Close a feedback loop: log each request's route, cost, and an outcome signal (was the cheap answer good enough? did it escalate? user thumbs / eval score), and use it to recalibrate the classifier and the escalation thresholds — learning which requests the cheap model actually handles well and which truly need the flagship. Re-tune as models and prices change (a new cheap model may now cover cases that used to escalate). The router improves as it observes what routing decisions actually worked.

Why this piece earns its place

The feedback this loop collects is censored, and that is the flaw to name out loud. You only ever observe the outcome of the model you actually picked, so the log tells you how often escalation was needed but never which flagship-routed requests the cheap model could have handled. Train on it as it stands and the classifier's frontier freezes wherever day one put it, growing more confident about a boundary it no longer tests. The fix is deliberate exploration: route a small slice against policy, or — cheaper and safer — shadow the cheap model offline on flagship traffic and compare the two answers after the fact. That work is off the request path, so it batches and runs on spare capacity. Version the policy alongside every logged decision, because routing shapes the data it is later trained on: a threshold edit shifts the whole distribution of what you observe, and without a policy version in the row you cannot separate a model regression from a routing change you made last Tuesday.

  • logroute + cost + outcome
  • calibrateclassifier + thresholds
  • adaptto new models/prices

What the new pieces do

Feedbackservice
Learns from outcomes (was the cheap answer good enough?) to recalibrate the classifier and routing thresholds.

Step 8 · The sharp edges

Misrouting, latency & over-optimization

Routing has real risks: misrouting a hard query to a weak model (bad answer), the routing step's own latency/cost, keeping the classifier calibrated, and over-optimizing cost at the expense of quality.

ClientCacheRouterClassifierCheap ModelStrong ModelSpecialistFeedback
The system as it stands at this step. · swipe to pan the diagram

Bound misrouting damage with the cascade (escalate on low confidence) and a conservative bias on high-stakes requests (when unsure, route up). Keep the routing overhead tiny — the classifier must be far cheaper/faster than the models, or it negates the savings (and adds latency to every request). Continuously calibrate against a real quality metric so the cost/quality trade stays where you want it. And don't over-optimize cost: define the quality bar first, then minimize cost under it — a router that saves money by quietly degrading answers is a failure, not a win.

Why this piece earns its place

The two failures here do not surface the same way, and that asymmetry is the whole problem. Cost arrives as a number, on a schedule, in front of somebody whose job is to read it. Quality arrives as an anecdote in a support queue, months later, attached to no particular release. A router drifts in the direction only one of those two can see. Two habits make the other one visible. First, stop relying on route up when unsure to protect the requests that matter: it fires only when the classifier knows it is unsure, and the misroutes that end in a complaint are the confident ones. Name the surfaces where a cheap answer is never acceptable and pin them to the strong tier in config, so protection is a list someone can read rather than a judgement made per request. Second, measure savings against a fixed basket of replayed requests, not against last month's bill — cost per request also falls when traffic gets easier, and a router credited for a seasonal shift will be blamed for the next one. That basket is also how you price a threshold edit before it ships, which matters because a threshold is one line of config that moves the quality of all traffic the moment it lands.

You did it

You just designed an LLM router.

ClientCacheRouterClassifierCheap ModelStrong ModelSpecialistFeedback
The finished design, end to end. · swipe to pan the diagram

Everything you assembled, in order

  • One-model-for-all is a bind: big overpays on the easy majority, small fails the hard minority.
  • A router picks the cheapest model that meets a quality bar, per request, over a model portfolio.
  • A fast, cheap classifier estimates difficulty/task — the signal every routing decision stands on.
  • Tiered routing + a cheap-first cascade (escalate on low confidence) make routing robust to classifier errors.
  • Semantic/task routing to specialists is cheaper AND better on their niche — a pure win.
  • Serve robustly: cache repeats, fail over across providers, and track cost/latency/health.
  • Learn routing from outcomes; keep the quality bar first and minimize cost under it (don't degrade quality).

Where an interviewer pokes next

Getting the boxes right is the easy half. These are the questions that separate a candidate who drew the diagram from one who has run the thing. Answer each one out loud before you open it.

  1. Why is routing better than just always using the biggest model or always the smallest?

    Because request difficulty varies enormously and follows a distribution — a large head of easy requests (reformatting, simple classification, short factual answers) and a smaller tail of genuinely hard ones (multi-step reasoning, nuanced analysis). Always using the biggest model means you pay flagship prices and latency on that easy majority, where a model 10 to 50 times cheaper and faster would produce an identical-quality answer, so you overpay massively. Always using the smallest model saves money but produces weak or wrong answers on the hard tail where capability actually matters. Neither fixed choice is efficient because no single model is optimal across the whole difficulty distribution. A router exploits the distribution by matching each request to the cheapest model that can still meet the quality bar — cheap capacity for the head, expensive capacity reserved only for the tail — capturing most of the cost savings of "small" without the quality loss, and most of the quality of "big" without the cost. The savings come precisely from the fact that most traffic is easy, so serving it cheaply while escalating only the hard minority dramatically lowers average cost at the same quality.

  2. Where does the routing signal come from, and why must it be cheap?

    The router needs to estimate, before generating an answer, how hard a request is and/or what type of task it is, so it can pick an appropriate model. That estimate — the routing signal — comes from a fast, lightweight classifier, which can be simple heuristics (length, keywords, structure), a small dedicated classifier model, or embedding-similarity of the request to a set of labeled example requests whose ideal routing is known. The critical constraint is that this signal must cost far less than the models it routes to. If you used the expensive flagship model to decide whether to use the expensive flagship model, you'd gain nothing; worse, any routing step adds latency and cost to every single request, so if the classifier isn't dramatically cheaper and faster than the target models, the routing overhead simply eats the savings you were trying to capture (and slows everything down). This is exactly why small models and embeddings make good routers — they're cheap enough per request that the classification tax is negligible relative to the cost difference between the cheap and expensive target models. The signal also needs to be reasonably accurate, because a wrong estimate causes misrouting, which the cascade then has to catch.

  3. What is the cheap-first cascade and why is it important given an imperfect classifier?

    The cascade is a robustness mechanism that compensates for the fact that the difficulty classifier is never perfect. Pure tiered routing sends each request directly to the model the classifier picks, but for ambiguous or misclassified requests that can send a hard query to a model too weak to answer it well. The cascade instead tries the cheap model first, then verifies the result — using a confidence score, a self-consistency check, a validator, or a quick quality signal — and escalates to a stronger model only if the cheap answer is low-confidence or fails the check. The effect is that most requests finish cheaply on the small model, and only the ones that genuinely need more capability incur the flagship cost, while classifier mistakes get caught and recovered rather than silently producing bad answers. The key tuning knob is the escalation threshold: set it too aggressive and you escalate almost everything, losing the cost savings; too lax and you keep low-quality cheap answers, losing quality. Tuned well, the cascade acts as a safety net that keeps average cost low while protecting quality against both classifier errors and genuinely hard tail requests, which is why it's central to a robust router rather than relying on the classifier alone.

  4. How does routing by task (semantic routing) differ from routing by difficulty, and why can it improve both cost and quality?

    Routing by difficulty chooses a model tier based on how hard the request is — cheap for easy, flagship for hard. Semantic or task routing chooses based on what kind of task the request is, sending it to a model specialized for that domain: a code-specific model for programming, a fine-tuned domain model for medical or legal questions, a cheap multilingual model for translation, and so on. The reason this can improve cost and quality simultaneously — unlike difficulty routing, which trades some quality risk for cost savings — is that a specialist model is often both cheaper and better than a general flagship on its particular niche. A well-tuned code model or a domain fine-tune can outperform a much larger general model on its slice while costing less to run, so detecting the task type and routing to the right specialist is close to a pure win: you get a better answer for less money. This means the router really operates on two axes at once — how hard the request is (which tier) and what kind of request it is (which specialist) — and it picks not just a model size but a model fit, using the classifier or embeddings to detect the domain/task and route accordingly.

  5. What's the failure mode to avoid when optimizing a router, and how do you guard against it?

    The central failure mode is over-optimizing for cost at the expense of quality — building a router that looks great on the cost dashboard because it saves money by quietly routing too many requests to weak models and producing degraded answers. That's not a win, it's a silent regression, because the entire premise of a router is real savings at the same quality, not cheaper answers that are worse. The guard is to invert the optimization: define the acceptable quality bar first, and then minimize cost subject to meeting that bar, rather than minimizing cost and hoping quality holds. Concretely, that means measuring quality continuously against a real metric (evals, user feedback, task success), calibrating the classifier and escalation thresholds so the cost/quality trade stays where you intend, biasing conservatively on high-stakes requests (route up when unsure), and using the cascade to catch misroutes. You also keep the routing overhead itself tiny so it doesn't erode the savings or add latency, and you re-tune as models and prices change so the router adapts instead of drifting. The discipline throughout is that quality is the constraint and cost is what you minimize under it — never the reverse.

Check yourself — the answers, and why

Nine steps in, these are the calls you should be able to make cold. Pick one, then read why.

  1. An LLM router exists because…

    Big-for-all overpays on the easy majority; small-for-all fails the hard minority.

  2. The routing signal comes from…

    Heuristics, a small model, or embeddings — the signal must cost far less than what it routes to.

  3. The cheap-first cascade exists to…

    Most requests finish cheaply; only ones that truly need it pay for the flagship.

  4. Semantic/task routing to a specialist can be…

    A tuned/purpose-built model often dominates a general one on its task at lower cost — a pure win.

  5. The failure mode to avoid is…

    A router that saves money by getting answers wrong is a failure; quality is the constraint.

How you’d open this design in an interview

Before any boxes: agree what it must do, pin the qualities that shape everything, then build — naming each trade-off as you make it. The walkthrough above is that exact order.

What it must do

Agree on these before drawing a single box.

  • Route: pick, per request, the cheapest model that still meets the quality bar.
  • Classify: a fast, cheap step estimates each request’s difficulty and task type.
  • Escalate: try the cheap model first, then escalate to a stronger one on low confidence.
  • Specialize: route by task to a specialist model (code, domain) that beats a general one on its niche.
  • Serve + learn: cache repeats, fail over across providers, and recalibrate from outcomes.

The qualities that shape everything

Each one names the mechanism that buys it.

Same quality at a fraction of the cost
The router picks, per request, the cheapest model that still meets a quality bar — cheap capacity for the easy head, the flagship only for the hard tail.
Decide before answering, without paying flagship prices to decide
A fast, cheap classifier (heuristics, a small model, or embeddings) estimates difficulty/task for far less than the models it routes to.
Robustness to an imperfect classifier
A cheap-first cascade tries the cheap model, verifies (confidence/self-consistency/validator), and escalates only on low confidence.
Cheaper AND better on a niche
Semantic/task routing sends code/domain/translation requests to a specialist that beats a general flagship on its slice at lower cost.
Stay fast, cheap and reliable in production
Cache repeated prompts (no model call), fail over to an equivalent model when a provider is down or rate-limited, and track per-model cost/latency/health.
Stay optimal as models, prices and traffic shift
A feedback loop logs route + cost + outcome and recalibrates the classifier and escalation thresholds.

The trade-offs you say out loud

Senior signal isn’t the boxes — it’s naming what you gave up and why it was the right price.

The cheapest capable model per request over one model for all (big or small)

Big-for-all burns 10–50× the cost on the easy majority; small-for-all fails the hard minority — since difficulty varies per request, matching each to the cheapest capable model captures the savings without the quality loss.

A cheap difficulty classifier over asking the big model first

Calling the expensive model to decide whether to call it defeats the purpose; a lightweight classifier (heuristics, a small model, or embeddings) must cost far less than the models it routes to.

A cheap-first cascade over trusting the classifier’s pick directly

Pure tiered routing sends a misclassified hard query to a too-weak model; trying cheap first and escalating on low confidence catches classifier mistakes while keeping average cost low.

Fail over to an equivalent model over queuing until the rate limit clears

Queuing adds unbounded latency for a provider-side problem; the router already tracks interchangeable models, so it routes to the next-best equivalent and keeps serving.

Quality bar first, cost second over minimizing cost blindly

A router that saves money by quietly routing hard queries to weak models is a silent regression, not a win; define the quality bar, then minimize cost under it.

The answer, out loud

What a strong answer to “Design an LLM Router” sounds like, first question to last trade-off. It is about 7 minutes of talking; the whiteboard and the interviewer fill the rest of the 45. Read it aloud once, then close the page and give it yourself.

  1. 0–3 min

    What kind of decision this is

    Before I draw anything, I want to name what kind of decision this is. Today the product picks a model once, up front, and applies that pick to every request it ever serves. But the requests aren’t alike — a few genuinely need the best model we have and most don’t — so we’re paying one price for work of wildly different value. What I’d design is that same choice, made per request, automatically. And I’d fix the direction of the optimisation up front, because it decides every knob after it: I’m holding answer quality where it is and taking cost out from underneath it, not chasing a cheaper bill and hoping quality survives.

  2. 3–8 min

    One decision in front of many models

    Both obvious designs fail. The big model for everything overpays on the easy majority; the small model for everything gets the hard minority wrong. Difficulty varies per request, so no single model suits both ends. My skeleton is a client, a router, and behind it a portfolio of models: a small cheap one, an expensive flagship and task specialists. Clients call one endpoint and never know which model answered. I’d frame the router’s job precisely: minimize cost subject to a quality bar. Where that bar sits is a business dial.

    Built in step 2: The router
  3. 8–14 min

    Where the routing signal comes from

    The router has to judge how hard a request is before anything answers it, and I’d fix the shape of that judgement before picking a technique. It runs in front of every single request, so it gets a budget first — a small slice of the cost of the call it’s deciding about, plus whatever latency I’ll add to all traffic — and then I take whichever signal fits inside it. That rules out the tempting move of asking a strong model to triage. I’d ship the crudest version that separates the extremes rather than block on a trained one. And I’d have it emit a score rather than a verdict, with the thresholds that turn a score into a route living in config — so retuning the router never means retraining anything.

    Built in step 3: The routing signal
  4. 14–20 min

    Cheap first, then escalate

    The classifier will be wrong sometimes, and if I route on its verdict alone, a hard request it called easy quietly gets a worse answer — nothing errors, so nobody finds out. So for anything ambiguous I’d try the cheap model first, check what comes back with a confidence score, a self-consistency check or a validator, and escalate to the stronger model only when that check fails. What I give up is that an escalated request pays for two calls and two waits — so escalation rate is the number I’d hold this design to. If it climbs, the cascade has quietly become the flagship with extra steps.

    Built in step 4: Tiered routing & the cascade
  5. 20–25 min

    Route by kind, not only by difficulty

    Difficulty isn’t the only axis. Code is best served by a code model, a domain question by a fine-tune on that domain, translation by a cheap multilingual model. So the classifier, or embeddings, also detects the task and routes to a specialist. This is where routing can be cheaper and better at once, because a specialist often beats a general flagship on its slice. The catch: it only pays where a good specialist exists, and task detection is one more thing the classifier can get wrong.

    Built in step 5: Semantic / task routing
  6. 25–31 min

    Serve it like a gateway

    In production these models sit behind providers that have their own bad days. Say the cheap model’s provider starts rate-limiting us mid-spike: I’d have the router move that traffic to an equivalent model elsewhere. What that costs is state — an explicit claim about which models are interchangeable for a given tier or task, plus live health, latency and cost for each one. And the equivalent it lands on is usually a different price point, so a provider incident quietly becomes a line on the bill. I’d cache identical prompts too, because a hit is the cheapest route in the design: no model call at all. This is where the router stops being a routing idea and becomes infrastructure somebody operates.

    Built in step 6: Multi-provider, fallback & caching
  7. 31–36 min

    Learn from what actually happened

    A router is only as good as its day-one classifier, and prices, models and traffic keep moving. So I’d close a feedback loop: log each request’s route, cost and an outcome signal — did it escalate, a user thumbs-down, an eval score — and recalibrate the classifier and the escalation thresholds from it. Routing is a prediction, “will the cheap model suffice?”, so it improves with feedback. When a new cheap model ships, that data shows which requests it can now take.

    Built in step 7: Learn routing from outcomes
  8. 36–42 min

    What I’d watch, and how it fails

    On the dashboard: cost per request, escalation rate, the routing step’s own latency, per-model health, and a real quality metric against the bar. The failures I’d plan for first. The classifier goes down and the router can’t tell easy from hard — I’d fail safe and send everything to the flagship, accepting flagship prices until it recovers. Or the strong model goes down and hard requests have nowhere to escalate, so the escalation path needs its own fallback. And the quiet one, misrouting: on high-stakes requests, when unsure, route up.

  9. 42–45 min

    Close on the trade-off

    So: one endpoint, a cheap decision in front of a portfolio of models, and a loop that keeps checking that decision against what happened. Every knob on it trades cost against quality, and the one thing I wouldn’t move is which of the two is the constraint: quality is what I hold fixed, cost is what I minimise underneath it. Backwards, and the thing optimises itself into a cheaper, worse product while the numbers it reports keep moving the right way. With more time that’s where I’d spend it — on measuring answer quality continuously, through evals, user feedback and task success — because every threshold here is only as honest as the quality number it’s tuned against.

What this teaches

Learn AI system design by building an LLM router that sends each request to the cheapest model that can handle it step by step. An interactive guide covering why one-model-for-all is wasteful, classifying query difficulty, tiered routing with a cheap-first cascade, semantic/task routing to specialized models, multi-provider serving with fallback and caching, learning routing from outcomes, and the unhappy paths (misrouting, routing latency, calibration).

Key takeaways

  • One-model-for-all is a bind: big overpays on the easy majority, small fails the hard minority.
  • A router picks the cheapest model that meets a quality bar, per request, over a model portfolio.
  • A fast, cheap classifier estimates difficulty/task — the signal every routing decision stands on.
  • Tiered routing + a cheap-first cascade (escalate on low confidence) make routing robust to classifier errors.
  • Semantic/task routing to specialists is cheaper AND better on their niche — a pure win.
  • Serve robustly: cache repeats, fail over across providers, and track cost/latency/health.
  • Learn routing from outcomes; keep the quality bar first and minimize cost under it (don't degrade quality).

Concepts covered

  • What does an LLM router do?
  • The cost/quality bind
  • The router
  • The routing signal
  • Tiered routing & the cascade
  • Semantic / task routing
  • Multi-provider, fallback & caching
  • Learn routing from outcomes
  • Misrouting, latency & over-optimization
RUN IT YOURSELF

The two different things a confidence score of 1.00 can mean

The cascade from step 4, running for real: ask the cheap model, re-ask it SAMPLES times, keep the answer only if the samples agree past a gate. It serves the same 240 requests three ways and prints the errors that survive the gate at agreement 1.00 split into the two causes that behave differently — bias (one reading, one rule) and luck (every sample landed on the same reading). Change SAMPLES, hit Run, and watch one column collapse while the other does not move.

HOW TO READ THE CODE — 5 IDEAS
  1. Neither fixed choice is efficient: the flagship costs 13x what the cheap tier bills here, the cheap tier gets 108 of 240 wrong (step 1).
  2. The gate is worth having — it escalates 35% of traffic, catches 77 of those 108, and runs at 61% of the flagship bill (step 4).
  3. 31 survive it at agreement 1.00. Raising SAMPLES to 9 leaves 17: the luck column falls 95 → 15 → 1, the bias column reads 16 16 16.
  4. Bias is a floor because it is a rule: a request inside the cheap model's window has one reading, so every sample returns the same wrong answer (step 3).
  5. 92% of the saving is bought on exactly those one-reading requests — the money and the silent errors are the same fact, not two (step 8).
CPython · WebAssembly
built to stop paying flagship prices for "hi, thanks" — make the calls, blind the router, run the gauntlet.
Finished this one? 0 / 65 AI System Designs done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.

More AI System Designs