Skip to content

feat(ui): edit project input and output TPM limits from the Projects modal - #37676

Merged
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
devin/lit-4693-support-model_itpm_limit-model_otpm_limit-at-project-level
Aug 21, 2026
Merged

feat(ui): edit project input and output TPM limits from the Projects modal#37676
yassin-berriai merged 1 commit into
litellm_internal_stagingfrom
devin/lit-4693-support-model_itpm_limit-model_otpm_limit-at-project-level

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Project ITPM/OTPM quotas were API-only, unreachable from the UI
  • Admins had to hand-edit raw project metadata to set them
  • A limit removed in the modal stayed enforced by the proxy

How it solves it:

  • Model-Specific Limits rows now take Input TPM and Output TPM
  • Update sends every limit map, empty ones included
  • An emptied map is what /project/update reads as "clear this"

User Flow

Before: an admin capping input and output tokens separately for an application cannot do it from the Projects page, and cannot take a cap back off once it exists

  1. They open http://localhost:4000/ui/?page=projects and click Edit on a project
  2. Under Advanced Settings, Model-Specific Limits offers only a TPM Limit and an RPM Limit per model, so input and output share one bucket
  3. To get separate caps they instead add raw metadata pairs named model_itpm_limit and model_otpm_limit, which only accept a single string value, not a per-model map
  4. Reopening the modal shows those two keys sitting in the Metadata editor as stringified objects, and any save writes the stringified value back
  5. Deleting the row that holds a cap and saving reports success, yet requests for that model keep getting 429s against the cap the admin just removed

After: the same admin sets and later removes independent input and output caps per model straight from the modal

  1. They open http://localhost:4000/ui/?page=projects and click Edit on a project
  2. Under Advanced Settings, each Model-Specific Limits row now offers Model, TPM Limit, RPM Limit, Input TPM Limit and Output TPM Limit
  3. They enter a model name with, say, 10000 Input TPM and 2000 Output TPM, and save
  4. Reopening the modal shows the row prefilled with both values, and the Metadata editor no longer lists the limit keys
  5. Traffic for that project is then capped independently: exceeding the input budget returns 429 while output budget still has room, and vice versa
  6. They reopen the modal, clear the Input TPM Limit field or remove the whole row, and save
  7. Reopening shows the cap gone, and traffic that the cap used to reject now goes through

Relevant issues

Linear ticket

Resolves LIT-4693

Review notes

The Projects modal talks to /project/update, where an omitted key means "leave the stored value alone". The form used to omit any limit map that came out empty, so removing a quota in the UI sent nothing about it and the proxy kept enforcing the quota the admin believed was gone. Empty metadata behaved the same way.

The fix splits the payload builder in two. Create still leaves blanks out, since a new project has nothing to clear. Update sends every map the form knows about, empty included, and an empty map is what makes the stored quota disappear. The absent case is still reachable: the Edit modal submits nothing about guardrails, limits or metadata unless Advanced Settings was opened, so an untouched save is byte for byte what it was before.

Both halves are covered. On the frontend, the modal test removes the row and asserts the payload carries model_itpm_limit: {}; restoring the omit-when-empty behaviour fails it. On the backend, /project/update is exercised with an empty map and asserted to write metadata that no longer carries the quota, alongside the counterpart test pinning that an update which says nothing about limits does not touch metadata at all.

Pre-Submission checklist

  • I have added meaningful tests
  • The handful of test files covering my change pass locally, e.g. uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*, make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more
  • My PR passes all required CI/CD checks (e.g., lint, schema.d.ts sync check, etc.)
  • My PR's scope is as isolated as possible; it only solves 1 specific problem
  • I have received a Greptile Confidence Score of at least 4/5 before requesting a maintainer review (Greptile reviews automatically once the PR is opened; only comment @greptileai to 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

Shared setup, run against a live proxy on a namespaced port so it does not collide with anything already on 4000:

export PROXY=http://localhost:4076 AUTH="Authorization: Bearer sk-1234"
TEAM=$(curl -s -X POST $PROXY/team/new -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"team_alias":"lit-4693-demo","models":["gpt-4o-mini"]}' | jq -r .team_id)
PROJ=$(curl -s -X POST $PROXY/project/new -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{\"project_alias\":\"lit-4693-demo\",\"team_id\":\"$TEAM\",\"models\":[\"gpt-4o-mini\"],
       \"model_itpm_limit\":{\"gpt-4o-mini\":6000},\"model_otpm_limit\":{\"gpt-4o-mini\":4000},
       \"metadata\":{\"owner\":\"platform\"}}" | jq -r .project_id)

Every curl below was run live against that proxy and its output is pasted verbatim. The numbered UI steps are the screenshots still to attach: the dashboard is npm run dev in ui/litellm-dashboard pointed at the same proxy, and each save it performs sends exactly the payload shown next to it

Before (fdc0bfa)

Clearing a stored input TPM limit

  1. Open http://localhost:3000/ui/?page=projects, Edit the lit-4693-demo project, expand Advanced Settings, and remove the gpt-4o-mini row. Save, and the modal reports success
  2. Removing the row leaves the payload with no model_itpm_limit at all, so this is what the proxy is asked to act on:
curl -s -X POST $PROXY/project/update -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{\"project_id\":\"$PROJ\",\"project_alias\":\"lit-4693-demo\"}" -o /dev/null -w "http=%{http_code}\n"
curl -s -G $PROXY/project/info -H "$AUTH" --data-urlencode "project_id=$PROJ" | jq .metadata
http=200
{
  "owner": "platform",
  "model_itpm_limit": {
    "gpt-4o-mini": 6000
  },
  "model_otpm_limit": {
    "gpt-4o-mini": 4000
  }
}
  1. The quota the admin just deleted is still stored, so the proxy keeps enforcing it

After (a037dae)

Clearing a stored input TPM limit

  1. Open http://localhost:3000/ui/?page=projects, Edit the lit-4693-demo project, expand Advanced Settings, remove the gpt-4o-mini row, and save
  2. The same removal now puts emptied maps in the payload:
curl -s -X POST $PROXY/project/update -H "$AUTH" -H 'Content-Type: application/json' \
  -d "{\"project_id\":\"$PROJ\",\"project_alias\":\"lit-4693-demo\",\"model_rpm_limit\":{},\"model_tpm_limit\":{},
       \"model_itpm_limit\":{},\"model_otpm_limit\":{},\"metadata\":{\"owner\":\"platform\"}}" \
  -o /dev/null -w "http=%{http_code}\n"
curl -s -G $PROXY/project/info -H "$AUTH" --data-urlencode "project_id=$PROJ" | jq .metadata
http=200
{
  "owner": "platform",
  "model_rpm_limit": {},
  "model_tpm_limit": {},
  "model_itpm_limit": {},
  "model_otpm_limit": {}
}
  1. Reopen the modal: Model-Specific Limits is empty, the Metadata editor still shows owner, and the UI and /project/info now agree that the quota is gone

Setting input and output TPM limits from the modal

  1. Open http://localhost:3000/ui/?page=projects and Edit a project, then expand Advanced Settings. Each Model-Specific Limits row carries Input TPM Limit and Output TPM Limit, which the merge base (3a31331) does not have
  2. Fill a row with gpt-4o-mini, 6000 Input TPM, 4000 Output TPM, and save. The setup above stores that same pair, which is the state the next step reads back:
curl -s -G $PROXY/project/info -H "$AUTH" --data-urlencode "project_id=$PROJ" | jq .metadata
{
  "owner": "platform",
  "model_itpm_limit": {
    "gpt-4o-mini": 6000
  },
  "model_otpm_limit": {
    "gpt-4o-mini": 4000
  }
}
  1. Reopen the Edit modal: the row comes back prefilled with both values and the Metadata editor does not list the two limit keys

Type

🆕 New Feature

Caveats (if any)

Final Attestation

  • The tests check the right things, including the edge cases, and regressions in the respective real-world customer use-cases are not possible after this PR

Link to Devin session: https://app.devin.ai/sessions/ed1e7fcde5704c6485c5eed12930b901
Requested by: @yassin-berriai

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@greptile-apps

greptile-apps Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR exposes per-model input and output TPM limits in the Projects modal and ensures explicitly cleared limits are sent to /project/update.

  • Adds input/output TPM fields and API parameter types.
  • Separates create payload omission from update clearing semantics.
  • Adds frontend and backend regression coverage for setting, retaining, and clearing limits and metadata.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains; the previously reported persisted-limit issue is fixed by sending explicit empty maps on advanced updates, with frontend and endpoint regression coverage.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectModals/projectFormUtils.ts Separates create and update payload construction so updates transmit empty maps needed to clear persisted quotas.
ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectModals/EditProjectModal.tsx Hydrates input/output TPM limits from metadata while preserving untouched-save behavior until Advanced Settings is opened.
ui/litellm-dashboard/src/app/(dashboard)/projects/_components/ProjectModals/ProjectBaseForm.tsx Adds accessible per-model input and output TPM controls to the advanced project form.
tests/enterprise/litellm_enterprise/proxy/management_endpoints/test_project_endpoints_prisma.py Verifies explicit empty maps clear stored limits while omitted limit fields leave metadata untouched.

Reviews (3): Last reviewed commit: "feat(ui): support project input and outp..." | Re-trigger Greptile

@yassin-berriai
yassin-berriai force-pushed the devin/lit-4693-support-model_itpm_limit-model_otpm_limit-at-project-level branch from fdc0bfa to b40a22f Compare August 20, 2026 22:22
@yassin-berriai

Copy link
Copy Markdown
Contributor

@greptileai please re-review at b40a22f: the cleared-limit path now sends explicitly empty maps on update, with frontend and backend regression tests.

The Model-Specific Limits rows now carry Input TPM and Output TPM, and a
limit the operator removes is sent as an explicitly empty map so
/project/update actually drops it instead of leaving the stored quota
enforced behind a UI that shows it gone.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@yassin-berriai
yassin-berriai force-pushed the devin/lit-4693-support-model_itpm_limit-model_otpm_limit-at-project-level branch from b40a22f to a037dae Compare August 20, 2026 22:27
@yassin-berriai

Copy link
Copy Markdown
Contributor

@greptileai re-anchor at a037dae please: pure rebase onto staging to pick up ruff-tests.toml, no code change since your 5/5.

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@yassin-berriai
yassin-berriai enabled auto-merge (squash) August 21, 2026 00:26
@yassin-berriai
yassin-berriai merged commit c74e9e7 into litellm_internal_staging Aug 21, 2026
66 of 67 checks passed
@yassin-berriai
yassin-berriai deleted the devin/lit-4693-support-model_itpm_limit-model_otpm_limit-at-project-level branch August 21, 2026 00:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants