simple, medium, or complex — and routes it to the target you configured for that tier. Your application keeps calling one virtual model name; the AI Gateway decides which model actually serves each request.
Unlike the other routing strategies, the routing decision depends on what is in the request, not on weights, priorities, or measured latency.
Requirements and availability
The virtual model must also meet these conditions, or it is rejected when you save it:- Model types must be limited to
chat,completion, andresponses. Embedding, image, audio, rerank, and moderation are not supported. - Each target serves exactly one tier, and the same model cannot appear under two tiers.
- The classifier model (LLM strategy only) must be a catalog chat model, not a virtual model.
How a request is routed
Filter targets by metadata
metadata_match, non-matching targets are dropped before anything else happens. Classification and escalation only ever consider the remaining targets.Check the session pin
Classify the request
simple, medium, or complex.Build the target chain
Send the request, falling forward on failure
Pin the session
Complexity tiers
There are exactly three tiers, ordered from cheapest to most capable.simple and complex, for example, is valid — requests classified as medium escalate to complex. See Escalation and fallback.Classification strategies
Two strategies are available. The heuristic classifier is the default and costs nothing; the LLM classifier is more accurate on ambiguous prompts but adds a model call to every request.What text gets classified
Both strategies look at the same two pieces of text, extracted from the request body:- The last user message — the current turn.
- The last system or developer message — for context. This includes the top-level
systemfield on Anthropic/messagesrequests and the top-levelinstructionsfield on/responsesrequests.
Heuristic classification (default)
The heuristic classifier reads the prompt text, looks for a fixed set of signals, and picks a tier. It needs no configuration — this is what you get if you do not setclassification_strategy at all.
What the heuristic looks for
What the heuristic looks for
complex, whatever the other signals say.Empty prompts. A request with no classifiable text is treated as simple.The signals and keyword lists are fixed and cannot be customised. If they don’t match how difficulty shows up in your traffic, use the LLM classifier instead.Worked examples
Worked examples
LLM classification
The AI Gateway classifies the request by calling a fast chat model you nominate. Use this when the heuristic’s keyword-based view is too coarse for your traffic — for example domain-specific prompts where difficulty is not signalled by vocabulary.How the classifier call works
How the classifier call works
- The classifier is sent the request’s system context and user text, along with a short instruction block describing the three tiers.
- Its reply is constrained to a JSON schema (
{"tier": "simple" | "medium" | "complex"}) and capped at 256 output tokens. Models that ignore the schema and answer in prose still work — the AI Gateway extracts the first tier word it finds. - The call is abandoned after
timeout_ms(default2000), and the fallback strategy applies.
Fallback when the classifier fails
If the classifier times out, errors, returns a non-2xx status, or returns nothing usable, the configured fallback strategy decides the tier. The classifier failure never fails the request.heuristic to keep a content-aware decision during an outage, or static to send everything to one known tier. default_tier: complex preserves answer quality at higher spend; default_tier: simple does the opposite.
Escalation and fallback
Complexity-based routing does not just pick one target. It builds a fully ordered chain, so a request still gets served when the tier it classified into has no usable target. For a request classified into tierT, the order is:
- Targets in tier
T, in the order you declared them. - Targets in higher tiers, working upward one tier at a time.
- Targets in lower tiers, working downward one tier at a time.
simple is attempted on simple, then medium, then complex. A request classified as complex is attempted on complex, then medium, then simple. A request fails only after every target has been tried.
Each target in the chain uses its own retry_config, fallback_status_codes, and fallback_candidate settings — these behave exactly as they do for the other routing strategies.
simple and complex targets, a request classified as medium is escalated straight to complex and the response header reports the escalation. This is how you run a two-tier setup.Sticky routing
By default every turn of a conversation is classified independently, so a chat can start on your cheap model, move to your expensive one on a hard follow-up, and move back again. That is the cheapest behaviour, but it can be visibly inconsistent within one conversation. Sticky routing pins a session to the tier and target chosen on its first classified turn. Subsequent turns in the same session skip classification entirely and go straight to the pinned target.key— the header or metadata field name to read.source—headersormetadata.
ttl_seconds) must be between 300 (5 minutes) and 3600 (1 hour) — a shorter range than weight-based sticky routing allows. The window slides: every turn that reads the pin refreshes its expiry, so an active conversation stays pinned and an abandoned one expires.
What else to know
What else to know
- Redis is required. Pins are stored in Redis and shared across all gateway pods. If Redis is not configured or is unreachable, sticky routing no-ops and every turn is classified independently.
- Pins are written only after success. A target that failed is never pinned.
- Sessions are isolated. Two different session identifier values are two different pins.
- Stale pins are discarded. If the pinned target is no longer an eligible target of the virtual model, the request is reclassified.
- Missing identifiers. If none of the configured identifiers resolve to a value on a request, there is no session to pin and the request is classified normally.
Configure in the dashboard
Complexity-based routing is configured on a virtual model, alongside the other routing strategies. See Create a virtual model for the full walkthrough.Create the virtual model and choose Complexity

Creating a virtual model with Complexity selected as the routing type
Pick a classification strategy

LLM Classification with a small, fast model as the classifier
Set a target for each tier

A target for each tier, from lowest to highest cost
Configuration reference
Examples
Cost-optimised three-tier chat model
Cost-optimised three-tier chat model
Two tiers — cheap and capable only
Two tiers — cheap and capable only
medium entirely. Requests classified as medium escalate to the complex target.LLM classifier with a conservative static fallback
LLM classifier with a conservative static fallback
complex tier if that classifier is ever unavailable — quality never regresses, spend rises only during the outage.Consistent tier for multi-turn conversations
Consistent tier for multi-turn conversations
Per-tier resilience with retries and a second complex target
Per-tier resilience with retries and a second complex target
Cheaper prompts per tier
Cheaper prompts per tier
Observability
Which tier served the request?
Every response carries anx-tfy-applied-rules header. For complexity-based routing it includes a complexity block with the tier that was served and how the tier was decided, plus the full ordered chain in routing_model_order:
cause tells you which mechanism produced the tier:
reason on each entry in routing_model_order tells you why that target is in that position:
x-tfy-resolved-model response header.
Metrics
Every routing decision increments a counter, so you can see your tier mix and how often escalation happens.decided_tier— the tier the request classified into.resolved_tier— the tier of the first target actually attempted.
Traces and logs
- Each LLM classifier call produces a
ComplexityBasedRouting: LLMClassifierspan, with the classifier’s own chat-completion span nested beneath it — so classifier latency is visible separately from the served model’s latency. - The request span carries
tfy.complexity.tierandtfy.complexity.cause. - The gateway logs each decision with the tier, the cause, the signals that matched, and the resolved model — useful for checking why a given request landed on the tier it did.
Validation errors
These configurations are rejected when you save the virtual model, with a400:
FAQ
How much can I actually save?
How much can I actually save?
ai_gateway_complexity_routing_decisions_total down by resolved_tier to see what share of traffic landed on each tier, and compare per-model cost in analytics. Your saving is that share multiplied by the price difference between tiers.Can I use complexity-based routing in the tenant-level Routing Config YAML?
Can I use complexity-based routing in the tenant-level Routing Config YAML?
Can I use it for embeddings, images, or audio?
Can I use it for embeddings, images, or audio?
Can I customise how the heuristic classifies requests?
Can I customise how the heuristic classifies requests?
Does the LLM classifier call show up in my usage and cost?
Does the LLM classifier call show up in my usage and cost?
timeout_ms tight.What happens if the classifier is down?
What happens if the classifier is down?
fallback_strategy decides the tier — the built-in heuristic by default, or a fixed default_tier if you chose static.What if the tier a request classified into has no target?
What if the tier a request classified into has no target?
decided_tier and resolved_tier separately so you can spot it.Why is a session jumping between models mid-conversation?
Why is a session jumping between models mid-conversation?
sticky_routing with a session identifier your client always sends.Do unhealthy-target cooldowns apply?
Do unhealthy-target cooldowns apply?
Does it work with streaming?
Does it work with streaming?
Does it work with the Anthropic Messages API and the Responses API?
Does it work with the Anthropic Messages API and the Responses API?
chat virtual model serves both /chat/completions and /messages, and a responses virtual model serves /responses. In both cases the top-level system prompt (system and instructions respectively) is included as classification context.Next steps
- Virtual Models overview — the other routing strategies, per-target options, and health detection.
- Create a virtual model — step-by-step setup.
- Analytics — compare cost and latency per target once traffic is flowing.