# Virtual models

Source: https://platform.lyrouter.com/ai_gateway/docs/presets/en
Last updated: 2026-10-19

A virtual model bundles a primary model, fallback models, a Channel sort strategy, inference params and a system prompt under one name. The client sets the request's `model` to `@vmodel/<name>`; the gateway calls the primary model first, falls back to the fallback models in order when it fails, and merges the params and system prompt into the request sent upstream. To switch models or adjust params later, you only edit the virtual model; client code stays the same.

## When to use it

- Several applications share one set of models and params that you want to adjust in one place.
- You want calls to move to another model automatically when the primary model is unavailable, without the client switching models and retrying itself.
- You want to standardize and lock params such as `temperature`, `max_tokens` and the output format.
- You want to add a standard system prompt to requests that do not bring their own.

If you only need to route between several channels of the same model, call the model ID directly; see [Channels and routing](https://platform.lyrouter.com/ai_gateway/docs/routing/en).

## Calling a virtual model

Set `model` to `@vmodel/<name>`; everything else is the same as calling a regular model. The example below calls a virtual model named `support-bot`. Setting the environment variables is covered in [Your first model call](https://platform.lyrouter.com/ai_gateway/docs/quickstart/en):

```bash
curl "$LYROUTER_BASE_URL/v1/chat/completions" \
  -H "Authorization: Bearer $LYROUTER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model":"@vmodel/support-bot","messages":[{"role":"user","content":"Introduce yourself in one sentence"}]}'
```

- **Supported endpoints**: `/v1/chat/completions`, `/v1/messages` and `/v1/responses`. Image, video and audio endpoints do not support virtual models and return 403 `Model is not available in the model plaza`; use a specific model ID instead.
- **Names are case-sensitive**: names contain only lowercase letters, digits and hyphens, so `@vmodel/Support-Bot` does not find `support-bot`. The old prefix `@preset/<name>` is equivalent to `@vmodel/<name>`.
- **Scoped to a workspace**: a virtual model belongs to one workspace, and any valid API key in that workspace can call it; there is no separate per-key authorization.
- **Finding available names**: `GET /v1/models` lists the workspace's enabled virtual models as `@vmodel/<name>`. Members who cannot see the Virtual models page can look up names there.
- **Upstream and billing**: the upstream receives the ID of the model actually called as `model`, and cost is calculated for that actual model.

## Creating a virtual model

The current Account Owner clicks **New virtual model** on the workspace's **Virtual models** page. Account Members do not see this menu, but they can call enabled virtual models. The form has four parts, Basics, Model & routing, Inference params and System prompt; only Virtual model name and Default model are required.

| Field | Description |
| --- | --- |
| Virtual model name | The call name, that is, the part after `@vmodel/`. Lowercase letters, digits and hyphens only, starting with a letter or digit, and unique within the workspace |
| Default model | The model to use first (the primary model); the list shows it in the Default model column |
| Fallback models | Optional; you can add several, tried in the order entered when the primary model is unavailable |
| Channel sort strategy | How channels are sorted when the same model has several: Price first, Throughput first, Latency first, or Load balanced (the default) |
| Inference params | 17 params in seven groups; each param can be locked on its own, see below |
| System prompt | Added by the gateway when the client request has no system prompt; see below |

(Screenshot: The virtual model list shows the copyable call name, primary model, locked params and status.)

- Default model and Fallback models can be chosen from the model catalog or typed as model IDs. Saving does not check whether these models are usable in the workspace, and unusable models are skipped at call time. Before saving, make sure they are enabled in the [model marketplace](https://platform.lyrouter.com/ai_gateway/docs/choose/en) or served by a working [BYOK](https://platform.lyrouter.com/ai_gateway/docs/byok/en).
- Once saved, the virtual model is Active and reaches the gateway within seconds; you do not need to change API keys. The **Save and test** button is not available yet, so send a call directly to verify.

(Screenshot: Set the primary model, fallback models and inference params in the virtual model configuration; call it by its actual name.)

### Common save errors

| Message | Cause |
| --- | --- |
| Virtual model name must match ^[a-z0-9][a-z0-9-]*$ | The name contains uppercase letters, spaces or Chinese characters, or starts with a hyphen |
| Please choose a default model | Default model is empty |
| `slug already in use in this workspace` | The workspace already has a virtual model with this name |
| The JSON Schema isn't valid JSON — please check the syntax. | The JSON Schema text of Response Format has a syntax error |
| `invalid preset params: response_format.json_schema.name is required ...` | The JSON Schema object lacks a non-empty `name`; see Inference params below |
| Please enter the function name | Tool Choice is set to Specific function, but no function name was entered |

## Which model is actually called

The gateway tries models in the following order and moves to the next model only after every channel of the previous one fails:

1. The virtual model's Default model (the primary model).
2. The Fallback models, in the order entered. The gateway never moves a fallback model forward because it is faster or cheaper.
3. The workspace's Default Model, if one is set in [Smart routing](https://platform.lyrouter.com/ai_gateway/docs/routing/en).

When a model is served by several channels, they are sorted by the virtual model's Channel sort strategy; Prefer this Channel and Only use this BYOK set in BYOK still take precedence. The full fallback rules and the cases that never fall back are in [Retries and fallback](https://platform.lyrouter.com/ai_gateway/docs/fallback/en). The two most common are:

- When a guardrail model policy blocks the model actually selected, the request returns 403 `Request blocked by guardrail policy` at once, and no fallback model is tried. Guardrails check the actual model ID, so entering `@vmodel/...` in a model policy has no effect.
- When the current model does not support the requested input or endpoint (for example, sending an image to a text-only model), the request returns 400 at once, and no fallback model is tried. Fallback models should therefore support the same input types and tool calling as the primary model.

## Inference params: defaults and locks

An unlocked param is a default value; a locked param is enforced:

| State | Client did not send the param | Client sent the param |
| --- | --- | --- |
| Unlocked | The virtual model's value is used | The client's value is used |
| Locked | The virtual model's value is used | The virtual model's value is used and overrides the client's |

| Group | Params | Values |
| --- | --- | --- |
| Sampling | `temperature`, `top_p`, `top_k`, `seed` | 0 to 2; 0 to 1; integer ≥ 0; integer |
| Length | `max_tokens`, `stop` | integer ≥ 1; string or array of strings |
| Penalty | `frequency_penalty`, `presence_penalty`, `repetition_penalty` | -2 to 2; -2 to 2; greater than 0 |
| Output format | `response_format` | `text`, `json_object`, `json_schema` |
| Reasoning control | `reasoning_effort`, `enable_thinking`, `thinking_budget` | `minimal`/`low`/`medium`/`high`; true or false; integer ≥ 0 |
| Tools | `tool_choice`, `parallel_tool_calls` | `auto`, `none`, `required` or a specific function; true or false |
| Streaming | `stream`, `stream_options.include_usage` | true or false; true or false |

- Only params that have a value can be locked. Object params such as `response_format` and `tool_choice` are replaced as a whole, not merged field by field.
- Unlocked defaults are skipped automatically when they do not apply: `tool_choice` is not added when the request has no `tools`, and `response_format` set to `json_object` is not added when the word "json" does not appear in the conversation. Locked params are always written.
- Params are merged as-is under their Chat Completions field names, `/v1/responses` requests included. For example, `max_tokens` is not renamed to `max_output_tokens`, and the upstream may return 400 as a result. For virtual models used by Responses clients, set only params both protocols share, such as `temperature` and `top_p`.
- `stream` changes the response form: when it is set to true and applied, even non-streaming requests receive an SSE event stream.
- `reasoning_effort`, `enable_thinking` and `thinking_budget` belong to different vendors, and the gateway does not convert between them; fill in the one supported by the primary model's vendor.
- When Response Format is set to JSON Schema, enter the complete OpenAI `response_format.json_schema` object, for example `{"name": "answer", "schema": {"type": "object", "properties": {"text": {"type": "string"}}}}`. Pasting only the schema itself fails to save.
- A greyed-out control with the hint "Not supported by the current primary model" is only a console hint; the gateway does not strip params based on model capabilities.

## When the system prompt is injected

| Endpoint | Injection rule |
| --- | --- |
| `/v1/chat/completions` | Inserted at the start when `messages` has no message whose `role` is `system`; not injected if one exists |
| `/v1/messages` | Injected when the request has no `system`; not injected if it has one |
| `/v1/responses` | Filled in when `instructions` is empty; appended after a blank line when it already has content |

The system prompt cannot be locked: when a Chat or Messages request brings its own system prompt, the virtual model's prompt does not apply. Messages with the `developer` role do not count as a system prompt. When the gateway moves to a fallback model, the prompt is injected once more and never stacked twice.

## Editing, disabling and deleting

| Action | Effect on calls |
| --- | --- |
| Edit | New requests use the new configuration once saved. Changing the name changes the call name, and the old name stops working immediately |
| Disable | The status becomes Disabled; the gateway no longer recognizes the name, and `GET /v1/models` no longer lists it |
| Enable | The original name works again |
| Clone | Opens the create form prefilled with the full configuration and a `-copy` suffix on the name; nothing is created until you click save |
| Delete | Deleted permanently after you type the name to confirm; you can then create a new one with the same name |

After a virtual model is disabled, deleted or renamed, requests using the old name no longer match any virtual model. If the workspace has a Default Model, such chat requests are answered by it instead; otherwise they return 503 `all routed candidates exhausted`.

## Verification and FAQ

After a call, open its record in the [request logs](https://platform.lyrouter.com/ai_gateway/docs/logs/en). In the route trace, Virtual model shows the virtual model's name (without the `@vmodel/` prefix), and Model (actual) shows the model that answered; see [Inspecting request routing](https://platform.lyrouter.com/ai_gateway/docs/route-log/en).

### What should I check when a call returns 503 `all routed candidates exhausted`?

**Answer:** First check the spelling and case of `@vmodel/<name>`, then use `GET /v1/models` to confirm it is listed, which means it is enabled. If the name is correct, ask the Owner to confirm that Default model and Fallback models are enabled in the model marketplace or served by a working BYOK.

### Why did a model outside the virtual model answer?

**Answer:** After the primary model and every fallback model fail, the gateway uses the workspace's Default Model. When the name is misspelled or the virtual model is disabled, chat requests are also answered by the Default Model directly. Model (actual) in the logs confirms this; if you do not want this fallback, ask the Owner to clear the Default Model in Smart routing.

### The upstream returns 400 after I lock a param. What should I do?

**Answer:** Locked params are always written into the request, and an upstream that does not support one rejects it. Open Raw packets in the log, compare Upstream request with Upstream response to find the rejected param, then unlock or remove it.
