Drop-in rate-limit layer for the Gemini API free tier.
Backoff 4/8/16s + jitter · TTL response cache · RPM pacing · tolerant JSON recovery.
from gquota import gemini r = gemini.generate("gemini-2.5-flash", "P(typhoon lands this week)? JSON only") r.text # plain text r.data # {'p': 0.42, 'confidence': 0.8, ...} — recovered tolerantly r.cached # True → zero quota spent
Delphi, a prediction-market agent, runs three concurrent scans on one free-tier key (~10 RPM). The afternoon all three fired in the same minute:
15:59:47 open markets: 15 15:59:47 MARKET 0x2c178e ... implied=? model=FAIL 15:59:47 MARKET 0xb8fcc2 ... implied=? model=FAIL 15:59:47 MARKET 0x76225f ... implied=? model=FAIL ... 15/15 FAIL, all in the same second # four minutes later — same logic, hardened call layer: 16:03:34 open markets: 15 16:03:37 0x2c178e ... model=0.480@gemini-flash-latest →pass 16:03:51 0x76225f ... model=0.459@gemini-3-flash-preview →pass 16:04:01 0xb4ded8 ... model=0.170@gemini-3-flash-preview →pass
No market-logic changes — only the call layer. gquota is that layer, extracted, generalized, and tested with 34 mocked-HTTP tests.
4s → 8s → 16s with 0–1.5s uniform jitter on 429/503. Jitter is what breaks the lockstep: concurrent processes stop colliding on retry.
Identical (model, prompt) pairs never re-burn quota. 2h default — tuned when the 4h cron kept re-pricing unchanged questions.
6.5s minimum between live calls (~9 RPM). Staying under the cap proactively beats reacting to 429s after the fact.
Gemini sometimes emits unescaped quotes mid-string. Strict parse → strip fences → extract {…} → per-field regex salvage. Quota already spent; don't waste the answer.
| call | what it does |
|---|---|
| gemini.generate(model, prompt) | Everything handled: cache → pacing → backoff → fallback → recovery |
| gemini.configure(rpm=…, …) | One-time setup: key, RPM, TTL, backoff schedule, fallback ladder |
| Provider(post=…) | Endpoint-agnostic core; injectable transport (how the tests mock HTTP) |
| provider.stats | Live counters: calls, retries, cache_hits, pace_sleeps |
| RateLimitError / RetryExhausted | Explicit failure modes, never silent None |
pip install git+https://github.com/jtabsbm/gquota.git