DEV Community

Cover image for Virtual Models: One Model Name, Any Model Behind It
Saif Ali
Saif Ali

Posted on

Virtual Models: One Model Name, Any Model Behind It

#ai

Virtual Models: One Model Name, Any Model Behind It

A new model ships almost every week. One is cheaper, one is faster, one finally handles your hardest coding task. Taking advantage of it should be a five-minute decision. In most stacks it is a small project: change an environment variable in three services, update a provider SDK, add a new API key, redeploy, and hope nothing else breaks.

Virtual models remove that work. This article explains what a virtual model is, when to use one, why it fits the way AI agents and apps are built today, and how to route different jobs to different models using the routing strategies in ZRouter, an open source, OpenAI-compatible AI gateway.

What is a virtual model?

A virtual model is a model name you define, plus a rule for what that name means.

Your application sends the name in the normal model field:

{ "model": "assistant", "messages": [{ "role": "user", "content": "Hi" }] }
Enter fullscreen mode Exit fullscreen mode

The gateway reads assistant, looks up its rule, and decides which real provider model serves the request. The rule can be as simple as "assistant means anthropic/claude-sonnet-4-6" or as rich as "send easy prompts to a cheap model, hard prompts to a strong one, and fail over to a second provider if the first is down".

Your app never learns which model answered unless you want it to. It only knows the name.

In ZRouter, a virtual model comes in two forms:

  • Redirect (alias). A name that points at one or more real models. This is what most of this article is about.
  • Access policy. A rule with no target that restricts who can use an existing model, by user path, or disables it.

You create them in the dashboard (Models → New virtual model), in config.yaml, through the VIRTUAL_MODELS environment variable, or through the admin API. When prepaid billing is enabled, customers can also create private virtual models in their own dashboard.

The problem it solves: model choice is wired into your code

Without a gateway, the model is usually decided in three places at once:

  1. The endpoint. Each provider has its own base URL.
  2. The credentials. Each provider has its own API key, stored in each app's environment.
  3. The model ID. A string like gpt-4o-2024-08-06 hardcoded or set in an environment variable.

Changing models means changing one or all of these, in every service that calls the model. For a single chatbot that is annoying. For a company with ten apps and twenty agents it is a migration.

With a gateway and virtual models, all three are fixed:

  • One endpoint: https://zrouter.si/v1 (or your self-hosted gateway), for every provider.
  • One key: your gateway key. Provider keys live in the gateway, not in each app.
  • One stable model name: assistant, coder, summarizer, whatever you choose.
from openai import OpenAI

client = OpenAI(base_url="https://zrouter.si/v1", api_key=ZROUTER_API_KEY)

reply = client.chat.completions.create(
    model="coder",
    messages=[{"role": "user", "content": "Refactor this function..."}],
)
Enter fullscreen mode Exit fullscreen mode

Switching coder from one model to another is an edit in the gateway. No environment variable changes, no new endpoint, no redeploy, no restart of the agent. The next request simply goes to the new model.

Why virtual models fit today's AI agents and apps

Models change faster than release cycles

Frontier models are updated, renamed, repriced, and deprecated on a monthly rhythm. If every model change needs a code release, you either fall behind or spend your team's time on plumbing. A virtual model turns "upgrade the model" into a configuration change you can make and undo in seconds.

Agents call models from many places

A modern agent is not one model call. It plans, writes code, calls tools, summarizes results, checks its own work, and often runs sub-agents. Each step has a different ideal model. Hardcoding a model ID in each step spreads model decisions across your codebase. Naming each step by its job (planner, coder, reviewer, summarizer) keeps the agent code about the job, and moves the model decision to the gateway.

Many tools only let you set one model

Coding agents and AI apps such as Claude Code, Codex, Cursor, OpenCode, OpenWebUI, LibreChat, and n8n let you set a base URL and a model name, and nothing more. A virtual model lets that single name carry routing logic, failover, cost control, and guardrails the tool itself knows nothing about.

Reliability needs more than one provider

Providers have outages and rate limits. A virtual model with several targets keeps serving as long as one target is available, without the app writing retry logic for each provider.

Cost needs to follow the task

Sending every request to the strongest model is expensive. Sending every request to the cheapest model hurts quality. Routing by task, time of day, or request difficulty lets you pay for capability only where it matters.

Teams need governance in one place

Who can use which model, which guardrails apply, and how much they can spend are platform decisions. Putting them on the virtual model means every app that uses the name gets the same policy.

When to use a virtual model

Use one when any of these is true:

  • You expect to change or upgrade the model behind a feature.
  • More than one app or agent step shares a model decision.
  • You want failover to a second model or provider.
  • You want to balance load or cost across several models.
  • You want different models for different jobs behind a clear name.
  • You want to A/B test a model without touching the app.
  • You need to limit access to a model for certain users or teams.
  • You want guardrails to apply to everything that calls a model name.

When should you skip it? If one script calls one model and you have no routing, failover, or access needs, call the provider model ID directly. ZRouter accepts both, so you can add a virtual model later without changing anything else.

Route by job: name models after what they do

The most useful habit with virtual models is naming them after the job, not the model. Here is a typical agent setup:

Virtual model Job Good fit
planner Break a task into steps, reason about trade-offs Strongest reasoning model
coder Write and edit code Best coding model, with failover
fast Classify, extract, route, short answers Small, cheap, low-latency model
summarizer Condense logs, documents, tool output Cheap model with long context
reviewer Check output, judge quality A different model family from coder
auto General chat where difficulty varies Difficulty-based routing

The agent code calls planner for planning and fast for classification. When a better coding model ships, you repoint coder and every agent improves at once. When summarizer is costing too much, you swap it for a cheaper model and watch quality, all without a deploy.

Each of these names can use a different routing strategy. That is where the real power is.

Routing strategies, and what each is best for

A ZRouter redirect can have several targets and a strategy that decides which target serves each request. Every strategy shares three behaviors:

  • Unavailable targets are skipped. An unknown model or a provider that is down does not break the name.
  • The other targets form a failover chain. If the chosen target fails with a retryable error, ZRouter tries the remaining available targets in order. You can turn this off per virtual model.
  • Session keeping is on by default. Requests from one detected client session (for example one Claude Code or Codex session) stay on the same target, so provider prompt caches stay warm and behavior stays consistent mid-conversation. The failover strategy ignores this setting because it always uses the first available target anyway.

1. Single target (alias)

What it does: One name, one model.

Best for: Stable names for apps, hiding provider IDs, and making future upgrades a one-line change. Start here for every job, and add targets when you need them.

virtual_models:
  - source: summarizer
    target: openai/gpt-4o-mini
Enter fullscreen mode Exit fullscreen mode

2. Round-robin (default)

What it does: Rotates requests across targets. Weights bias the share: a target with weight 2 gets twice the traffic of a target with weight 1.

Best for:

  • Spreading load across providers to stay under rate limits.
  • Gradual migrations and canaries: send 10% of traffic to a new model with weights 9 and 1, then shift.
  • Comparing models on real traffic.
  - source: coder
    strategy: round_robin
    targets:
      - { model: anthropic/claude-sonnet-4-6, weight: 9 }
      - { model: openai/gpt-5, weight: 1 }
Enter fullscreen mode Exit fullscreen mode

3. Lowest cost

What it does: Sends every request to the cheapest currently available target, using catalog input plus output prices. Targets without pricing are skipped while a priced target exists.

Best for: High-volume jobs where several models are good enough and price is the deciding factor: classification, extraction, tagging, routine summaries. If the cheapest provider goes down, the next cheapest takes over automatically.

  - source: fast
    strategy: cost
    targets:
      - { model: openai/gpt-4o-mini }
      - { model: groq/llama-3.3-70b }
      - { model: anthropic/claude-haiku-4-5 }
