本文档尚未翻译成简体中文,现显示英文原文。
Model fallbacks
Declaring a candidate chain with `models[]`
Instead of a single model, send models — an ordered list. We try them in order and stop at the first one that answers. You are billed for the one that answered, and only for that one.
{
"models": ["lgai/exaone-4.0-32b", "google/gemma-4-26b-a4b"],
"messages": [{ "role": "user", "content": "요약해 줘." }]
}If models is present it wins; model is ignored. If neither is present, the request uses the service default, google/gemma-4-26b-a4b.
How the chain is built
Your list is not the chain — it is the input to it.
- Expand. Each model id is replaced by every provider that serves it, ordered by priority. Local GPU slots have the lowest priority number, so they come first. See Local-first routing.
- Concatenate. The expanded lists are joined in the order you gave. Duplicate provider/model pairs are collapsed, so listing the same model twice does nothing.
- Filter. Your
provider{}preferences, context-window checks, and cooldowns remove candidates. See Provider routing. - Run. Candidates are attempted in order until one responds.
So ["A", "B"] where A has a local slot and one external endpoint, and B has one external endpoint, produces a chain of three attempts, not two.
Unknown model ids
The rules differ between model and models, on purpose:
| Request | Behaviour |
|---|---|
"model": "nope/nope" | 400 model_not_found |
"models": ["nope/nope", "google/gemma-4-26b-a4b"] | Unknown entry is dropped silently; the request runs on google/gemma-4-26b-a4b |
"models": ["nope/nope", "also/nope"] | 400 model_not_found |
A list means "any of these", so an entry we cannot serve is simply not a candidate — that is the whole point of a fallback list. A single model is a specific instruction, so a typo is an error.
When retries happen, and when they stop
If a candidate refuses the request, times out on connect, or returns a non-2xx status, nothing has been sent to you yet and we move to the next candidate. If a candidate accepts and starts responding and then fails, we cannot silently restart on a different model — you have already received part of an answer from the first one. Instead:
- Streaming: an error event is emitted on the stream and the stream ends. See Streaming.
- Non-streaming: the failure surfaces as
502 provider_error.
This is why the model in X-MyIP-Model is authoritative: it is the model that produced the bytes you received.
When the whole chain fails
| Status | error_type | Meaning |
|---|---|---|
| 404 | no_endpoints_found | Nothing was left to try after filtering |
| 408 | timeout | At least one candidate hit the upstream timeout |
| 502 | provider_error | Candidates failed. metadata.provider_code carries the last upstream status |
| 503 | model_loading | Every candidate was a local slot that could not wake up in time. metadata.retry_after_sec and a Retry-After header tell you when to come back |
None of these are billed. A request that produced no tokens costs nothing.
Seeing what happened
Response headers name the winner:
X-MyIP-Model: google/gemma-4-26b-a4b
X-MyIP-Provider: MyIP Local GPU
X-MyIP-Generation-Id: gen-01JD8Q2K7M4X9NThe full attempt list — every candidate we tried, its status and how long it took — is in the generation record:
curl "https://openrouter.myip.co.kr/api/v1/generation?id=gen-01JD8Q2K7M4X9N" \
-H "Authorization: Bearer $MYIP_API_KEY"provider_responses in that response is the attempt log. If you are debugging why a request took longer than expected, that is where to look first.
Examples
curl https://openrouter.myip.co.kr/api/v1/chat/completions \
-H "Authorization: Bearer $MYIP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"models": ["lgai/exaone-4.0-32b", "google/gemma-4-26b-a4b"],
"messages": [{ "role": "user", "content": "다음 문단을 세 줄로 요약해 줘." }]
}' -D -The -D - on the curl example prints the response headers, which is the quickest way to see which model won.
A practical pattern: cold-start insurance
Local models can be asleep. Putting a second local model behind the first turns a possible 503 model_loading into a slightly different answer:
{ "models": ["lgai/exaone-4.0-32b", "google/gemma-4-26b-a4b"] }Because the GPU node is exclusive, whichever of the two is currently resident answers immediately. This is usually a better default than pinning a single local model and retrying on 503.
route
"route": "fallback" is accepted and does nothing — it describes the behaviour models[] already has. We take it rather than rejecting it so that code written for other gateways keeps working. There is no auto-router, no cost/quality frontier, and no model fusion; the chain is exactly what you declared. See Principles.
Combining with provider preferences
models[] chooses which models; provider{} chooses which endpoints for those models, and in what order. They compose:
{
"models": ["google/gemma-4-26b-a4b", "lgai/exaone-4.0-32b"],
"provider": { "only": ["local-gpu"], "allow_fallbacks": true }
}Note that provider.allow_fallbacks: false truncates the whole chain to its first candidate — including candidates that came from later entries in models[]. If you want a fallback list, leave it alone.
最后更新于 2026年9月5日