Kousigan A

LLM model routing: test on evals, pin it, fall back safely

Choose an LLM per task with your own eval set, route by task type, classifier or failed check, keep fallbacks on the same contract, and pin versions.

Last verified against official docs on

To choose a model for each task in an LLM (large language model) application, test the candidates on your own eval set, smallest first. Route each request by task type, a classifier or a failed check, and keep a tested fallback for errors and rate limits. Pin every model version, and run the evals again when a model changes or is deprecated.

The provider behaviour below comes from OpenAI’s, Anthropic’s and Google’s official docs, last verified on 1 November 2026. None of it was tested for this post.

This is part 27 of the series. Part 26 built the eval set, and this part uses it to compare models. Cost per successful task is in Part 22, so here it gets one row.

Write down what each task needs before you compare models

Model choice starts from the task, and one LLM application usually has several. A made-up support assistant labels tickets, pulls order details into JSON, answers customer questions and drafts replies. Those jobs need different things, so each gets its own answers to these questions.

Criterion The question for each task Where the answer comes from
Task difficulty A label, an extraction, or reasoning over many steps? Your own task list
Quality Does the model pass this task’s eval cases, run more than once? Your eval set (Part 26)
Latency Is a user waiting, or can the task run in the background? Your product requirement, measured on your own traffic
Cost What does one passed task cost, failed attempts included? Cost per successful task (Part 22)
Context window Do your largest real inputs fit, with room for the answer? The provider’s model page or models API
Output format and tools Does the model support the structured output and tools this task uses? The feature’s supported-models list
Data and region May this data go to this provider, and to which region? Your contracts, and the provider’s platform docs

Agree on the pass bar before any model runs. OpenAI’s model selection guide says to “Choose acceptance criteria before comparing models”.

Context window and feature support are documented facts, so read them from the provider. Anthropic’s Models API returns max_input_tokens, max_tokens and a capabilities object for every available model. The context window has to hold the prompt, any retrieved text and the answer, so test with your biggest real input.

Test the smaller model first on your eval set

Start with the smallest model that might do the task, and move up when the eval set shows a gap. Anthropic’s choosing a model guide calls this starting “efficiency-first”, one of the two starting points it describes. Its last step is “Upgrade only if necessary for specific capability gaps.” The same page says “Having a good evaluation set is the most important step in the process.”

OpenAI’s model selection guide gives similar advice: “If you can, experiment using the same inputs to compare results and keep the lightest setting that meets your quality bar.”

An example comparison run, one task at a time:

  1. Freeze the inputs: the eval set version, the prompt, the output schema and the settings.
  2. Run each candidate on that task’s cases more than once, because outputs vary between runs.
  3. Compare pass rates for the task, then read the failing cases one by one.
  4. Add cost per successful task and latency from the same runs.
  5. Keep the lightest model that clears the bar, and write the choice down.

Try a lower effort setting before you try a bigger model. Anthropic’s guide says “Tuning effort is often a better lever than switching models.” OpenAI’s guide ends its comparison steps with “vary reasoning effort while keeping the model and inputs fixed.”

A written record of each choice helps later, when the model is retired and nobody remembers why it was picked. An example record, with names made up for this post:

model-choices.yaml (example)
ticket_label:
  model: small-model-2026-08-01    # a pinned snapshot ID, never an alias
  effort: low
  eval_set: support-evals v14
  pass_bar: <agreed before the run>
  tried: smaller model and lower effort first
  fallback: other-provider-small-2026-07-15
  decided: <date>
draft_reply:
  model: large-model-2026-09-10
  effort: medium
  eval_set: support-evals v14
  pass_bar: <agreed before the run>
  fallback: none                   # hand the ticket to a person
  decided: <date>

Route each request by task type, a classifier or a failed check

A router decides which model handles a request. Anthropic’s Building effective agents defines it in one sentence. “Routing classifies an input and directs it to a specialized followup task.” One of its examples sends “easy/common questions to smaller, cost-efficient models” and “hard/unusual questions to more capable models”.

Route by task type when your code already knows the task

Routing by task type is a lookup in your code. The ticket labeller always calls one model and the reply writer calls another. Nothing has to guess the route, so there is nothing extra to evaluate. Start here, and add a classifier later if one task needs it.

Use a simple classifier when one task mixes easy and hard inputs

A classifier routes requests inside one task, for example short, common questions to a small model and long, unusual ones to a larger model. The same article says routing works “where classification can be handled accurately, either by an LLM or a more traditional classification model/algorithm.”

That makes the classifier a model choice of its own. Give it eval cases: labelled requests, each with the route it should take. Send any request the classifier is unsure about to the larger model.

Escalate to a larger model when a check fails

Escalation runs the small model first and calls a larger one when the answer fails a check. The check has to be cheap and trustworthy: schema validation, a test run, a rule in code, or a grader you have already evaluated. Anthropic’s cost and intelligence guide describes the same pattern for effort: “run every task at a low setting and re-run only the failures at a higher one.”

Cap escalation at one step. The failed first attempt still counts in cost per successful task.

