> ## Documentation Index
> Fetch the complete documentation index at: https://gomodel-feat-http-socks5.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Virtual Models

> Use GoModel virtual models to expose stable model names and test model swaps without changing app code.

<img src="https://mintcdn.com/gomodel-feat-http-socks5/2HgqHvsv37R2F8Qm/features/virtual-models.png?fit=max&auto=format&n=2HgqHvsv37R2F8Qm&q=85&s=c3fc888e753b74e10115771efc15dd24" alt="GoModel dashboard models page with the New virtual model button and an active redirect row" style={{ width: "100%", maxWidth: "1280px", height: "auto" }} className="rounded-lg" width="1600" height="636" data-path="features/virtual-models.png" />

## What virtual models do

A virtual model is a model you define, instead of one a provider advertises.
GoModel picks the kind from whether you give it a target:

* A **redirect** (an alias) is a stable name that points to a concrete provider
  model. Your app sends the name in the `model` field, and GoModel resolves it
  before sending the request upstream.
* An **access policy** has no target. It gates an existing model selector by
  `user_path` — or disables it entirely — without adding a new name. See
  [User Path](/features/user-path#model-access) for access control.

Create and manage both in the admin dashboard at `Models -> New virtual model`.
Fill **Target model** to make a redirect; leave it empty to make an access
policy on the **Source** selector.

GoModel does not persist empty, ineffective access policies, whether saved from
the dashboard or through the admin API. If a saved policy has no target, user
paths, description, or slowdown and its enabled state matches the inherited/default
access state, GoModel removes the stored row. It retains an otherwise empty
policy when that row still overrides a disabled default or an inherited
user-path restriction.

## Add artificial model latency

Set **Slowdown** on a concrete model or redirect to add a fraction of measured
inference time. Active values range from `0.1` to `10`; for example, `0.5`
adds 50%, so a response that takes 2 seconds upstream is returned after about
3 seconds. Set `0` to explicitly disable slowdown, including slowdown inherited
from a concrete model. Leaving the field empty leaves the setting unspecified.
Delays honor request cancellation.

For Chat Completions and Responses API SSE streams, GoModel drains the upstream
in the background and releases each read chunk on the scaled timeline. This
preserves the provider's relative chunk timing while delayed chunks accumulate
in memory. A large factor on a long or high-volume stream can therefore use
substantial memory. Realtime WebSocket sessions are not slowed.

When an alias and its concrete target both have a slowdown, the alias value
wins. An alias without its own value inherits the selected target model's
slowdown; set the alias to `0` to override that inheritance. This also applies
to load-balanced virtual models after a target is selected.

The rest of this page covers redirects.

## Use stable names

You can expose names like `regular` and `smarter` instead of provider-specific
model IDs.

* `regular` -> `anthropic/claude-sonnet-4-6`
* `smarter` -> `anthropic/claude-opus-4-6`

Your app can then send:

```json theme={null}
{
  "model": "regular",
  "messages": []
}
```

## Load balance across models

A redirect can point at **several** target models and spread requests across
them. Add more than one target in the editor (**Add target**) and pick a
**strategy**:

* **Round-robin** (default) rotates requests evenly across the targets. Set a
  per-target **weight** to bias the share — a target with weight `2` receives
  twice the traffic of a target with weight `1`.
* **Lowest cost** routes every request to the cheapest currently-available
  target, ranked by the model registry's input + output per-token price. When a
  target has no registry pricing it is skipped while a priced target exists; if
  none are priced, the first listed target is used.
* **Failover** always routes to the first currently-available target in the
  order listed: the targets form a priority list, and lower entries serve only
  while every entry above them is unavailable or fails. Weights and session
  keeping do not apply.

Targets that the gateway cannot currently serve (unknown model, provider down)
are skipped automatically, so a redirect keeps working as long as one target is
available. A redirect with a single target behaves exactly like a plain alias.

Whatever the strategy, the remaining targets are the redirect's **failover
chain**: when the chosen target fails with a retryable error, the request is
retried against the other available targets in the order listed. The editor's
**Failover** checkbox (on by default; `failover: false` as code) switches that
off for one redirect, so a failed request is answered with the error instead;
it can also be switched off globally or by the request's workflow. See
[Failover](/features/failover) for the errors that trigger a retry and for
adding a failover chain to a real model by shadowing it.

Requests belonging to one detected client session stick to the target that
served the session first (**Session keeping**, on by default), so
conversations keep their provider prompt cache warm across turns. Untick the
editor's **Session keeping** checkbox (or set `session_affinity: false`) to
restore stateless balancing — see
[Session Keeping](/features/session-keeping).

```json theme={null}
{
  "model": "smart",
  "messages": []
}
```

For example, point `smart` at both `openai/gpt-4o` and `anthropic/claude-sonnet-4-6`
with the round-robin strategy to split load, or at a premium and a budget model
with the cost strategy to always take the cheaper one.

## Chain virtual models

A redirect target can name **another virtual model** instead of a concrete
model. The inner virtual model is resolved at request time, so its targets,
weights, and strategy are defined once and reused everywhere it is referenced —
no duplicated target lists that drift apart.

```yaml theme={null}
virtual_models:
  # Shared base: round-robin over small models.
  - source: cheap
    targets:
      - { model: openai/gpt-4o-mini }
      - { model: groq/llama-3.3-70b }

  # Layered on top: the cheap pool is one leg next to a premium model.
  - source: smart-frugal
    strategy: round_robin
    targets:
      - { model: cheap, weight: 3 }
      - { model: openai/gpt-4o }

  # A plain alias onto the pool.
  - source: production
    target: cheap
```

Resolution composes level by level: the outer strategy picks one of its legs
(a chained leg counts like any target, honoring its weight), and a chosen
virtual-model leg then applies its own strategy among its own targets. Session
keeping pins at every level, so a session stays on the same concrete model.

* The **cost** strategy prices a chained leg at the cheapest concrete model
  currently available behind it.
* A chained virtual model that is **disabled**, or has no available target, is
  skipped like an unavailable provider model.
* Only the **outer** virtual model's `user_paths` and `slowdown` apply to a
  request; the inner one's scope is not consulted when it is reached through a
  chain.
* `/v1/models` lists a chained redirect with the metadata of its first
  available concrete model.

A target written as one name is matched by name wherever it is declared —
`config.yaml`, the dashboard, or the admin API — so `team/cheap` reaches the
virtual model of that name even though it looks like `provider/model`. Set
`provider` explicitly (`{ provider: groq, model: llama-3.3-70b }`) to pin a
target to a concrete model regardless of any virtual model sharing the name.

Chains must be acyclic (`a -> b -> a` is rejected, with the cycle spelled out)
and at most 8 virtual models deep. Both rules are enforced when saving from the
dashboard and at startup for declarative entries. A target must exist when it is
saved — create the inner virtual model first — and a virtual model that others
chain through cannot be deleted or renamed until they are repointed.

## Rename or repoint a redirect

Open a redirect in the dashboard and edit its **Source** to rename it. GoModel
moves the definition to the new name in one step — the old name stops resolving,
so update any app still sending it. Renaming onto a name that already belongs to
another virtual model is rejected; renaming onto a concrete model name shadows
that model (see [Shadow a model](#shadow-a-model)).

Redirects that shadow a model can be edited the same way — use the model row's
**Edit redirect** action. Virtual models declared as code keep their `source`
fixed in configuration; rename them by editing the declaration.

## Define virtual models as code

Operators who manage configuration as infrastructure-as-code can declare virtual
models in `config.yaml` or the `VIRTUAL_MODELS` environment variable instead of
the dashboard. Declarative entries are applied at startup, **override** any
admin-store row with the same `source`, and are shown read-only in the dashboard
(a **Config** badge). Edit the configuration to change them.

In `config.yaml`:

```yaml theme={null}
virtual_models:
  # A plain alias.
  - source: regular
    target: anthropic/claude-sonnet-4-6
    slowdown: 0.5

  # A weighted round-robin load balancer.
  - source: smart
    strategy: round_robin
    targets:
      - model: openai/gpt-4o
        weight: 2
      - model: anthropic/claude-sonnet-4-6

  # A cost-based load balancer.
  - source: cheap
    strategy: cost
    targets:
      - { model: openai/gpt-4o }
      - { model: groq/llama-3.3-70b }
```

The same list as a single environment variable (JSON), which overrides
`config.yaml` entries with a matching `source`:

```env theme={null}
VIRTUAL_MODELS=[{"source":"smart","strategy":"cost","targets":[{"model":"openai/gpt-4o"},{"model":"groq/llama-3.3-70b"}]}]
```

Each entry accepts `source`, a single `target` (shorthand) or a `targets` list,
`strategy` (`round_robin`, `cost`, or `failover`), `session_affinity` (default `true`; see
[Session Keeping](/features/session-keeping)), `failover` (default `true`; see
[Failover](/features/failover)), `user_paths`, `description`,
`slowdown` (`0` disables inherited slowdown; active factors range from `0.1` to
`10`), and `enabled`.
A target `model` may name another declared virtual model (see
[Chain virtual models](#chain-virtual-models)).
Leave the targets empty to declare an access policy on the `source` selector. An
invalid declaration (unknown strategy, missing or self-referential target, a
chain cycle or a chain deeper than 8 virtual models, or a target `provider` that
matches no configured provider — a typo) fails startup with a clear error
listing the registered provider names.

Two lenient exceptions keep valid setups booting:

* A target `provider` that is declared under `providers:` but did not register —
  typically because its credentials are unset in this environment — only logs a
  warning; the target stays unavailable until the credentials resolve. A
  `config.yaml` shared across environments still boots everywhere.
* Whether a target *model* exists is never checked at startup: the model catalog
  loads asynchronously, so availability is a runtime concern and an unavailable
  target is simply skipped at resolve time.

## Expose only redirects

To hide provider models from `GET /v1/models`, set:

```env theme={null}
KEEP_ONLY_ALIASES_AT_MODELS_ENDPOINT=true
```

When this is enabled, GoModel returns enabled redirects from `/v1/models`
instead of the full provider model list. The setting is also listed in
`.env.template`.

## Shadow a model

A redirect can "shadow" a model by using the same name as an existing model and
pointing it somewhere else. This lets you override a requested model without
changing application code.

For example:

* source: `anthropic/claude-opus-4-6`
* target model: `anthropic/claude-sonnet-4-6`

Your app can keep sending `anthropic/claude-opus-4-6`, while GoModel routes the
request to `anthropic/claude-sonnet-4-6`.

## A/B testing

Redirects are useful for short model experiments. Move a redirect from one
target to another, then compare app behavior, latency, and usage.

For example, point `smarter` at Opus for one test and Sonnet for another. You can
also shadow `opus-4-6` with `sonnet-4-6` to check whether the same app flow still
works.
