Skip to content

feat(ptu): add PTU inputs to the model form and flat cost to the Usage page - #35393

Merged
yucheng-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_ptu_usage_ui
Aug 10, 2026
Merged

feat(ptu): add PTU inputs to the model form and flat cost to the Usage page#35393
yucheng-berri merged 1 commit into
litellm_internal_stagingfrom
litellm_ptu_usage_ui

Conversation

@yucheng-berri

@yucheng-berri yucheng-berri commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

TLDR

Problem this solves:

  • Admins cannot configure PTU on a model from the UI
  • Teams cannot see flat cost next to per-request spend

How it solves it:

  • Model add and edit forms get PTU count, rate, and effective-window date pickers
  • Team Usage shows a Total Cost tile that expands to a Request Cost and Flat Cost split, plus a stacked Daily Spend chart

Relevant issues

Linear ticket

Resolves LIT-4077

Pre-Submission checklist

  • I have added meaningful tests
  • My PR passes all CI/CD checks (e.g., lint, format, unit tests)
  • 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

Screenshots / Proof of Fix

Captured live against the dashboard on this branch (commit a181f3d)

This PR was rebased onto litellm_internal_staging at f6587fa on 2026-08-06 and is now at c76d1a7. The rebase took no conflicts in ui/, and this commit's own frontend diff is byte-identical before and after it, so the screenshots below still show the code that ships. The 493 frontend tests pass on the rebased head

The model add form Advanced Settings now shows PTU Count, Calculated Cost per PTU / Hour (USD), and PTU Effective From / To date-time pickers:

Model form Advanced Settings: PTU Count "e.g. 15", Calculated Cost per PTU / Hour (USD) "e.g. 2.00", PTU Effective From (UTC) and PTU Effective To (UTC) rendered as antd date pickers

Verified end to end against a live proxy, with real provider traffic. Seven chat completions were sent through a team-scoped key to Gemini 2.5 Pro and 2.5 Flash, and landed in LiteLLM_SpendLogs at 0.0803658 of genuine provider spend; the nightly rollup wrote the matching flat-cost rows. The endpoint the tiles read then returned

GET /team/daily/activity?start_date=2026-08-01&end_date=2026-08-06&team_ids=04ff1369-...
  total_spend     : 840.0804032
  total_flat_cost : 2880.0
  -> Total Cost   : 3720.08

Of that request cost, 0.08 is the live Gemini traffic; the remainder is seeded rows on the demo database so the request segment is large enough to see next to a 480/day reservation. Every flat-cost figure is real rollup output

Frontend unit tests cover the chart panel and the export columns (113 tests across EntityUsage, UISettings, and EntityUsageExport pass)

Screens

Stored PTU config on the model detail view, and the same four fields in edit mode:

Model detail view showing stored PTU config

PTU fields in the edit form

In-form validation, so a value the backend would reject never leaves the browser:

Pair validation

Fractional count rejected

The window is entered and stored as a UTC wall clock; this browser is at UTC-7, so a zone conversion would show as a seven-hour shift and does not:

Effective window entered as UTC in a UTC-7 browser

Once a team has accrued flat cost the money tile reads Total Cost and carries a chevron; the summary row stays at five tiles so no card gets narrower:

Team usage summary row with the collapsed Total Cost tile

Selecting it reveals the split, coloured to match the chart series, and the two parts add back to the tile above them:

Total Cost expanded into Request Cost and Flat Cost

Each of the three money tiles explains itself on hover, including that flat cost is reported rather than charged against budgets:

Flat Cost tooltip

The daily chart stacks the two series, so the reserved floor stays flat at 480 a day while the request segment moves with traffic:

Daily spend chart with flat cost stacked on request cost

Hovering a bar splits that day and totals it:

Chart tooltip splitting request cost and flat cost

A team that has accrued no flat cost keeps the original tile, with no chevron and no breakdown, so teams that never use PTU see the page they see today:

Team with no flat cost renders as before

Non-team entity views are unchanged, five tiles and a single series:

Tag usage regression check

Rate validation

