Pattern 04 — Single Provider vs. Resilient Provider Chain
Hard-coding a single LLM provider is a single point of failure. This pattern shows how to add multi-provider resilience with circuit breaking, automatic failover, and cost-aware routing.
❌ Before — The Single-Provider Prototype
flowchart LR
App["Application"]
API["OpenAI API\n(only option)"]
Err["Exception\nHTTP 429 / 500 / 503\n→ crashes caller"]
App -->|"direct SDK call"| API
API -.->|"rate limit\noutage\nlatency spike"| Err
Err -->|"unhandled"| App
style App fill:#1e1e2e,stroke:#585b70,color:#cdd6f4
style API fill:#313244,stroke:#585b70,color:#cdd6f4
style Err fill:#3b1219,stroke:#f38ba8,color:#f38ba8
What breaks:
- Any provider outage or rate-limit kills your application
- No retry → transient errors become hard failures
- No fallback → 100% blast radius on provider degradation
- No circuit breaking → the application hammers a failing provider
- No routing → you're always paying premium pricing even for cheap tasks
✅ After — The Resilient Provider Chain
flowchart TD
App["Application"]
subgraph Routing["🟠 L04 Model Runtime — Routing"]
WR["WorkloadRouter\ncost · latency · capability scoring"]
end
subgraph Reliability["🔴 L03 Reliability"]
CB_A["CircuitBreaker A\nfailure_threshold=5"]
CB_B["CircuitBreaker B\nfailure_threshold=5"]
CB_C["CircuitBreaker C\nfailure_threshold=5"]
FC["FallbackChainPort\n[A → B → C]"]
RT["retry(max_attempts=3\nbackoff=2.0)"]
end
subgraph Providers["Providers"]
PA["Provider A\nOpenAI gpt-4o\n(primary, capable)"]
PB["Provider B\nAnthropic claude-3-5\n(fallback, capable)"]
PC["Provider C\nOllama llama3\n(offline, free)"]
end
subgraph Observability["🔵 L02 Observability"]
CL["CostLedger\nper-provider cost tracking"]
OBS["ObservabilityService\nfallback_provider_index attribute"]
end
App --> WR
WR -->|"route by score"| FC
FC --> RT
RT -->|"try 1"| CB_A
RT -.->|"on failure"| CB_B
RT -.->|"on failure"| CB_C
CB_A --> PA
CB_B --> PB
CB_C --> PC
PA & PB & PC -->|"response"| CL
CL --> OBS
OBS --> App
style Routing fill:#2e1a0d,stroke:#fab387,color:#cdd6f4
style Reliability fill:#2e0d0d,stroke:#f38ba8,color:#cdd6f4
style Providers fill:#181825,stroke:#585b70,color:#cdd6f4
style Observability fill:#0d1a2e,stroke:#89dceb,color:#cdd6f4
What each layer provides:
| Component | Failure mode addressed |
|---|---|
CircuitBreaker per provider |
Stops hammering a degraded provider |
FallbackChainPort |
Automatic promotion to next provider on failure |
retry |
Transient errors (timeouts, 429s) resolved without caller awareness |
WorkloadRouter |
Cost-optimised routing — cheap tasks go to cheap models |
CostLedger |
Per-provider cost tracking to understand failover spend |
ObservabilityService |
_fallback_provider_index attribute shows which provider won |
from electripy.ai.fallback_chain import FallbackChainPort
from electripy.concurrency.circuit_breaker import CircuitBreaker
from electripy.ai.cost_ledger import CostLedger
# Wrap each provider with its own circuit breaker
cb_a = CircuitBreaker(failure_threshold=5, recovery_timeout=30.0)
cb_b = CircuitBreaker(failure_threshold=5, recovery_timeout=30.0)
class ProtectedProvider:
def __init__(self, provider, breaker):
self._p, self._cb = provider, breaker
def complete(self, request, *, timeout=None):
return self._cb.call(lambda: self._p.complete(request, timeout=timeout))
chain = FallbackChainPort(providers=[
ProtectedProvider(openai_adapter, cb_a),
ProtectedProvider(anthropic_adapter, cb_b),
ollama_adapter, # no breaker — free, always available
])
response = chain.complete(request)
# response.metadata["_fallback_provider_index"] → 0 (OpenAI), 1 (Anthropic), 2 (Ollama)