← All posts

LLM routing: how to automatically choose the right AI model

Every AI app eventually hits the same wall: one model can't be the right answer for every request. The cheap model is fine for "summarize this ticket" and wrong for "reason through this contract." The frontier model is brilliant and slow. The local model is free and private but doesn't know everything.

LLM routing lets you stop choosing. Send every request to one endpoint and let a policy pick the best model for that request — local-first, by prompt size, by whether there's an image, by customer tier — with automatic failover when the preferred model isn't available.

Dynamic model switching means your application doesn't have to decide which AI model to use. The gateway evaluates each request and can switch between local, cloud, fast, reasoning, or vision models based on rules you define — and it can change those rules without a deploy.

This guide shows how routing works in the HostAnywhere AI Gateway: what a route is, four routing patterns that cover many common real-world cases, the exact JSON each one needs, how to call a route from your code, and how to prove a rule actually fired. Everything below runs on the free tier.

Prefer to watch? Routes are set up at 6:44 in the 17-minute AI Gateway walkthrough — the embed starts there.

1. What LLM routing is — and why route at all

LLM routing puts one endpoint between your application and every model it might use. The app sends a request to that endpoint; a policy you control decides which model answers it, and which one to try next if that model fails.

                      ┌──→ Local model (your own hardware)
                      │
Your app ──→ AI Gateway ──→ Cloud model (OpenAI, Anthropic, Groq …)
   "model": "smart"   │
                      ├──→ Vision model (requests with images)
                      │
                      └──→ Fallback model (when the first choice fails)

One endpoint → one route → many possible models

Hard-coding a model name into an app instead creates four problems that only get worse as usage grows:

The usual fix is a tangle of if len(prompt) > N branches, retry wrappers, and per-provider SDKs inside the app. Routing moves all of that out of the app and into a policy you edit in one place.

2. What a route is

A route is a virtual model your applications call by name.
Application: "model": "smart" → AI Gateway: smart = local/deepseek-r1:8bopenai/gpt-4o-mini → Actual model that answers.

Your code never names a real model. The gateway resolves the route to an ordered chain of real targets — local models on hardware you own (local/<model>) or provider models (<provider>/<model>) — tries them in order, and fails over automatically when one errors or times out.

A route has three parts:

PartWhat it does
targetsThe default chain, best-first. Each target carries its own timeout_ms and retries (max, backoff: exponential / linear / constant, delay_ms).
rulesOptional conditional rules. Each rule is a set of conditions on the request — all must match — plus a target chain that replaces the default chain when it does. Up to 16 rules, 8 conditions each.
GuardrailsA per-route guardrail policy: inherit the account policy, turn it off, use the standard or strict preset, or define a custom one for this route only.

Conditions can read six attributes of the request:

AttributeWhat it readsTypical use
prompt_tokensApproximate size of the prompt in tokensSmall → cheap model, large → capable model
has_imageWhether the request carries an imageSend multimodal requests to a vision model
header (with key)An HTTP header on the requestTier, tenant, or environment routing
metadata (with key)A key in the request body's metadata objectSame as header, without touching HTTP
end_userThe request's user fieldPin a customer or test account to a model
modelThe model name the app asked forMigrate old model names without changing apps

Operators: eq, ne, contains, in, gt, gte, lt, lte.

Behind every target sits a circuit breaker: after 3 consecutive failures a target is skipped for 30 seconds, so a dead provider doesn't add its full timeout to every request — traffic goes straight to the next target until it recovers.

You can build routes three ways in Dashboard → AI → Routes: the visual Flow editor, a Form, or raw JSON. The examples below use JSON because it's the most precise; every one of them can be pasted into the JSON view as-is.

The Flow editor: Start → local model → fallback → Anthropic → fallback → OpenAI → Response
The Flow editor. Requests enter at Start and flow top to bottom; each orange edge is a fallback taken only when the model above it errors or times out.
The Form editor listing three targets in order with per-target timeout and retries, and an empty Conditional rules section
The same route in the Form editor — targets tried top to bottom, first success wins, with per-target timeout and retries; conditional rules are added underneath.
The JSON editor showing the route definition and an inline reference for conditional-routing attributes and operators
The JSON view exposes everything, including the attribute and operator reference for conditional rules.

3. Pattern 1 — Local-first with cloud failover

The route most people start with: try the model running on your own hardware first, fall back to a cloud model if it's slow or down.

🔐 Private by default. Try your own AI server first; send the request to a cloud provider only when the local model can't handle it. Your prompts stay on your machine whenever it can answer — the cloud is insurance, not the default.

When to use it: privacy matters · you have local GPU or Apple-silicon capacity · you want to minimise cloud spend · the local model is good enough for most requests.

{
  "name": "smart",
  "strategy": "fallback",
  "targets": [
    { "type": "local", "model": "local/deepseek-r1:8b", "timeout_ms": 8000 },
    { "type": "cloud", "provider": "openai", "model": "openai/gpt-4o-mini",
      "retries": { "max": 2, "backoff": "exponential" } }
  ]
}