A review pass found the edit form validating only PTU count, so a negative rate typed past the number input's min reached the backend and failed the save with a 422 the operator had no way to anticipate. The non-negative rate check now lives in the shared ptuValidation module beside the count rule and is applied on both the add and the edit form, so the two paths cannot drift again

Behavior changes

For a team with non-zero flat cost the Total Spend tile is replaced by Total Cost, which reads request spend plus flat cost and expands on click; the chart legend and tooltip say Request cost where they previously said Request spend. Teams with no flat cost, and every non-team entity view, are byte-identical to the previous build. The CSV export is unchanged

Live run 2026-08-08, commit 2a915a2

Dashboard built from this branch and served by the proxy at /ui, real data from the runs on the PRs below it

Team Usage shows the flat cost alongside request cost. Total Cost $6,260.00, and the Daily Spend chart splits into a Flat cost and a Request cost series. The 2026-08-07 bar is taller because two deployments were live that day, $720 plus $480, which is the same 1200.0 the API returns

Usage page with PTU flat cost

The four PTU inputs on the model form, under Advanced Settings, between Tags and Use in pass through routes

PTU inputs on the model form

To reproduce: http://localhost:4000/ui/usage/, set Usage View to Team Usage, then Models + Endpoints, Add Model, expand Advanced Settings

Model Settings on an existing deployment. The read view renders the four stored values, with the two window bounds formatted as UTC rather than raw ISO

Model Settings read view

Edit Settings pre-populates all four from the stored config, so a save that touches something unrelated round-trips the PTU values unchanged instead of clearing them

Model Settings edit view

Clearing PTU Count while a rate is still set fails in the form, on both fields, with the wording the backend uses for the same rejection. The edit form previously validated only the count, so a bad pair reached the proxy and failed the save with a 400 the operator had no way to anticipate

Pair validation on the edit form

Type

New Feature

Changes

advanced_settings adds PTU Count, Calculated Cost per PTU / Hour, and two antd DatePicker fields for the effective window; the create submit maps the picker value to an ISO string in model_info. model_info_view mirrors the same fields in the edit view, prefilling the pickers from the stored ISO strings and writing them back on save. Both forms label the window UTC and route the picker value through ptuDatetime, which keeps the value in UTC mode end to end so a non-UTC browser does not shift the stored hour. Handing the picker a locally re-parsed wall clock would look equivalent but is not: a reading the local zone skips at a DST spring-forward gets advanced an hour by the engine, and since the save path re-stamps whatever the picker holds as UTC, that shift would be written back to the stored window, and both share ptuCountRules so a ptu_count that is fractional, zero or negative is rejected in the form instead of by the backend. PTU Count and Cost per PTU / Hour also validate as a pair, since the backend rejects a half-set pair with a 400; filling or clearing one without the other now surfaces inline, and removing PTU config from a deployment means clearing both. The usage page paginates daily activity at 1000 rows a page and sums each page's metadata, so total_flat_cost joins the summed keys; leaving it out froze the Flat Cost and Total Cost tiles at the first page's value for any team with more than a page of activity, while spend kept summing correctly and made the figure look right. The read view renders the stored window through a single formatter, so a timestamp looks the same whether it came back from the backend as +00:00 or is the .000Z a just-saved form is still holding

EntityUsage keeps the team Cost tab at five summary tiles and turns the money tile into an expandable Total Cost that reveals Request Cost and Flat Cost underneath, rather than adding two tiles to the row. Seven fixed columns left each card 145px wide against the 161px a four-digit dollar figure needs, so the previous layout clipped any team past about a thousand dollars, including the pre-existing Total Spend tile; five columns give 209px and hold values up to roughly twelve million, which is the headroom the row had before this PR. The breakdown, the chevron and the Total Cost label appear only once total_flat_cost is non-zero, so a team that never uses PTU renders exactly as it does today. The three money tiles carry tooltips, and the Daily Spend chart stacks Flat cost on Request cost with a split tooltip. EntityUsageExport adds the Flat Cost and Total Cost columns when a team accrued non-zero flat cost; the existing Spend header is left alone so anything parsing the CSV keeps working

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

Open in Devin Review

Note

Medium Risk
Touches billing-related model metadata and team cost reporting; risk is moderated because calculation stays on the backend and the UI gates export columns on non-zero flat cost.

Overview
Adds PTU (provisioned throughput) configuration to model create/edit flows and surfaces flat PTU cost on team usage and exports.

Model configuration: Advanced Settings and model edit now include PTU count, cost per PTU/hour, and optional UTC effective window (DatePicker). Create/update submit paths persist these on model_info (numbers and ISO date strings).

Team usage (Cost tab): When entityType === "team", the overview adds Flat Cost and Total Cost tiles, and the daily chart stacks Request spend and Flat cost with an updated tooltip. Other entity types keep the prior single-series behavior.

Exports: Daily CSV/JSON gains Flat Cost ($) and Total Cost ($) columns and summary fields only when metadata.total_flat_cost is non-zero; types add optional flat_cost / total_flat_cost.

Reviewed by Cursor Bugbot for commit 36f372d. Bugbot is set up for automated code reviews on this repo. Configure here.

@yucheng-berri yucheng-berri changed the title feat(ptu): PTU inputs on the model form and flat cost on the Usage page feat(ptu): add PTU inputs to the model form and flat cost to the Usage page Jul 31, 2026
@greptile-apps

greptile-apps Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds PTU configuration to model create and edit forms and surfaces flat PTU costs in team usage views and exports.

  • Adds shared validation and UTC date conversion for PTU count, hourly rate, and effective windows
  • Preserves explicit PTU field clearing through model updates
  • Aggregates flat costs across paginated activity results
  • Adds expandable cost summaries, stacked daily cost charts, and conditional export fields

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
ui/litellm-dashboard/src/components/model_info_view.tsx Adds PTU editing, validation, UTC round-tripping, explicit clearing, and read-only display while resolving the prior form findings.
ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx Adds validated PTU count, rate, and effective-window inputs to model creation.
ui/litellm-dashboard/src/utils/ptuValidation.ts Centralizes PTU bounds, pairing, required-start, and window-order validation consistently with backend constraints.
ui/litellm-dashboard/src/utils/ptuDatetime.ts Preserves UTC-labelled wall-clock values across create, display, edit, and save paths.
ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.ts Includes total flat cost among metadata values accumulated across activity pages.
ui/litellm-dashboard/src/app/(dashboard)/usage/_components/components/EntityUsage/EntityUsage.tsx Adds conditional team cost breakdown tiles and stacked request-versus-flat-cost visualization.
ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts Adds conditional flat-cost and total-cost fields to team usage exports.

Reviews (26): Last reviewed commit: "feat(ptu): PTU inputs on the model form ..." | Re-trigger Greptile

greptile-apps[bot]

This comment was marked as resolved.

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

View 1 additional finding in Devin Review.

Open in Devin Review

Comment thread ui/litellm-dashboard/src/components/model_info_view.tsx Outdated
Comment thread ui/litellm-dashboard/src/components/add_model/advanced_settings.tsx
Comment thread ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts
Comment on lines +107 to +108
// total_flat_cost defaults to 0 on every entity response, so only a non-zero value
// means a PTU-configured team actually accrued flat cost worth exporting.

@devin-ai-integration devin-ai-integration Bot Jul 31, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 New code adds explanatory comments the repository forbids

Explanatory comments were added throughout the new front-end code (for example the doc block at ui/litellm-dashboard/src/utils/ptuDatetime.ts:8-13), which the repository's coding guidelines prohibit for newly written code.
Impact: The change does not follow the repository's stated rule that no new comments be written unless explicitly requested.

Where the added comments live

CLAUDE.md (referenced as mandatory by AGENTS.md) states: "Do not write any comments (existing comments can stay) unless explicitly asked to in a user (not system) prompt".

Newly added comments appear in:

  • ui/litellm-dashboard/src/utils/ptuDatetime.ts:8-13, :19-26, :30-38, :49-54
  • ui/litellm-dashboard/src/utils/ptuValidation.ts:22, :40, :50-54
  • ui/litellm-dashboard/src/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity.ts:72-76
  • ui/litellm-dashboard/src/components/EntityUsageExport/utils.ts:107-108
  • ui/litellm-dashboard/src/components/add_model/handle_add_model_submit.tsx:167, :175
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 36f372d. Configure here.

className="mb-4"
>
<DatePicker showTime style={{ width: "100%" }} />
</Form.Item>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PTU dates ignore UTC labeling

High Severity

PTU Effective From/To are labeled UTC and the rollup bills in UTC hours, but the Ant Design DatePicker plus dayjs use the browser local timezone. Saving via toISOString() and prefilling via dayjs(...) therefore shift the wall-clock time users enter or see, so non-UTC admins configure the wrong active-hour window and flat cost accrues incorrectly.

Additional Locations (2)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 36f372d. Configure here.

...updatedModelInfo,
ptu_effective_to: values.ptu_effective_to?.toISOString?.() ?? values.ptu_effective_to,
};
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cleared PTU fields not saved

Medium Severity

On model edit save, PTU fields are only written when non-empty, while updatedModelInfo is seeded from the existing model_info. Clearing ptu_count, cost_per_ptu_per_hour, or the effective dates (including making ptu_effective_to blank for open-ended) leaves the previous values in place, so admins cannot disable PTU or reopen the window from the UI.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 36f372d. Configure here.

@yucheng-berri
yucheng-berri force-pushed the litellm_ptu_usage_ui branch from 36f372d to d2fa8a5 Compare July 31, 2026 20:27
@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai fixed: the date pickers now store the picked wall-clock time as UTC, and clearing a PTU field sends explicit null. Re-review please

devin-ai-integration[bot]

This comment was marked as resolved.

@yucheng-berri
yucheng-berri force-pushed the litellm_ptu_usage_ui branch 2 times, most recently from 6713636 to 91408c3 Compare July 31, 2026 20:46
devin-ai-integration[bot]

This comment was marked as resolved.

@yucheng-berri
yucheng-berri force-pushed the litellm_ptu_usage_ui branch 2 times, most recently from 695e882 to 410f1ba Compare July 31, 2026 21:11
@yucheng-berri
yucheng-berri force-pushed the litellm_ptu_usage_ui branch from 410f1ba to f0eeca7 Compare July 31, 2026 21:15
@yucheng-berri
yucheng-berri force-pushed the litellm_ptu_usage_ui branch from f0eeca7 to 1bf5061 Compare July 31, 2026 21:18
@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai rebased onto the updated base; the DatePicker UTC handling and Usage flat-cost changes are unchanged. Re-review please

greptile-apps[bot]

This comment was marked as resolved.

devin-ai-integration[bot]

This comment was marked as resolved.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

greptile-apps[bot]

This comment was marked as resolved.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

greptile-apps[bot]

This comment was marked as resolved.

@yucheng-berri

Copy link
Copy Markdown
Contributor Author

@greptileai review

Add PTU count, cost per PTU per hour, and effective-from/to date-time pickers to
the model add and edit forms; the create submit and the edit save map the picker
values to model_info as ISO strings

On the Team Usage Cost tab the money tile becomes Total Cost once a team has
accrued flat cost, and expands to a Request Cost and Flat Cost breakdown, so the
summary row stays at five tiles and the cards keep their width. Each of the three
carries a tooltip, including that flat cost is reported rather than charged
against budgets. The Daily Spend chart stacks Flat cost on Request cost, with a
tooltip that splits the two and shows the total

A team that has accrued no flat cost renders exactly as it did before, and other
entity views are unchanged. CSV export gains Flat Cost and Total Cost columns
when a team accrued non-zero flat cost; the existing Spend header is left alone
so downstream parsers keep working

Both forms validate the PTU pair through one shared module. The count rule rejects
a fractional, zero or negative value, and a rate rule rejects a negative one, each
mirroring a contract the backend enforces. Keeping the rate rule shared rather than
on a single form is deliberate: the edit form previously validated only the count,
so a negative rate typed past the input's min reached the backend and failed the
save with a 400 the operator had no way to anticipate

Both forms require PTU Effective From once PTU Count is set, matching the backend, which
rejects PTU config without a start because flat cost accrues from that instant and an
inferred one would bill days a deployment did not exist. The rule lives beside the count
and rate rules in the shared module, so the add and edit paths cannot drift.
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.

3 participants