orchestrata_ultimate.providers.rate_limiter
Reference › orchestrata_ultimate › providers › rate_limiter
rate_limiter tracks how much of a provider's request and token allowance is used (specification 011, user story US-004). RateLimiter is created per provider by provider_manager with a requests-per-minute limit (default 60) and a tokens-per-minute limit (default 100000), and read by the router. record_request adds a request and its token count to a 60-second window, which _maybe_reset_window clears when it has elapsed. headroom_pct gives the fraction of allowance left, is_available says whether more requests fit, has_low_headroom is true under 10% so traffic can be steered away early, seconds_until_reset gives the wait for the window, and get_unavailability_reason and get_status give readable detail. With a Redis client from set_redis, tracking is distributed.
At a glance
| Source | src/orchestrata_ultimate/providers/rate_limiter.py |
| Lines of code | 160 |
| Classes | 1 |
| Functions | 0 |
| Imports (internal) | orchestrata_ultimate.core.base.traces |
| Imported by | orchestrata_ultimate.providers.provider_manager, orchestrata_ultimate.providers.router |
| Tests that import it | tests/llm/test_client_extended.py, tests/providers/test_provider_defect_fixes.py, tests/providers/test_rate_limiter.py, tests/providers/test_request_router.py, tests/providers/test_router.py, tests/test_providers.py |
Class diagram
Dependency diagram
API
Classes
RateLimiter
Decorators: traces(spec='011', story='US-004', reqs=['REQ-011-041', 'REQ-011-042', 'REQ-011-043', 'REQ-011-044']) · Line 25
Per-provider rate limit tracker.
| Method | Signature | Summary |
|---|---|---|
__init__ |
__init__(provider_name: str, rpm_limit: int = 60, tpm_limit: int = 100000) |
|
headroom_pct property |
headroom_pct() -> float |
Percentage of rate limit remaining (0.0 to 1.0). |
is_available property |
is_available() -> bool |
Whether the provider has headroom for more requests. |
seconds_until_reset property |
seconds_until_reset() -> float |
Seconds remaining until the 60-second sliding window resets. |
get_unavailability_reason |
get_unavailability_reason() -> str |
Human-readable reason why the provider is currently unavailable. |
has_low_headroom property |
has_low_headroom() -> bool |
Whether headroom is below 10% (should route away). |
set_redis |
async set_redis(client: redis.Redis) -> None |
Set Redis client for distributed tracking. |
record_request |
async record_request(token_count: int = 0) -> None |
Record a request and its token usage. |
_maybe_reset_window_locked |
_maybe_reset_window_locked() -> None |
Reset counters if the 60-second window has elapsed. |
_maybe_reset_window |
_maybe_reset_window() -> None |
Reset counters if the 60-second window has elapsed (acquires lock). |
get_status |
get_status() -> dict |
Return current rate limit status. |
Source
"""Per-provider rate limit tracking.
SPEC-011 FR-010: Tracks RPM and TPM with sliding window.
Routes away from rate-limited providers.
"""
import logging
import threading
import time
import redis.asyncio as redis
from orchestrata_ultimate.core.base.traces import traces
logger = logging.getLogger(__name__)
_RL_PREFIX = "orchestrata:provider:rl"
@traces(
spec="011",
story="US-004",
reqs=["REQ-011-041", "REQ-011-042", "REQ-011-043", "REQ-011-044"],
)
class RateLimiter:
"""Per-provider rate limit tracker.
Tracks requests per minute (RPM) and tokens per minute (TPM)
using a sliding window approach. Supports Redis persistence
for distributed tracking.
"""
@traces(spec="011", story="US-011-001", reqs=["REQ-011-041"])
def __init__(
self,
provider_name: str,
rpm_limit: int = 60,
tpm_limit: int = 100_000,
):
self.provider_name = provider_name
self.rpm_limit = rpm_limit
self.tpm_limit = tpm_limit
self._window_start: float = time.monotonic()
self._request_count: int = 0
self._token_count: int = 0
self._redis: redis.Redis | None = None
# Guards the sliding-window counters so concurrent callers (free-threaded
# 3.14, no GIL) cannot lose increments or window resets to a data race.
self._lock = threading.Lock()
@property
@traces(spec="011", story="US-004", reqs=["REQ-011-041"])
def headroom_pct(self) -> float:
"""Percentage of rate limit remaining (0.0 to 1.0)."""
with self._lock:
self._maybe_reset_window_locked()
if self.rpm_limit <= 0:
return 1.0 # unlimited
rpm_headroom = 1.0 - (self._request_count / self.rpm_limit)
if self.tpm_limit <= 0:
return max(0.0, rpm_headroom)
tpm_headroom = 1.0 - (self._token_count / self.tpm_limit)
return max(0.0, min(rpm_headroom, tpm_headroom))
@property
@traces(spec="011", story="US-004", reqs=["REQ-011-041"])
def is_available(self) -> bool:
"""Whether the provider has headroom for more requests."""
return self.headroom_pct > 0.0
@property
@traces(spec="011", story="US-004", reqs=["REQ-011-041"])
def seconds_until_reset(self) -> float:
"""Seconds remaining until the 60-second sliding window resets."""
with self._lock:
self._maybe_reset_window_locked()
elapsed = time.monotonic() - self._window_start
return max(0.0, 60.0 - elapsed)
@traces(spec="011", story="US-004", reqs=["REQ-011-041"])
def get_unavailability_reason(self) -> str:
"""Human-readable reason why the provider is currently unavailable.
Returns:
One of ``RPM exhausted``, ``TPM exhausted``, or
``rate limit exhausted`` when no headroom remains. Caller should
guard with ``is_available``; this method assumes the limiter is
exhausted.
"""
with self._lock:
self._maybe_reset_window_locked()
rpm_exhausted = self.rpm_limit > 0 and self._request_count >= self.rpm_limit
tpm_exhausted = self.tpm_limit > 0 and self._token_count >= self.tpm_limit
if rpm_exhausted and tpm_exhausted:
return "RPM and TPM exhausted"
if rpm_exhausted:
return "RPM exhausted"
if tpm_exhausted:
return "TPM exhausted"
return "rate limit exhausted"
@property
@traces(spec="011", story="US-004", reqs=["REQ-011-043"])
def has_low_headroom(self) -> bool:
"""Whether headroom is below 10% (should route away)."""
return self.headroom_pct < 0.10
@traces(spec="011", story="US-004", reqs=["REQ-011-041"])
async def set_redis(self, client: redis.Redis) -> None:
"""Set Redis client for distributed tracking."""
self._redis = client
@traces(spec="011", story="US-011-001", reqs=["REQ-011-041"])
async def record_request(self, token_count: int = 0) -> None:
"""Record a request and its token usage."""
with self._lock:
self._maybe_reset_window_locked()
self._request_count += 1
self._token_count += token_count
if self._redis:
key = f"{_RL_PREFIX}:{self.provider_name}"
pipe = self._redis.pipeline()
pipe.hincrby(key, "rpm", 1)
pipe.hincrby(key, "tpm", token_count)
pipe.expire(key, 120)
await pipe.execute()
def _maybe_reset_window_locked(self) -> None:
"""Reset counters if the 60-second window has elapsed.
Caller MUST hold ``self._lock``.
"""
now = time.monotonic()
if now - self._window_start >= 60.0:
self._window_start = now
self._request_count = 0
self._token_count = 0
@traces(spec="011", story="US-011-001", reqs=["REQ-011-041"])
def _maybe_reset_window(self) -> None:
"""Reset counters if the 60-second window has elapsed (acquires lock)."""
with self._lock:
self._maybe_reset_window_locked()
@traces(spec="011", story="US-011-001", reqs=["REQ-011-041"])
def get_status(self) -> dict:
"""Return current rate limit status."""
with self._lock:
self._maybe_reset_window_locked()
request_count = self._request_count
token_count = self._token_count
return {
"provider": self.provider_name,
"rpm_used": request_count,
"rpm_limit": self.rpm_limit,
"tpm_used": token_count,
"tpm_limit": self.tpm_limit,
"headroom_pct": round(self.headroom_pct * 100, 1),
"is_available": self.is_available,
}