✨ GPT Image 2.6 is now available. Come and experience it!

Back to blog
AI Engineering

Reliability for Multi-Model Systems: Timeouts, Retries, Circuit Breaking, and Failover

A practical playbook for multi-model systems: classify errors before retrying, set timeouts in layers, break circuits at the right granularity, and fail over in four defined levels — plus why failed requests still cost money.

October 28, 2026 · 11 min read
Wiring up multiple model providers takes an afternoon. Keeping the product alive when one of them degrades takes considerably longer. In production, failures rarely look like "the model answered wrong." They look like this:
an upstream provider returns 429 in the middle of your peak hour;
the connection opens but the first byte never arrives;
a streamed response dies halfway and the client is left holding truncated JSON;
one regional endpoint is unavailable while the global status page stays green;
a model is deprecated, or a preview endpoint is shut down on a published date;
a content filter refuses before any output is generated — and you are still billed;
output hits the token ceiling and returns a truncated result with finish_reason: length.
The order of operations that actually works is: classify, then retry; put a budget on retries; break circuits above that; fail over in defined levels; and cover the whole thing with deprecation management and observability.

1. Classify the error before deciding to retry

Error class
Examples
Retry?
Handling
Rate limit
429
Yes
Back off 1–5s, then alternate provider
Transient upstream
500 / 502 / 503 / 504
Yes
Back off, then alternate provider
Network / stream
reset, first-byte timeout, mid-stream drop
Yes, with care
Check for side effects before resending
Request is wrong
400 / 422
No
Fix the payload; retrying reproduces the bug
Auth / permission
401 / 403
No
Alert; a key or scope problem
Context too long
context_length_exceeded
No
Trim, chunk, or route to a longer-window model
Policy refusal
safety block
No
Rewrite the prompt or escalate to a human
Truncated output
finish_reason: length
No
Raise the cap or demand shorter output
The most common waste is retrying errors that cannot succeed. Retrying a 400 once triples your cost and your latency and changes your success rate by exactly zero.

2. Retries need a budget, not just a policy

At most two retries per request (three attempts total).
Exponential backoff with full jitter: sleep random(0, base * 2^n) rather than a fixed delay, so a fleet of clients does not retry in lockstep.
A global retry rate ceiling — for example 5%. Above it, stop retrying and treat it as an incident. A retry storm is self-inflicted: a client that retries everything twice multiplies load by 3x at exactly the moment the upstream is weakest.
Idempotency guards. Tool calls with side effects — placing an order, sending mail, writing to a database — must carry a dedupe key, or must not be retried at all. On timeout, query state instead of resending.

3. Timeouts come in layers

One global timeout is not a timeout strategy; it is a way to pay for work you throw away.
Layer
Suggested starting point
Connect
2–3s
First byte (TTFT)
10–30s for standard models, 30–60s for reasoning models
Streaming idle
60s with no delta arrives → treat as dead
Total ceiling
derived from max_tokens, not a global 300s
Ordering matters: upstream timeout < your gateway timeout < client timeout. Reverse that and you will kill connections while the upstream is still generating — and generate a bill for output nobody reads.

4. Break circuits at the right granularity

Granularity: provider x model x region. A global "model is down" breaker will take healthy traffic offline with the sick traffic.
Trigger: error rate above 50% within a window, with at least 20 samples. Small samples produce false alarms; require volume.
Cooldown: 30–60s before allowing probe traffic.
Half-open probe: admit 1–5 real requests; restore on success, keep the breaker open on failure.
Make breaker state observable. Silent degradation is how you end up with a week of low-quality answers and no incident ticket.

5. Failover in four levels, each with different requirements

Level
What switches
User-visible change
Prerequisite
L1
Same model, different provider or region
None
Identical request format
L2
Equivalent model at the same tier
Slight style shift
Verified schema, tool format, context window
L3
Cheaper / smaller model
Quality drop
Tagged in logs, ideally surfaced in UI
L4
Cached or templated response
Explicitly not a model answer
Honest disclosure to the user, never disguised
The compatibility checklist for L2 — this is where most failovers quietly break:
Structured output: strict JSON-schema enforcement versus prompt-based JSON hinting.
Tool calling: parallel calls, argument validation, tool-name length limits.
System role: some models have no separate system role and fold it into the first user turn.
Multimodal input: image formats, size caps, whether PDFs are accepted natively.
Streaming: delta formats and end-event semantics differ between vendors.
Stop reasons: length versus max_tokens versus stop — your parser must handle all of them.
Context window: a request that is fine on a 1M-token model returns 400 on a 128K model.

6. Deprecation is planned failure

Treat vendor deprecation notices as scheduled outages with a known date.
Track the deprecation announcements and put shutdown dates on a calendar with at least a 30-day migration buffer.
Pin snapshot versions in production; do not put a preview suffix in a path that carries an SLA.
Run shadow traffic: send 5–10% of requests to the successor model and compare output properties, not exact strings.
Recalculate the bill, not just the code. Published examples are blunt about this: when a small audio model was retired, the recommended successor cost roughly three times as much per audio token, and a price-equivalent option existed only behind a different API surface — meaning a rewrite, not a config change. Preview image endpoints have also been shut down on fixed dates, after which requests simply fail.

7. Failed requests still cost money

Three mechanisms worth budgeting for:
  1. Some providers bill certain pre-output refusals at standard rates.
  1. When you time out and disconnect, the upstream may have already generated and billed the output.
  1. Every failed retry attempt is billed like the one that finally succeeded.
So failure rate is a cost metric, not just a quality metric. The single most honest number to put on a dashboard is cost per successful task: total spend divided by completed tasks. It captures retries, failures, and failover overhead in one figure.

8. Log these twelve fields

request_id, upstream request id, provider, model, attempt count, failover level, TTFT, total duration, input tokens, output tokens, finish_reason, cache hit, estimated cost.
Four golden signals, reviewed weekly: success rate, p95 latency, failover rate, cost per successful task.

9. Rehearse the failures

Once a quarter, deliberately break things in staging:
  1. Inject 429 for 10 minutes; confirm backoff and provider rotation work.
  1. Inject first-byte delays past your TTFT threshold; confirm the breaker opens.
  1. Disable the primary provider entirely; confirm L2 failover passes your schema validation.
  1. Return a truncated finish_reason: length payload; confirm the client does not treat it as success.
  1. Change one field name in a structured-output response; confirm validation catches it instead of silently writing bad data.
  1. Take the primary key out of scope; confirm you get an alert, not a retry loop.

The takeaway

Reliability in a multi-model system is not about preventing every error. It is about making sure that when an error happens, the fallback path was written, tested, and priced before it was needed.
Billing behaviour and deprecation dates referenced above are illustrative examples of public announcements and change over time. Confirm current policy with each provider.