Skip to content

Commit 47a7e17

Browse files
authored
Merge pull request #37539 from BerriAI/litellm_batch_enqueued_token_limit
feat(proxy): enqueued-token rate limiting for batches with refund on completion and cancellation
2 parents 8cf0b50 + d5ac495 commit 47a7e17

15 files changed

Lines changed: 1862 additions & 6 deletions

litellm/constants.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1766,6 +1766,17 @@
17661766
# one is seconds old, so a few minutes separates them.
17671767
PTU_PRUNE_SKEW_GRACE_SECONDS: Final[int] = 300
17681768

1769+
# How long enqueued-token reservations for batches live without a refund. Providers
1770+
# complete or expire batches within their completion window (24h for OpenAI), so a
1771+
# reservation still unrefunded after 8 days belongs to a batch whose terminal state
1772+
# was never observed (e.g. proxy restart); expiry returns the tokens to the caller.
1773+
BATCH_ENQUEUED_TOKEN_TTL_SECONDS: Final[int] = 8 * 24 * 60 * 60
1774+
1775+
# Key/team metadata field that opts batches into enqueued-token limiting. Only proxy
1776+
# admins may write it: when present it replaces the standard RPM/TPM checks for
1777+
# batch submissions.
1778+
BATCH_ENQUEUED_TOKEN_LIMIT_METADATA_KEY: Final = "batch_enqueued_token_limit"
1779+
17691780
# Shared read-only empty mapping, for defaulting optional Mapping parameters without
17701781
# constructing a fresh mutable dict at each call site.
17711782
EMPTY_MAPPING: Final = MappingProxyType({})

litellm/proxy/auth/auth_utils.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,12 @@
1212
import litellm
1313
from litellm import Router, provider_list
1414
from litellm._logging import verbose_proxy_logger
15-
from litellm.constants import MINIMUM_CUSTOM_KEY_LENGTH, STANDARD_CUSTOMER_ID_HEADERS
15+
from litellm.constants import (
16+
BATCH_ENQUEUED_TOKEN_LIMIT_METADATA_KEY,
17+
EMPTY_MAPPING,
18+
MINIMUM_CUSTOM_KEY_LENGTH,
19+
STANDARD_CUSTOMER_ID_HEADERS,
20+
)
1621
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
1722
from litellm.litellm_core_utils.url_utils import (
1823
SSRFError,
@@ -1169,6 +1174,46 @@ def enforce_output_token_estimates_are_admin_only(
11691174
)
11701175

11711176

1177+
class BatchEnqueuedTokenLimitRequest(Protocol):
1178+
"""The shape of any management request that can carry a batch enqueued-token limit."""
1179+
1180+
@property
1181+
def metadata(self) -> Mapping[str, object] | None: ...
1182+
1183+
@property
1184+
def model_fields_set(self) -> Collection[str]: ...
1185+
1186+
1187+
def enforce_batch_enqueued_token_limit_is_admin_only(
1188+
data: BatchEnqueuedTokenLimitRequest,
1189+
existing_metadata: Mapping[str, object] | None,
1190+
user_api_key_dict: UserAPIKeyAuth,
1191+
entity: Literal["key", "team"],
1192+
) -> None:
1193+
"""Only a proxy admin may change a key or team's batch enqueued-token limit.
1194+
1195+
When set, ``batch_enqueued_token_limit`` replaces the standard RPM/TPM checks
1196+
for batch submissions, so a holder-writable copy would let a caller lift their
1197+
own batch quota. Gated on the resulting value rather than on presence, so a
1198+
form resending the stored value stays a no-op.
1199+
"""
1200+
if user_api_key_dict.user_role == LitellmUserRoles.PROXY_ADMIN.value:
1201+
return
1202+
stored: Final[Mapping[str, object]] = existing_metadata or EMPTY_MAPPING
1203+
requested: Final[Mapping[str, object]] = (
1204+
(data.metadata or EMPTY_MAPPING) if "metadata" in data.model_fields_set else stored
1205+
)
1206+
if requested.get(BATCH_ENQUEUED_TOKEN_LIMIT_METADATA_KEY) == stored.get(BATCH_ENQUEUED_TOKEN_LIMIT_METADATA_KEY):
1207+
return
1208+
raise HTTPException(
1209+
status_code=403,
1210+
detail={ # mutable-ok: HTTPException.detail has no immutable form
1211+
"error": f"Only proxy admins can set {BATCH_ENQUEUED_TOKEN_LIMIT_METADATA_KEY} on a {entity}. "
1212+
"It replaces the standard rate limit checks for batch submissions."
1213+
},
1214+
)
1215+
1216+
11721217
def get_model_rate_limit_from_metadata(
11731218
user_api_key_dict: UserAPIKeyAuth,
11741219
metadata_accessor_key: Literal["team_metadata", "organization_metadata", "project_metadata"],

0 commit comments

Comments
 (0)