Circuit Breaker Pattern in Python for LLM API Calls
Implements a circuit breaker class that wraps LLM client calls to fail fast when the service is degrading, then recover automatically after a timeout.
Python code
47 linesimport time
class CircuitBreaker:
def __init__(self, failure_threshold=3, recovery_timeout=5):
self.failure_threshold = failure_threshold
self.recovery_timeout = recovery_timeout
self.failure_count = 0
self.state = "closed"
self.last_failure_time = None
def call(self, func):
if self.state == "open":
if time.time() - self.last_failure_time >= self.recovery_timeout:
self.state = "half-open"
else:
raise RuntimeError("Circuit breaker is OPEN")
try:
result = func()
if self.state == "half-open":
self.state = "closed"
self.failure_count = 0
return result
except Exception:
self.failure_count += 1
if self.failure_count >= self.failure_threshold or self.state == "half-open":
self.state = "open"
self.last_failure_time = time.time()
raise
def mock_llm_call():
if getattr(mock_llm_call, "attempt", 0) < 3:
mock_llm_call.attempt = mock_llm_call.attempt + 1
raise ConnectionError("LLM temporarily unavailable")
return {"response": "Hello from LLM"}
mock_llm_call.attempt = 0
if __name__ == "__main__":
breaker = CircuitBreaker(failure_threshold=2, recovery_timeout=2)
for i in range(6):
try:
result = breaker.call(mock_llm_call)
print(f"Call {i+1}: SUCCESS -> {result}")
except Exception as e:
print(f"Call {i+1}: FAILED -> {type(e).__name__}: {e}")
time.sleep(0.5)
Output
Call 1: FAILED -> ConnectionError: LLM temporarily unavailable
Call 2: FAILED -> ConnectionError: LLM temporarily unavailable
Call 3: FAILED -> RuntimeError: Circuit breaker is OPEN
Call 4: FAILED -> RuntimeError: Circuit breaker is OPEN
Call 5: SUCCESS -> {'response': 'Hello from LLM'}
Call 6: SUCCESS -> {'response': 'Hello from LLM'}
How it works
The circuit breaker tracks consecutive failures in failure_count and flips to open once the threshold is hit or a half-open attempt fails. In the open state, every call raises RuntimeError immediately — a fast-fail that keeps your app from hammering a dead service. After recovery_timeout seconds, a probe call is allowed through in the half-open state; if it succeeds the breaker resets to closed, otherwise it snaps back to open and restarts the timer. Wrapping any LLM client call in breaker.call(...) gives you fail-fast resilience without external dependencies.
Common mistakes
- Forgetting to reset `failure_count` when transitioning from half-open back to closed
- Using the same breaker instance across unrelated services, causing one outage to block all traffic
- Not wrapping the probe call in a timeout, so a hung LLM blocks the half-open check forever
Variations
- Add `functools.wraps` and a decorator interface so you can annotate functions with `@breaker` directly
- Track a rolling window of success rates instead of a simple counter for smoother transitions
Real-world use cases
- Wrapping an OpenAI or Anthropic client so a provider outage fails fast instead of stalling every request.
- Protecting a chat endpoint from backend LLM flakiness during peak traffic with automatic recovery.
- Isolating a multi-LLM router so one degraded vendor doesn't cascade into app-wide latency.
Sponsored
More from AI & LLM integration patterns
- Cache LLM Completions by Hashing the Prompt in Python easy
- Chain of Thought Prompting in Python: Step-by-Step Reasoning Demo easy
- Cosine Similarity to Retrieve Top K Chunks in Python easy
- Demonstrate Prompt Injection Bypass in Python easy
- How to Accumulate Streamed Tokens into a Final String in Python easy
- How to Append Few-Shot Examples to a Prompt in Python easy
Keep learning
Related tutorials and quizzes for this topic.