An example router config, with names made up for this post:

router.yaml (example)
routes:
  ticket_label:
    model: small-model-2026-08-01
  order_details:
    model: small-model-2026-08-01
    check: validate against order_schema v3
    on_check_fail: escalate to large-model-2026-09-10, once
  customer_question:
    classifier: question-router v2   # has its own eval cases
    easy: small-model-2026-08-01
    hard_or_unsure: large-model-2026-09-10
log: route, model ID, escalated (yes or no), for every request

Record the route and the model ID on every request. They belong on the same trace as the rest of the request, as in Part 21.

Fall back on errors and rate limits without changing the contract

A fallback sends a request to another model or provider when the call fails: an error, a timeout or a rate limit. Escalation handles an answer that failed a check. A fallback handles a call that never returned a usable answer.

Retry the errors a retry can fix, then fall back

Each provider documents which errors are worth retrying. The table also shows how each one scopes rate limits, which decides whether another model on the same account can help.

Provider Retry these A retry won’t fix these Rate limits apply to
Anthropic (errors, rate limits) 429 rate_limit_error; 500 api_error “with exponential backoff”; 529 overloaded_error; wait for retry-after when it is sent A tier spend-cap 429 “has no retry-after header and keeps failing until access resumes” Requests and tokens per minute “for each model class”
OpenAI (rate limits, error codes) 429 and 503; “follow Retry-After when the header is present” “Don’t retry quota, billing, or other errors that require you to take action.” Each model, and “Some model families have shared rate limits”
Google Gemini (troubleshooting, rate limits) “429 RESOURCE_EXHAUSTED or 503 UNAVAILABLE”, with exponential backoff “Do not retry on client errors (like 400, 402, or 403)” “Rate limits are applied per project, not per API key”, and limits vary by model

Every row comes from the linked docs, last verified on 1 November 2026; none was tested for this post.

On OpenAI, models in the same shared-limit list draw from one pool, per its rate limits guide. Falling back to a sibling in that list may hit the same 429. A spend-cap or billing error belongs to the account. Switching models on that account is unlikely to help, so send those to a person.

Count the retries you already have. Anthropic’s SDK retries transient failures “twice by default”, per its errors page. Gemini’s Python SDK retries “up to four times”, per its troubleshooting guide. OpenAI’s rate limits guide warns: “If you manage retries in your application, disable SDK retries or account for them in those limits so nested retry loops don’t multiply requests.”

Timeouts need a deadline for the whole request, fallback included. The same OpenAI guide notes that “A timeout for each attempt isn’t necessarily a deadline for the entire operation.”

Keep the prompt, schema and safety settings the same across a fallback

A fallback that changes the contract ships a different feature during an outage. These parts stay the same across a fallback.

Keep the same Why
Prompt instructions and examples A fallback that needs different wording gets a new prompt version, with its own eval run
Output schema The code reading the answer doesn’t know which model wrote it
Tools and their permissions The fallback gets no tool or access the primary model didn’t have
Safety settings Moderation, content filters, refusal handling and system rules
Data and region rules An outage is no reason to send data somewhere your contracts forbid
Limits Output tokens, timeouts and the per-task budget

Structured output support differs between models and providers. Anthropic’s structured outputs page lists the supported models by ID. OpenAI’s guide says older models “may use JSON mode instead”. Both OpenAI and Gemini support a subset of JSON Schema, so test the fallback with your real schema.

Region can change with the platform too. Anthropic’s models overview notes that Amazon Bedrock offers “global endpoints (dynamic routing) and regional endpoints (guaranteed data routing)” for Claude Sonnet 4.5 and later.

Pin model versions and run the evals again when a model changes

Pinning means calling one exact model snapshot, so the model under your application can’t change without you. OpenAI’s text generation guide recommends “Pinning your production applications to specific model snapshots” to “ensure consistent behavior”, and building evaluation suites for “when you change and upgrade model versions”.

Provider How a version is pinned Notice before retirement How to watch
Anthropic (model IDs, deprecations) From the 4.6 generation, each model ID “maps to a single, fixed model snapshot”; older aliases such as claude-sonnet-4-5 point to “the most recent dated snapshot” “at least 60 days’ notice before model retirement for publicly released models” Email, the deprecations page, and a usage export by API key and model
OpenAI (text generation, deprecations) A dated snapshot ID, such as gpt-5.5-2026-04-23 Generally available models “At least 6 months”; preview models “may be retired with much shorter notice, such as 2 weeks” Email, the deprecations page, and the shutdown_date field from GET /v1/models
Google Gemini (models, deprecations) A stable model string: “Most production apps should use a specific stable model.” A -latest alias “will get hot-swapped with every new release” Preview models “will be deprecated with at least 2 weeks notice”; a -latest alias gets “a 2-week notice” by email before a breaking change Announcements in the release notes, and the deprecations page, whose dates are “the earliest possible dates on which a model might be retired”

Every row comes from the linked docs, last verified on 1 November 2026; none was tested for this post.

