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.

Medium Python 3.9+ Aug 9, 2026 AI & LLM integration patterns 14 views 0 copies

Python code

47 lines
Python 3.9+
import 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

stdout
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

  1. Add `functools.wraps` and a decorator interface so you can annotate functions with `@breaker` directly
  2. 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

Run this sample

Open the browser IDE to tweak the example and see results without installing anything.

Open editor

More from AI & LLM integration patterns

Related tutorials and quizzes for this topic.