Skip to content
Niv Raz
Go back

Three-tier model routing on Amazon Bedrock with LiteLLM

Updated:

Originally published on AWS Builder Center. Any opinions in this article are my own and may not reflect the opinions of AWS.

Table of contents

Open Table of contents

The challenge: the bill follows traffic, not difficulty

Most applications on Amazon Bedrock pick one model during the prototype and keep it for everything, so a greeting and a design review cost the same per token. A frontier model overpays for the routine majority. A small model gives the hard minority weaker answers. In the enterprise workloads I see, most traffic is routine: classification, short answers, extraction, and everyday coding help. What’s missing is a reliable way to tell routine from hard on every request, with a safety net when that call is wrong.

What I built

One OpenAI-compatible endpoint, bedrock-auto, that looks like a single model to any client. Behind it, a LiteLLM gateway classifies each request with Auto Router v2 and sends it to one of three tiers on Amazon Bedrock:

TierRouter classModelTypical requests
Tier 1 (Fast)SIMPLEOpenAI GPT-6 LunaGreetings, lookups, classification, extraction
Tier 2 (Balanced)MEDIUMClaude Sonnet 5.5Everyday coding, RAG answers, drafting, tool calls
Tier 3 (Frontier)COMPLEX, REASONINGClaude Opus 5.5Design reviews, multi-step reasoning, long agent tasks

The router always assigns one of four classes, so COMPLEX and REASONING share a model. A fourth model, Amazon Nova 2 Lite, does the classifying and never answers a request.

How the routing decision works

1/ Keyword rules first. Domain terms set a minimum tier, so a request that mentions “threat model” never lands on Tier 1.

2/ A small classifier model decides the rest. Nova 2 Lite reads the request and the last three user turns and returns a class. If it fails, the router falls back to a local heuristic scorer instead of failing the request.

3/ Conversation state overrides both. Session affinity keeps a conversation on its first-turn model, so a “thanks!” doesn’t move it and lose the prompt cache. classification_mode: user_turn classifies new user requests only, so an agent’s tool results stay on the model that started the task.

- model_name: bedrock-auto
  litellm_params:
    model: auto_router/complexity_router
    complexity_router_default_model: tier2-balanced
    complexity_router_config:
      tiers:
        SIMPLE: tier1-fast          # OpenAI GPT-6 Luna, no reasoning
        MEDIUM: tier2-balanced      # Claude Sonnet 5.5, low effort
        COMPLEX: tier3-frontier     # Claude Opus 5.5
        REASONING: tier3-frontier
      classifier_type: llm
      classifier_llm_config: {model: classifier-fast, timeout_ms: 1500}   # Amazon Nova 2 Lite
      classifier_fallback: heuristic
      classifier_context_window_size: 3
      classification_mode: user_turn
      keyword_tier_rules:
        - keywords: ["hi", "hello", "thanks"]
          tier: SIMPLE
        - keywords: ["5g core", "network slicing", "sip trunk"]
          tier: COMPLEX
        - keywords: ["root cause", "architecture review", "threat model"]
          tier: REASONING
      enable_context_window_escalation: true
      session_affinity: true
      max_tokens_from_tier_model: false   # keep the client's max_tokens, see below

I use a classifier model because the heuristic scorer measures vocabulary, not difficulty. A public walkthrough shows it sending “prove that the halting problem is undecidable” to the cheapest tier because the prompt is short.

Why this architecture

Architecture diagram. Clients send requests over HTTPS to AWS WAF and an internal Application Load Balancer, which blocks admin UI paths with 403. The load balancer forwards to a LiteLLM gateway on Amazon ECS with AWS Fargate in private subnets across two Availability Zones. The gateway's Auto Router applies keyword rules, a classifier, session pins, and a heuristic fallback. A VPC endpoint connects it to Amazon Bedrock, where Amazon Nova 2 Lite is the classifier and the three tiers are OpenAI GPT-6 Luna, Claude Sonnet 5.5, and Claude Opus 5.5. Amazon Aurora Serverless v2 stores keys and spend, Amazon ElastiCache stores session pins, a NAT gateway handles startup downloads, and AWS Secrets Manager and Amazon CloudWatch Logs support the gateway.

Clients call one endpoint, bedrock-auto, over HTTPS. AWS WAF and an internal Application Load Balancer pass requests to the LiteLLM gateway on Amazon ECS with AWS Fargate. Amazon Nova 2 Lite classifies each request, and the router sends it through a VPC endpoint to GPT-6 Luna, Claude Sonnet 5.5, or Claude Opus 5.5. Aurora Serverless v2 holds keys, budgets, and spend logs, and ElastiCache holds session pins.

Three lessons from building it:

Results

I ran a test suite through the gateway from inside the VPC, with two gateway tasks. It routed 120 prompts that I wrote and labeled with the cheapest tier that should answer correctly, 40 per tier. The set is synthetic and evenly split, so it shows how the router behaves, not your traffic mix.

Routing accuracy

Labeled tierRouted correctlyMisrouted
Tier 139 of 401 to Tier 2
Tier 235 of 403 to Tier 3, 2 to Tier 1
Tier 340 of 40none