Two settings do the real work. The 8-second timeout on the local target is the promise you're making to users: if the home box can't answer in that time, the request moves on. The retries with exponential backoff on the cloud target absorb a rate-limit blip without failing the request.

Model node panel with the provider set to My AI server and the model picker listing models on your AI server
Picking a local target: with the provider set to My AI server, the model list shows exactly what is installed on your own machine.
💡 Local models come from your AI Server — any Mac, PC, or Linux machine you own with the AI Server switched on. The gateway sees its models automatically; no URLs or ports to configure.

4. Pattern 2 — Route by prompt size

The classic cost optimization. Short prompts are often good candidates for a smaller, faster model — a classification, an extraction, a one-line question — while requests carrying a lot of context tend to benefit from a model with a larger context window or stronger reasoning. Length is a useful signal, not a proxy for difficulty: a five-word prompt can be hard and a long one trivial, so treat the threshold as a cost lever you tune, not a rule about intelligence.

When to use it: most of your traffic is short and repetitive · a big share of cost comes from a frontier model answering easy questions · you can measure quality on the cheaper model before committing.

This is a route exported straight from the dashboard (the one in the screenshot below). By default it answers with gpt-4.1-mini and falls back to Claude Opus; when a prompt is 10,000 tokens or more, the rule sends it to Claude Opus directly:

{
  "name": "Demo-11",
  "default": true,
  "strategy": "fallback",
  "targets": [
    {
      "type": "cloud",
      "provider": "openai",
      "model": "gpt-4.1-mini-2025-04-14",
      "timeout_ms": 30000,
      "retries": {
        "max": 2,
        "backoff": "exponential",
        "delay_ms": 0
      }
    },
    {
      "type": "cloud",
      "provider": "anthropic",
      "model": "claude-opus-4-8",
      "timeout_ms": 30000,
      "retries": {
        "max": 2,
        "backoff": "exponential",
        "delay_ms": 0
      }
    }
  ],
  "rules": [
    {
      "when": [
        {
          "attr": "prompt_tokens",
          "op": "gte",
          "value": "10000"
        }
      ],
      "targets": [
        {
          "type": "cloud",
          "provider": "anthropic",
          "model": "claude-opus-4-8",
          "timeout_ms": 30000,
          "retries": {
            "max": 2
          }
        }
      ]
    }
  ]
}

The important idea isn't prompt length itself — it's that routing decisions are made on request attributes rather than on a model name hard-coded in the app. Prompt size is one of six attributes; image presence, headers, metadata, the end user, and the requested model name work exactly the same way.

A few things worth noticing in the export:

Flow editor with a conditional split: if prompt_tokens ≥ 10000 the true branch goes to Anthropic; the false branch runs the default chain of OpenAI with an Anthropic fallback
The same route in the Flow editor: a Conditional split on prompt size. Requests that match take the green true branch to one model; everything else follows false into the default chain with its own fallback.

Pick the threshold from your own traffic: the Usage & cost tab in Observability lists every request with its route and prompt tokens, so you can see where the "simple" requests actually end. 10,000 tokens is a "long document" threshold; for chat-style traffic something like 800 is more typical.

5. Pattern 3 — Send images to a vision model

Text-only models reject image content, and vision-capable models cost more. With has_image the app never has to know which is which:

When to use it: one endpoint serves both text and multimodal requests · your default model is text-only or local · you only want to pay vision prices when there's actually an image.

{
  "name": "assistant",
  "strategy": "fallback",
  "targets": [
    { "type": "local", "model": "local/llama3.2:3b", "timeout_ms": 6000 },
    { "type": "cloud", "provider": "openai", "model": "openai/gpt-4o-mini" }
  ],
  "rules": [
    {
      "when": [ { "attr": "has_image", "op": "eq", "value": "true" } ],
      "targets": [
        { "type": "cloud", "provider": "openai", "model": "openai/gpt-4o" }
      ]
    }
  ]
}

Text requests go local-first; anything with an image jumps to gpt-4o. One route name for the whole assistant.

6. Pattern 4 — Route by tenant, tier, or environment

Sometimes the right model depends on who is asking, not what. Premium customers get the best model; free-tier traffic gets the economical one; staging never touches the expensive provider. Conditions on a header, a metadata key, or the user field handle all three.

When to use it: you sell tiers · several environments share one gateway · specific users or bots should never reach paid models.