Enter fullscreen mode Exit fullscreen mode

4. Failover

What it does: Always uses the first available target in the list. Lower entries only serve when everything above them is unavailable or fails.

Best for: Production features where you have a clear preferred model and want backups only for emergencies. Customer-facing chat, critical agent steps, and anything where consistency matters more than spreading load.

  - source: planner
    strategy: failover
    targets:
      - { model: anthropic/claude-opus-4-6 }
      - { model: openai/gpt-5 }
      - { model: anthropic/claude-sonnet-4-6 }
Enter fullscreen mode Exit fullscreen mode

5. Schedule (time of day)

What it does: Uses one set of targets during a daily peak window and another set off-peak, in a timezone you choose.

Best for:

  • Paying for speed when users are waiting (business hours) and saving money when they are not.
  • Overnight batch jobs, evals, and background agents that can run on a cheaper model.
  • Moving traffic away from a provider that is congested at certain hours.
  - source: assistant
    strategy: schedule
    targets:
      - model: openai/gpt-4o
      - model: groq/llama-3.3-70b
    strategy_config:
      timezone: America/New_York
      peak_start: "08:00"
      peak_end: "18:00"
      peak_targets: [openai/gpt-4o, groq/llama-3.3-70b]
      off_peak_targets: [groq/llama-3.3-70b]
Enter fullscreen mode Exit fullscreen mode

Set the timezone to where your users are, not where the server runs. Windows that wrap midnight work too.

6. Super Intelligence Router (request difficulty)

What it does: Scores every request from 0 to 100 using signals in the request itself: prompt length, code, reasoning words, math, tools, images, conversation length, and requested reasoning effort. The score picks a tier: simple (below 25), standard (25 to 54), or complex (55 and above). The request goes to the first available target in that tier. Scoring adds well under a millisecond.

Best for: General-purpose names like auto where users and agents send a mix of easy and hard work. Greetings and rewrites go to a cheap model, debugging sessions with tools go to your strongest model. A hard request is never downgraded while a strong model is up: if its tier is empty or down, ZRouter tries stronger tiers first.

  - source: auto
    strategy: super_intelligence
    targets:
      - model: groq/llama-3.3-70b
      - model: openai/gpt-4o-mini
      - model: anthropic/claude-sonnet-4-5
    strategy_config:
      simple_targets: [groq/llama-3.3-70b]
      standard_targets: [openai/gpt-4o-mini]
      complex_targets: [anthropic/claude-sonnet-4-5]
Enter fullscreen mode Exit fullscreen mode

Every response explains the decision, which makes tuning easy:

X-ZRouter-Route: super_intelligence; tier=complex; score=63; reasons=code,tools,reasoning-cues
Enter fullscreen mode Exit fullscreen mode

7. Adaptive (ZRouter Pro)

What it does: Picks the target with the best observed mix of latency, error rate, and price, backs off from targets that are rate limited or failing, and keeps a session on the provider whose prompt cache is warm.

Best for: High-traffic production names where provider performance changes during the day and you want the gateway to react on its own. On an open source build this strategy falls back to round-robin.

8. Plugin strategies

What it does: Hands the choice to a routing plugin. ZRouter ships one, cheapest_healthy, which prefers the cheapest (or fastest) target whose recent error rate is acceptable. You can also write your own.

Best for: Cost-sensitive jobs that also need to avoid flaky providers, and teams with custom routing rules (data residency, customer tiers, internal model servers). If the plugin is missing, errors, or takes longer than 250 ms, the redirect falls back to weighted round-robin, so a broken plugin does not take the name down.

  - source: smart-router
    strategy: plugin
    strategy_plugin: cheapest_healthy
    strategy_config:
      prefer: cheapest     # or: fastest
      max_error_rate: 0.2
    targets:
      - openai/gpt-4o-mini
      - anthropic/claude-haiku-4-5