A pinned ID can still shift a little. Anthropic’s model IDs page says “Occasionally, infrastructure updates produce minor differences in observable behavior even when the model ID and weights have not changed.” Run the eval set on a schedule, as well as on every change.

Run it again when any of these happen:

  • a new model or snapshot you might move to
  • a deprecation notice for any model you call, fallbacks included
  • an alias change notice, if you still call an alias
  • a change in behaviour that nobody can explain
  • any change to the prompt, the schema or the router

Put the retirement date in the team calendar on the day the notice arrives. Anthropic’s deprecations page advises: “Test your applications with newer models well before the retirement date of your current model.”

Two prompts and a checklist for model routing

Neither prompt relies on one coding agent’s features.

Prompt 1: map every model call and its routing

An example prompt for a coding agent, to review how an application picks and calls models.

Prompt 1: map every model call and its routing
Task: map how <the application or service> chooses and calls models.
Start from: <path or entry point>

Do not change code and do not call any model API.
For every model call, report:
1. Where it is called, and which task it does.
2. The model ID: a pinned snapshot, an alias such as "latest",
   or a value from config?
3. How the model is chosen: fixed per task, a classifier, or
   escalation after a failed check.
4. What happens on a 429, a 5xx error or a timeout: retries,
   backoff, overall deadline, fallback model.
5. Do SDK retries and application retries stack?
6. Does a fallback keep the same prompt, output schema, tools,
   safety settings and region?
7. Which eval cases cover this call, if any?

Cite the file and line for each answer. Mark anything you could not
confirm as unverified.
Never print, expose or commit secret values.
Do not commit, push, deploy, run destructive commands or make external
changes unless I explicitly authorise it.
If two reasonable attempts fail, stop and report the failure rather
than repeatedly guessing.

End with the calls that have no eval cases, no pinned version or an
untested fallback.

Prompt 2: plan a move off a deprecated model

An example prompt for when a deprecation notice names a model you call.

Prompt 2: plan a move off a deprecated model
Task: plan the move from <current model ID> to <candidate model ID>
for <the task or feature>. Retirement date: <date from the notice>.
Eval set: <name and version, or path>

Do not change production config and do not deploy.
1. List every place the current model ID is used, fallbacks and
   config files included.
2. From the provider's docs, check the candidate's context window,
   output limit and support for the features these calls use.
   Link each source.
3. Write the command that runs the eval set on both models, with the
   same prompts, schema and settings, more than once.
4. Before running it, tell me how many model calls it will make, and
   wait for my go-ahead.
5. After the run, compare pass rates per task and list the cases
   that newly fail.

Never print, expose or commit secret values.
Do not commit, push, deploy, run destructive commands or make external
changes unless I explicitly authorise it.
If two reasonable attempts fail, stop and report the failure rather
than repeatedly guessing.

End with a recommendation per task: move, adjust the prompt and test
again, or try another candidate.

A model routing checklist

An example checklist, in the order of this post:

Model routing checklist
MODEL SELECTION AND ROUTING: REVIEW

□ Criteria and pass bar written per task before comparing models
□ Candidates run on the eval set, smallest first
□ Lower effort tried before a larger model
□ Each choice recorded: model ID, eval set version, date
□ Routing by task type, unless one task mixes easy and hard inputs
□ Any classifier has its own eval cases
□ Escalation on a failed check, capped at one step and logged
□ Retries only on errors a retry can fix, inside one deadline
□ SDK retries and application retries counted together
□ Fallback keeps prompt, schema, tools, safety settings and region
□ Fallback model passed the same eval set
□ Every model ID pinned; no "latest" alias in production
□ Retirement dates in the calendar, fallbacks included
□ Eval set run on every model change, and on a schedule

Quick answers

What is LLM model routing?

LLM (large language model) model routing sends each request to the model that suits it. The router can use a fixed map from task type to model, or a classifier that sorts easy from hard requests. It can also escalate to a larger model after a failed check.

How do you choose an LLM for a task?

Choosing a large language model (LLM) for a task starts with written criteria: quality on your own eval cases, latency, cost, context window, output format, tools and data rules. Run the candidates on the same eval set, smallest first, and keep the lightest model that passes.

What is the difference between LLM fallback and escalation?

An LLM (large language model) fallback calls another model or provider when the first call fails with an error, a timeout or a rate limit. Escalation calls a larger model when the first call returned an answer that failed a check, such as schema validation.

Should you pin LLM model versions in production?

Pinning a model version means calling one exact snapshot ID instead of an alias that moves to new releases. OpenAI recommends pinning production applications to model snapshots, and Google says most production apps should use a specific stable Gemini model.

What should you do when an LLM model is deprecated?

A deprecated large language model (LLM) keeps working until its retirement or shutdown date, and then requests to it fail. Note the date, test replacement candidates on your eval set with the same prompts and schema, and move every task before then, fallbacks included.

More on AI coding is in the AI category.

Cartoon of Kousigan making a heart with his hands

Kousigan A

Build with AI. Ship with care.

Software engineer building real products with AI assistants. I write down what works: prompts, rules, skills and agents, and how to ship safely.