{
  "name": "chat",
  "strategy": "fallback",
  "targets": [
    { "type": "cloud", "provider": "groq", "model": "groq/llama-3.3-70b-versatile" }
  ],
  "rules": [
    {
      "when": [ { "attr": "header", "key": "x-plan", "op": "eq", "value": "premium" } ],
      "targets": [ { "type": "cloud", "provider": "anthropic", "model": "anthropic/claude-sonnet-4-5" } ]
    },
    {
      "when": [ { "attr": "metadata", "key": "env", "op": "eq", "value": "staging" } ],
      "targets": [ { "type": "local", "model": "local/llama3.2:3b" } ]
    },
    {
      "when": [ { "attr": "end_user", "op": "in", "value": "qa-bot,load-test" } ],
      "targets": [ { "type": "local", "model": "local/llama3.2:3b" } ]
    }
  ]
}

The app sends x-plan: premium as a header, or {"metadata": {"env": "staging"}} in the body, or "user": "qa-bot" — all standard OpenAI-API fields — and the gateway does the rest. Conditions inside one rule are ANDed, so "premium and large prompt" is just two conditions in the same when.

ℹ️ Rules are evaluated in the order you list them; the first rule whose conditions all match wins. Put the most specific rule first.

What routing gives you

Without routingWith routing
Model name hard-coded in the appApp calls a virtual model (a route)
Provider keys in every applicationKeys held once, on the gateway
One model for everythingModel selected per request
Provider outage breaks the appAutomatic failover to the next target
Frontier prices for simple tasksCheap or local model for simple tasks
Provider migration = code change + deployEdit the route

A real application uses all four

Picture a customer-support assistant. It makes one API call to one route, and behind that route the four patterns above are just rules:

                 Support request  →  "model": "support"
                                │
                        AI Gateway (route: support)
                                │
        ┌───────────────────────┼───────────────────────┐
        │                       │                       │
   plain text            has_image = true      x-plan = premium
        │                       │                       │
   Local model  ──fail──▶  Vision model            Claude Sonnet
        │                       │                       │
        └───────── fallback: cloud model (openai/gpt-4o-mini) ─────────┘

Text goes local-first with a cloud fallback; anything with a screenshot goes to a vision model; premium customers get the strongest model; and if any first choice fails, the chain continues. The application code never changes when any of that does.

7. Calling a route from your code

A route is called exactly like a model: the OpenAI-compatible endpoint, your gateway key, and the route name in model. Your app authenticates with a HostAnywhere gateway key (ha-gw-…) — provider keys stay on the gateway and never appear in your code.

curl https://api.hostanywhere.ai/v1/chat/completions \
  -H "Authorization: Bearer ha-gw-..." \
  -H "Content-Type: application/json" \
  -H "x-plan: premium" \
  -d '{
    "model": "chat",
    "messages": [{"role": "user", "content": "Summarize the attached contract in five bullets."}]
  }'

With the OpenAI Python SDK, only two lines change — the base URL and the key:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.hostanywhere.ai/v1",
    api_key="ha-gw-...",          # HostAnywhere gateway key, not a provider key
)

resp = client.chat.completions.create(
    model="smart",                 # the route name — create smart, call smart, inspect resp.model
    messages=[{"role": "user", "content": "Classify this ticket: 'card declined twice'"}],
)
print(resp.model)                  # which real model answered — see below
print(resp.choices[0].message.content)

Running the gateway on your own device instead of the hosted one? Same code — point base_url at the device's gateway address (https://<mesh-ip>:36140/v1) shown under API keys. Routes, keys, and rules are account-wide and apply on every gateway you run.

8. Proving a rule fired

Don't rely on the model's self-description ("I'm ChatGPT…") to know where a request went — models are unreliable narrators of their own identity. Two dependable signals:

  1. The response's model field reports the resolved provider modelopenai/gpt-4o-mini or anthropic/claude-sonnet-4-5 — not the route name. Print it next to each test request.
  2. Observability → Usage & cost lists every request with its route, the provider and model that answered, tokens, cost, and latency. Send a short and a long prompt to a route with a prompt-size rule and you'll see the two rows land on different models.

The Chat Playground (under Tools) does the same interactively: pick the route, send a prompt, and read which target answered.

9. Common routing mistakes

10. Routing isn't the whole story

A production AI gateway needs four layers:

  1. Route — where should the request go?
  2. Protect — is the request safe to send, and the answer safe to show? (guardrails)
  3. Control — how much can it cost? (spend limits)
  4. Optimize — can we avoid calling a model at all? (caching)

HostAnywhere combines all four in one gateway:

All three apply to every route automatically. The complete AI Gateway guide covers each in depth.

11. Getting started

  1. Sign in at hostanywhere.ioAI. The Overview walks you through enabling a gateway, adding a provider (or switching on an AI Server on a machine you own), and creating an ha-gw-… key.
  2. Open Routes → New route, paste one of the JSON examples above, and save.
  3. Point your app's base_url at the gateway, set model to the route name, and send two requests that should land on different targets. Check resp.model.

Routing, failover, guardrails, and spend limits are on the free plan. Full syntax lives in the routes documentation, and the 17-minute walkthrough video shows the whole gateway set up end to end.