Enter fullscreen mode Exit fullscreen mode

Choosing a strategy

If the job needs... Use
A stable name and easy upgrades Single target
Load spread, canaries, gradual migration Round-robin with weights
The lowest price among good-enough models Lowest cost
One preferred model with emergency backups Failover
Speed by day, savings by night Schedule
One name for mixed easy and hard work Super Intelligence Router
Automatic tuning on live performance Adaptive (Pro)
Cheapest model that is also healthy, or custom rules Plugin (cheapest_healthy or your own)

Combine strategies by chaining virtual models

A target can be another virtual model. This lets you build routing in layers instead of one complicated rule.

virtual_models:
  # A cheap pool, balanced across two providers.
  - source: cheap
    targets:
      - { model: openai/gpt-4o-mini }
      - { model: groq/llama-3.3-70b }

  # A premium model with a backup.
  - source: premium
    strategy: failover
    targets:
      - { model: anthropic/claude-sonnet-4-6 }
      - { model: openai/gpt-5 }

  # Difficulty routing on top of both pools.
  - source: auto
    strategy: super_intelligence
    targets:
      - { model: cheap }
      - { model: premium }
    strategy_config:
      simple_targets: [cheap]
      complex_targets: [premium]
Enter fullscreen mode Exit fullscreen mode

Now auto sends easy work to a balanced cheap pool and hard work to a premium model with failover. Change cheap once, and every name that uses it follows. Chains are checked for cycles and can be up to 8 levels deep. Session keeping applies at every level, so a session stays on one concrete model.

More things a virtual model can carry

Routing is the main use, but the same name can hold other policies:

  • Shadowing. Give a virtual model the same name as a real model, such as anthropic/claude-opus-4-6, and point it somewhere else. Apps that hardcode that ID get the new model without a code change. This is useful for emergency swaps and for testing whether a cheaper model handles an existing flow.
  • Access control. Limit a name to certain user paths (teams or customer accounts), or disable it.
  • Guardrails. Attach up to 16 guardrails that run on every request to that name, such as PII redaction or blocked terms. Every app and agent that uses the name is covered.
  • Promotions. Publish a model for a limited time with per-user and total request and token caps, for launches and free trials.
  • Slowdown. Add artificial latency to test how your app behaves with a slower model before you switch.
  • Model list control. Set KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT=true so /v1/models lists only your curated names instead of every provider model.

A practical rollout plan

  1. Name your jobs. List the places your apps and agents call a model and give each job a name: assistant, coder, fast, summarizer.
  2. Start with single targets. Create each name pointing at the model you use today. Point your apps at the gateway URL and switch the model field to the new names. Behavior is unchanged.
  3. Add failover to critical names. Add a second provider to anything customer-facing.
  4. Optimize cost where quality allows. Move high-volume jobs to lowest cost, or put a general auto name on the Super Intelligence Router.
  5. Experiment safely. Use weighted round-robin to try new models on a small share of traffic, and compare usage, latency, and quality in the dashboard.
  6. Keep the names, change the models. From now on, model upgrades are edits in the gateway.

Key takeaways

  • A virtual model is a stable name that the gateway resolves to a real model at request time.
  • Your app keeps one endpoint, one key, and one model name. Switching models never means updating environment variables or reconfiguring endpoints.
  • Name virtual models after jobs, then give each job the routing strategy that fits it.
  • Round-robin spreads load, lowest cost saves money, failover protects critical paths, schedule follows the clock, the Super Intelligence Router follows request difficulty, adaptive follows live performance, and plugins cover everything else.
  • Chain virtual models to combine strategies, and attach guardrails and access rules so every caller gets the same policy.

ZRouter is open source under the MIT license and speaks the OpenAI API. Point any existing client at https://zrouter.si/v1, create your first virtual model, and your next model upgrade will be a configuration change instead of a release.

Top comments (0)