How the Gemini LLM Provider Handles Rate Limits and Retries

The GeminiProvider class in models.py implements a fixed retry loop with exponential back-off, random jitter, and server-suggested retry hints to gracefully handle Google Gemini API quota errors.

The Gemini LLM provider in the interviewstreet/hiring-agent repository manages rate limits and retries through a robust error-handling strategy located in models.py. This implementation ensures that hiring agent workflows remain resilient during high-throughput scenarios or strict API quotas. The provider automatically intercepts quota violations, calculates intelligent delays, and re-attempts requests without manual intervention.

Core Architecture and Configuration

Model Initialization and Client Setup

The provider configures the Google Generative AI client using genai.configure with the provided API key, then instantiates a GenerativeModel for the specific model name (see lines 13-50 in models.py).

self.client = genai
gemini_model = self.client.GenerativeModel(
    model_name=model, generation_config=generation_config
)

Message Translation

Before sending requests, the provider converts incoming chat messages from the generic Ollama-style format to the Gemini-specific format (lines 52-57). This translation ensures compatibility while preserving the retry logic wrapper.

Rate Limit Detection and Back-Off Strategy

The Fixed Retry Loop

The chat() method encloses API calls within a fixed-size retry loop using MAX_RETRIES = 5 (lines 58-66). If the call succeeds, the Gemini response transforms back into an Ollama-compatible dictionary and returns immediately. This loop acts as the primary resilience mechanism against transient quota failures.

Catching ResourceExhausted Exceptions

The provider specifically catches google.api_core.exceptions.ResourceExhausted (lines 33-36), which the Gemini API raises when quota limits are exceeded or throttling occurs. This targeted exception handling distinguishes rate limits from other API errors that should fail immediately without retry.

Exponential Back-Off with Jitter

When a rate limit is detected, the provider calculates wait times using exponential back-off (lines 36-42):

  • Base delay: 10 seconds
  • Formula: BASE_DELAY * 2**attempt (capped at 120 seconds maximum)
  • Jitter: A random multiplier between 0.8 and 1.2 applied via random.uniform(0.8, 1.2) to prevent thundering herd effects

This calculation ensures progressive delays while the randomization distributes retry attempts across time to avoid synchronized spikes.

Honoring API-Provided Retry Hints

Gemini occasionally embeds retry-after suggestions directly in exception messages (e.g., "retry in 30s"). The provider extracts these values using regex parsing (lines 73-81) and uses them only if they are shorter than the computed exponential delay. This respects the API's explicit guidance while maintaining the back-off safety floor.

Logging and Final Failure Handling

Before sleeping, the provider outputs a concise log line indicating the attempt number and chosen wait duration (lines 86-92). If all five retries are exhausted, the original ResourceExhausted exception is re-raised (lines 66-70), allowing upstream error handlers to manage unrecoverable quota violations or trigger circuit breakers.

Practical Usage Examples

Direct Provider Initialization

You can instantiate the provider directly to leverage the built-in retry logic:

import os
from models import GeminiProvider

gemini = GeminiProvider(api_key=os.getenv("GEMINI_API_KEY"))

messages = [
    {"role": "user", "content": "Explain the difference between recursion and iteration."}
]

response = gemini.chat(
    model="gemini-1.5-flash",
    messages=messages,
    options={"temperature": 0.7}
)

print(response["message"]["content"])

Via the Utility Factory

The repository's llm_utils.py automatically selects the Gemini provider based on model name detection, as configured in prompt.py:

from llm_utils import get_llm_provider

provider = get_llm_provider(model_name="gemini-1.5-flash")
answer = provider.chat(
    model="gemini-1.5-flash",
    messages=[{"role": "user", "content": "What is a hash table?"}]
)
print(answer["message"]["content"])

Both approaches benefit from the automatic rate limit handling implemented in models.py.

Summary

  • Fixed retry count: The GeminiProvider attempts each call up to 5 times before failing.
  • Smart exception handling: Only ResourceExhausted errors trigger the back-off logic.
  • Exponential delays: Wait times start at 10 seconds and double each attempt, maxing at 120 seconds.
  • Jitter protection: Random 20% variance prevents synchronized retry storms.
  • Hint integration: Server-suggested retry delays are parsed and honored when shorter than calculated back-off.
  • Clean failure: After exhausting retries, the original exception propagates for upstream handling.

Frequently Asked Questions

How many retry attempts does the Gemini provider make before giving up?

The provider makes 5 attempts total, as defined by the MAX_RETRIES constant in models.py. After exhausting these attempts, it re-raises the ResourceExhausted exception for the calling code to handle the unrecoverable quota issue.

What is the maximum wait time between retries?

The calculated delay caps at 120 seconds (2 minutes), regardless of the attempt number. However, the actual sleep duration includes a random jitter of ±20%, so observed waits may vary slightly above or below this maximum.

Does the provider respect Google's suggested retry-after headers?

Yes. The code parses retry-after suggestions embedded in exception messages using regex (lines 73-81). If the API suggests a specific wait time that is shorter than the exponential back-off calculation, the provider uses the shorter value. Otherwise, it sticks to the calculated delay.

Can I configure the base delay or retry count?

Currently, the BASE_DELAY (10 seconds) and MAX_RETRIES (5) are hardcoded constants in models.py. To modify these values, you would need to edit the source file or subclass GeminiProvider to override the retry logic before initialization.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →