feat(proxy): add project-level ITPM and OTPM quotas - #35110
Conversation
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@greptileai review |
Greptile SummaryAdds project-level per-model input and output token quotas
Confidence Score: 5/5The PR appears safe to merge No blocking failure remains
|
| Filename | Overview |
|---|---|
| litellm/proxy/hooks/parallel_request_limiter_v3.py | Implements separate project ITPM and OTPM reservations, window-aware reconciliation, request estimation, and quota response headers |
| litellm/proxy/hooks/batch_rate_limiter.py | Applies per-row model-specific input and output quota estimates to batch files |
| litellm/responses/streaming_iterator.py | Propagates project quota enforcement through Responses API WebSocket frames |
| litellm/llms/custom_httpx/llm_http_handler.py | Discovers registered project quota callbacks and supplies them to native and managed WebSocket handlers |
| litellm/proxy/_types.py | Adds project ITPM and OTPM request fields and management metadata persistence |
| litellm/proxy/auth/auth_utils.py | Extends project metadata rate-limit lookup typing to the new quota maps |
| tests/test_litellm/proxy/hooks/test_tpm_concurrent.py | Covers split quota reservation, streaming reconciliation, usage shapes, cache reads, and window transitions |
Reviews (23): Last reviewed commit: "Merge remote-tracking branch 'origin/lit..." | Re-trigger Greptile
PR overviewAll previously flagged issues have been addressed. No open security concerns remain on this pull request. Security reviewNo open security issues remain on this pull request. Fixed/addressed: 20 · PR risk: 0/10 |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
|
@greptileai review |
Embeddings rows were identified by body shape (has `input`, no `messages`/`prompt`), which also matches a `/v1/responses` batch row and reserved zero output tokens for it -- letting a project caller run large Responses generations against a quota-limited model without consuming OTPM. Classify embeddings by the row's own `url` instead, and read `max_output_tokens` as a Responses output cap alongside `max_tokens`/`max_completion_tokens`. Co-authored-by: Cursor <cursoragent@cursor.com>
…uota-project-itr1
Image, file, video, and previous_response_id requests reserved the whole project ITPM limit up front, so any window with existing usage rejected them and one in-flight multimodal request blocked the entire project. Reserve the token_counter estimate instead, like every other request; post-call reconciliation already charges actual usage.
|
bugbot run |
- scale batch output-token reservations by the row's n / best_of candidate count - parse client-supplied output caps defensively instead of 500ing on unparseable values - exclude project IO descriptors from the first should_rate_limit pass when TPM reservation is disabled so their buckets are not double-charged
|
bugbot run |
|
bugbot run |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 3c34c34. Configure here.
…itellm_pr35110_itpm_otpm # Conflicts: # type-discipline-budget.json
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit c435c25. Configure here.
mateo-berri
left a comment
There was a problem hiding this comment.
LGTM. Thanks for the contribution!
55777d0
into
BerriAI:litellm_internal_staging
TLDR
Problem this solves:
How it solves it:
model_itpm_limitandmodel_otpm_limitmaps to projectsUser Flow
Before: a proxy admin cannot give a project separate input and output token budgets, so a single combined TPM cap is the only lever
{"project_alias": "search-ranking", "models": ["MODEL"], "model_itpm_limit": {"MODEL": 300}, "model_otpm_limit": {"MODEL": 60}}and the response drops both fields: the created project's metadata carries neither limit"max_tokens": 200requests all minute long and every one returns 200: nothing enforces an output budgetAfter: the same project carries per-model ITPM and OTPM buckets and requests over either one get a 429 naming that bucket
model_itpm_limitandmodel_otpm_limitin the project's metadataRate limit exceeded for model_per_project_itpm"max_tokens": 200against the 60-token output bucket returns 429 withRate limit exceeded for model_per_project_otpmx-ratelimit-model_per_project_itpm-remaining-tokensandx-ratelimit-model_per_project_otpm-remaining-tokensheaders showing each bucket drain independentlyRelevant issues
Builds on #31952
Linear ticket
Resolves LIT-5646
Pre-Submission checklist
@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Live proxy QA against real Anthropic
claude-fable-5calls. Shared setup, run once per hash with a fresh Postgres DB:Each case below uses its own project (fresh per-minute windows) with the tight bucket set to 300 input or 60 output tokens and the other bucket at 100000
Before (6d32d40)
Project creation echoes the limits
POST /project/newwith both limit maps returns 200 but drops them, the created project's metadata is empty"metadata": {}/v1/chat/completions input budget (itpm 300)
"Summarize the history of aviation. "x 80,max_tokens30) sails through the 300-token input bucket/v1/chat/completions output budget (otpm 60)
"max_tokens": 200against the 60-token output bucket is not blocked/v1/messages input budget (itpm 300)
/v1/responses output budget (otpm 60)
"max_output_tokens": 200on the Responses surface also passes, the response body bills 200 output tokensAfter (c435c25)
Project creation echoes the limits
POST /project/newnow persists and echoes both maps/v1/chat/completions input budget (itpm 300)
/v1/chat/completions output budget (otpm 60)
"max_tokens": 200is rejected, naming the output bucket"max_tokens": 30passes with the output bucket showing the reservation/v1/messages input budget (itpm 300)
/v1/responses output budget (otpm 60)
"max_output_tokens": 200is rejected before dispatch"max_output_tokens": 30passes with per-project headersType
🆕 New Feature
Caveats (if any)
Limits live in project metadata, no DB migration
Estimates reserve up front and real usage reconciles after the call, except realtime WebSocket response.create frames and batch file rows, which charge their estimates without post-call reconciliation (documented in the frame hook's docstring)
Docs PRs: docs: add LITELLM_DEFAULT_AUDIO_TOKEN_ESTIMATE to env vars reference litellm-docs#638 (env-var row) and docs(proxy): document project-level ITPM and OTPM limits litellm-docs#934 (project-level section on the ITPM/OTPM page)
Review-round hardening in 72960d1: batch rows scale their output reservation by n / best_of, unparseable output caps fall back to the no-cap floor instead of 500ing inside the limiter (a string cap still fails later in the provider transformation, same as base), and disabled-reservation mode no longer double-charges the ITPM/OTPM buckets. 69ea1c6 makes batch rows coerce n / best_of the same way live traffic does (float or numeric-string counts now scale the reservation) by reusing the limiter's candidate-count helper, and 3c34c34 guards that coercion against float overflow so a crafted row like n: 1e309 can no longer crash the limiter (live requests get a clean 400, and batch rows fall back to a conservative estimate instead of skipping the quota charge through the batch hook's fail-open error handler)
3c34c34 passes /live-pr-risk
c435c25 passes /live-pr-risk (merge of litellm_internal_staging; base touches neither rate-limiter hook, QA and overflow probe re-run green at this tip)
Note
Cursor Bugbot is generating a summary for commit 72960d1. Configure here.