|
12 | 12 | import litellm |
13 | 13 | from litellm import Router, provider_list |
14 | 14 | 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 | +) |
16 | 21 | from litellm.litellm_core_utils.safe_json_loads import safe_json_loads |
17 | 22 | from litellm.litellm_core_utils.url_utils import ( |
18 | 23 | SSRFError, |
@@ -1169,6 +1174,46 @@ def enforce_output_token_estimates_are_admin_only( |
1169 | 1174 | ) |
1170 | 1175 |
|
1171 | 1176 |
|
| 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 | + |
1172 | 1217 | def get_model_rate_limit_from_metadata( |
1173 | 1218 | user_api_key_dict: UserAPIKeyAuth, |
1174 | 1219 | metadata_accessor_key: Literal["team_metadata", "organization_metadata", "project_metadata"], |
|
0 commit comments