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.
- Pick which conditions trigger fallback (by default:
5xx,timeout,rate_limit). - Set
timeout_ms(default30000) andmax_attempts(default3).
When fallback triggers
Model translation across providers
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.
If the client requests gpt-5-mini and fails over to Anthropic, the gateway applies the VK’s model_aliases to pick the Anthropic equivalent (e.g. claude-haiku-4-5-20251001). Configure this per VK:
:fallback suffix is optional, if absent the gateway uses the same model name against the next provider and expects it to exist.
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-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
Diminishing returns after 3 entries. With the defaulttimeout_ms=30000 and max_attempts=3, the worst-case wall-clock to exhaust the chain is ~90s; the latency budget of the original call is gone well before then and the client has probably given up. Use chains of 2-3 for latency-sensitive traffic and lower per-entry timeout_ms if you need a tighter total budget; longer chains are fine for batch/offline.