The run landed 114 of 120 prompts on their labeled tier. 1.7 percent went to a cheaper tier and 3.3 percent to a more expensive one. The Nova 2 Lite classifier made 145 of 150 routing decisions. One classifier call timed out, the circuit opened, and the heuristic scorer made the other five. Two earlier runs, with Nova 2 Lite on Tier 1 and the 5.0 Claude models, landed 112 of 120.

Latency, pinning, and reasoning tokens

Two decisions from the log

“Classify this ticket as Billing, Technical, or Account: ‘I was charged twice this month.’” stayed on Tier 1:

ComplexityRouter: routing decision cause=llm_classifier, tier=SIMPLE, score=n/a, signals=('llm-classifier:SIMPLE',), routed_model=tier1-fast

“Prove that the halting problem is undecidable”, the prompt the heuristic scorer sent to the cheapest tier, went to Tier 3:

ComplexityRouter: routing decision cause=llm_classifier, tier=REASONING, score=n/a, signals=('llm-classifier:REASONING',), routed_model=tier3-frontier

What it costs

On-demand Standard tier prices in US East (N. Virginia) for the US cross-Region inference profiles. Check Amazon Bedrock pricing for your Region.

RoleModelInput, per 1M tokensOutput, per 1M tokens
Tier 1OpenAI GPT-6 Luna$0.11$0.55
Tier 2Claude Sonnet 5.5$2.20$11.00
Tier 3Claude Opus 5.5$4.40$22.00
ClassifierAmazon Nova 2 Lite$0.33$2.75

The estimate assumes 55 percent of requests need Tier 1, 35 percent Tier 2, and 10 percent Tier 3, then applies the measured misroutes. Each request has 2,000 input and 400 output tokens, plus a classifier call of 600 input and 20 output tokens.

Per 1,000 requestsCost
Everything on Tier 3$17.60
Everything on Tier 2$8.80
Routed, if every decision were right$5.34
Routed, with the measured misroutes$5.53

Routing costs 69 percent less than sending everything to Opus 5.5, and 37 percent less than sending everything to Sonnet 5.5. The classifier adds $0.25 per 1,000 requests and the misroutes add $0.20. Tier 1 costs $0.44 per 1,000 requests, so nearly the whole bill is Tiers 2 and 3, and every request kept off them counts. Routing stays cheaper than all Sonnet 5.5 until about 29 percent of requests need Tier 3, with the rest split evenly between Tiers 1 and 2. Past that, it buys quality, not savings. The estimate leaves out reasoning tokens, which bill as output on Tiers 2 and 3, prompt caching, and the gateway’s fixed monthly cost, which can outweigh the savings at low volume.

Build it yourself

You need model access to the four models in a US Region. To install and run LiteLLM, follow its production deployment guide and production best practices. This is what I added on top.

1/ Define the tiers and the classifier. Each is a LiteLLM model name that points at a US cross-Region inference profile, using LiteLLM’s Amazon Bedrock provider. Each max_tokens applies when the client sends none. Add the bedrock-auto entry from above to the same model_list. GPT-6 Luna also needs bedrock:InvokeModel on your account’s default Amazon Bedrock project, as its model card shows.

model_list:
  - model_name: tier1-fast
    litellm_params:
      model: bedrock/us.openai.gpt-6-luna
      aws_region_name: us-east-1
      reasoning_effort: "none"
      max_tokens: 2048
  - model_name: tier2-balanced
    litellm_params:
      model: bedrock/us.anthropic.claude-sonnet-5-5
      aws_region_name: us-east-1
      reasoning_effort: "low"
      max_tokens: 4096
  - model_name: tier3-frontier
    litellm_params:
      model: bedrock/us.anthropic.claude-opus-5-5
      aws_region_name: us-east-1
      max_tokens: 8192
  - model_name: classifier-fast
    litellm_params:
      model: bedrock/us.amazon.nova-2-lite-v1:0
      aws_region_name: us-east-1

2/ Share state across tasks. Point the router at Redis, as the production guide shows:

router_settings:
  redis_host: os.environ/REDIS_HOST
  redis_port: os.environ/REDIS_PORT
  redis_password: os.environ/REDIS_PASSWORD

3/ Choose how routed requests get their output limit. With max_tokens_from_tier_model: false, the client’s max_tokens goes through. To cap every routed answer no matter what the client asks for, write the tier inside tiers as {model_name: tier3-frontier, litellm_params: {max_tokens: 8192}}.

4/ Pin the model list. LiteLLM reads prices, output limits, and which Amazon Bedrock API to call from its model list. The list bundled in v1.103.2 doesn’t include GPT-6 Luna, Sonnet 5.5, or Opus 5.5, and for a model it doesn’t know, LiteLLM picks a different Bedrock API without warning. Each task downloads the list from a fixed LiteLLM commit at startup, checks its SHA-256 hash, and writes it over the bundled copy. LITELLM_LOCAL_MODEL_COST_MAP=True stops LiteLLM from replacing it with the latest list on a restart.

Clients then call the gateway with any OpenAI-compatible SDK, using model: bedrock-auto. I’ll link the CloudFormation template and the test suite behind these numbers here once they’re published.

What to watch for

Try it

Start with one internal application. Route it through bedrock-auto, label a few hundred of its real prompts, and measure the tier mix before you widen the rollout. If you try it, share your tier map and results in the comments.


Share this post:

Previous Post
Technology's Real Job Is to Shorten the Distance to Value