Configuring a chain
On the VK edit screen:- Select a primary provider credential.
- Add one or more fallback credentials in the order they should be tried.
chain is the ordered credential list. max_attempts (default 3) sets how many of them the gateway tries for one request. A key with no fallback gets exactly one attempt.
When fallback triggers
The gateway decides which failures move to the next provider. It reads the real upstream result, and the rule is the same for every virtual key. There is no per-key trigger list. A narrowed list could only turn a failure the gateway can recover from into one that your callers see.Crossing providers mid-chain
A single VK may have mixed providers (OpenAI + Anthropic + Bedrock). The gateway uses Bifrost’s provider-dispatch library to translate payloads: samemessages schema, different wire formats.
The model does not change across the chain. One request resolves to one model. The gateway sends that same model name to the next credential and expects the provider to serve it. So a chain helps when its entries overlap on the models you call: an Anthropic credential with a Bedrock credential that serves Anthropic models, or two OpenAI accounts in different regions.
The gateway skips a credential that cannot serve the resolved model. It does not call it. When the model name tells the gateway which provider owns it, through an alias, the provider/model form, or a known bare name, the chain visits only that provider’s credentials on the VK. If the name tells the gateway nothing, the gateway walks the chain as configured, and the providers’ own answers decide the outcome.
A VK narrowed to specific providers is enforced here too. If the model resolves to a provider that the key cannot use, the gateway refuses with model_not_allowed. The refusal names the model and tells the key’s owner to widen the key’s provider access.
Streaming
Fallback behaviour differs based on when failure occurs:- Before first chunk emits → transparent fallback. The stream-setup call (
bifrost.ChatCompletionStreamRequest) walks the chain the same way non-streaming dispatch does; the client sees a single stream from whichever slot accepted the request.X-LangWatch-Fallback-Countreports the skipped slot count. - After first chunk has streamed → no mid-stream fallback. The gateway emits a terminal
event: errorframe (withcode: upstream_mid_stream_failure) and closes the connection. The client may retry; a fresh request would then re-walk the chain.
Observing the chain in traces
Fallback attribution today:- Prometheus counter
gateway_provider_attempts_total{credential_id, outcome}increments once per attempt, withoutcomeinsuccess | fallback_success | retryable_5xx | not_found | rate_limit | timeout | network | circuit_open | non_retryable | chain_exhausted | context_done. - Response header
X-LangWatch-Fallback-Count: N, how many fallbacks were attempted before success. - Request-id correlation via
X-LangWatch-Gateway-Request-Id, join the metric + log line back to the specific trace.
Per-attempt nested spans (
langwatch.fallback.attempt, .reason attrs) are a v1.1 observability follow-up. In v1, the counter + header give you the aggregate picture; per-attempt reasoning lives in the gateway log line for that request-id.provider_error or upstream_timeout as the type).
Circuit breaker
Each provider has an independent circuit breaker with a sliding window:
Override at the service level:
Per-replica, not shared: each gateway replica maintains its own breaker state. This is deliberate, under a large-scale outage, N replicas rediscovering the recovered provider independently is resilient; depending on Redis for breaker consensus is not.
Circuit state is emitted as a Prometheus gauge
gateway_circuit_state{credential_id}, one series per credential slot. The state is encoded in the gauge value, not in a label: 0 closed, 1 open, 2 half-open. Alert on gateway_circuit_state == 1 for 5m to catch real provider outages (as distinct from transient blips).
When a request hits a circuit that’s currently open, the gateway skips to the next entry in the fallback chain immediately, no wasted round-trip to a provider we already know is down.
Sizing the chain
Returns decrease after 3 entries. Each extra attempt spends one more provider round-trip, one after the other. With the defaultmax_attempts=3, the worst case is three upstream calls in a row. The client has usually stopped waiting before the last one answers. Use chains of 2 to 3 entries for latency-sensitive traffic. Lower max_attempts if you need a tighter total budget. Keep longer chains for batch and offline work, where wall-clock time is cheap.