feat(ptu): surface ptu_flat_cost via /team/daily/activity response - #33266
feat(ptu): surface ptu_flat_cost via /team/daily/activity response#33266yucheng-berri wants to merge 1 commit into
Conversation
Greptile SummaryThis PR threads prorated PTU reservation flat cost (
Confidence Score: 4/5Safe to merge; all changes are additive and the sentinel-filtering invariant is consistently enforced across both read paths The sentinel-exclusion logic is correctly applied in both the paginated row-by-row path and the SQL GROUPING SETS aggregated path. Two observations are worth noting before production: total_flat_cost in the paginated metadata reflects only the current page rows (same constraint as total_spend), and the metadata_metrics_func override path would silently zero out flat_cost if ever wired to a team endpoint. litellm/proxy/management_endpoints/common_daily_activity.py — specifically the metadata_metrics_func branch and the paginated totals accumulation
|
| Filename | Overview |
|---|---|
| litellm/constants.py | Adds PTU_SENTINEL_API_KEY and PTU_ROLLUP_JOB_ID constants at end of file — correctly follows the sentinel-constant-in-constants.py rule |
| litellm/types/proxy/management_endpoints/common_daily_activity.py | Adds flat_cost to SpendMetrics and total_flat_cost to DailySpendMetadata; both default to 0.0, fully additive and backward-compatible |
| litellm/proxy/spend_tracking/ptu_reservation_rollup.py | Removes inline constant definitions and imports them from litellm.constants instead; no behavioral change |
| litellm/proxy/management_endpoints/common_daily_activity.py | Core logic for surfacing PTU flat_cost; sentinel filtering is consistently applied across both paginated and aggregated paths; minor concern around metadata_metrics_func bypassing flat_cost for the team path |
| tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py | Adds 15 new focused unit tests covering flat_cost accumulation, sentinel exclusion from api_key breakdowns, SQL query shape, and end-to-end get_daily_activity; all mocked, no network calls |
| ui/litellm-dashboard/src/lib/http/schema.d.ts | Regenerated type definitions adding flat_cost to SpendMetrics and total_flat_cost to DailySpendMetadata, both with default 0 |
Comments Outside Diff (1)
-
litellm/proxy/management_endpoints/common_daily_activity.py, line 882-884 (link)total_flat_costsilently zeroed whenmetadata_metrics_funcis suppliedWhen a caller provides
metadata_metrics_func(currently used by the tag endpoint withcompute_tag_metadata_totals),metadata_metricsis replaced by the function's return value. That function buildsSpendMetricsfrom deduped records viaupdate_metrics, but records from non-team daily tables never carryptu_flat_cost, soflat_costwill always be0.0. This is harmless today because no team endpoint setsmetadata_metrics_func, but if one ever does,total_flat_costwould silently vanish from the response while per-dayflat_costwould still be populated correctly — making the metadata inconsistent. A follow-up note or assertion documenting thatmetadata_metrics_funcis incompatible with PTU flat-cost surfacing would prevent a future silent regression.
Reviews (1): Last reviewed commit: "feat(ptu): surface ptu_flat_cost via /te..." | Re-trigger Greptile
| api_keys: Set[str] = { | ||
| record.api_key for record in records if record.api_key and record.api_key != PTU_SENTINEL_API_KEY | ||
| } |
There was a problem hiding this comment.
Paginated
total_flat_cost is page-scoped, not global
_aggregate_spend_records processes only the records fetched on the current page. If sentinel PTU rows happen to land on page 2 and the caller is reading page 1, total_flat_cost in the metadata will be 0.0 even though flat cost exists for those days. This is the same pre-existing limitation as total_spend in the paginated path, but it's newly surfaced via total_flat_cost. Worth documenting — especially for FinOps consumers of this endpoint who may aggregate total_flat_cost across pages and get an undercount. The aggregated variant (?aggregated=true) correctly returns a single grand-total row from SQL and is unaffected.
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
|
@greptileai thanks for the review. Both P2s are non-blocking and I'm shipping as-is; wanted to acknowledge them explicitly rather than leave them ambiguous. P2 (inline, page-scoped P2 (outside diff, Both are documentation-level, not correctness bugs. Merging when the stack lands. |
|
Both explanations are technically sound — acknowledged and accepted. On the page-scoped On the Nothing blocking here. Ship it. |
1c317df to
56ad42e
Compare
4454840 to
66aca9a
Compare
56ad42e to
2f89070
Compare
66aca9a to
69dd1e1
Compare
2f89070 to
6962709
Compare
69dd1e1 to
e724ae5
Compare
Extends the shared daily-activity read path so team daily activity carries prorated PTU flat cost alongside per-request spend. All changes are additive to the response shape. - SpendMetrics gains flat_cost (float, default 0.0) - DailySpendMetadata gains total_flat_cost (float, default 0.0) - update_metrics + _record_to_spend_metrics read ptu_flat_cost from rollup rows via getattr, so non-team daily tables (user, org, tag, agent, end_user) pass through with flat_cost defaulting to 0 - Grouping-sets SQL query selects SUM(ptu_flat_cost) only for litellm_dailyteamspend; every other daily table emits a 0::float shim so the response shape stays uniform - Sentinel PTU rows (api_key = PTU_SENTINEL_API_KEY) contribute to per-day, per-model, per-provider, per-endpoint, and per-entity totals, but never appear in api_keys / api_key_breakdown maps at any level - get_api_key_metadata skips the sentinel in its Prisma lookup PTU_SENTINEL_API_KEY and PTU_ROLLUP_JOB_ID moved to litellm/constants.py. The rollup module re-exports both so external callers continue working. Addresses Greptile P2 on #33137. Regenerated ui/litellm-dashboard/src/lib/http/schema.d.ts. 15 new unit tests, 8 kill mutations that either drop the ptu_flat_cost accumulation or turn off the sentinel filter.
6962709 to
1a98990
Compare
e724ae5 to
48d46cc
Compare
|
Closing as superseded. This is part of the v1 PTU design, which stored PTU config in a separate reservation table. The shipped design puts that config on the model deployment instead, merged as #35341, #35343, #35391, #35393 and #36829. The read path landed as #35391; staging already returns flat_cost and total_flat_cost from common_daily_activity.py. The branch is kept, so nothing here is lost. |
Relevant issues
Linear ticket
Resolves LIT-1697 (stage 3 of 5)
Stacks on #33137. Merge that first.
Pre-Submission checklist
Screenshots / Proof of Fix
Prereq: stages 1 and 2 already applied and a reservation seeded. Enable flag, run a real Azure gpt-4 request as a real team key so we have per-request spend on the same team+model+day, and run the backfill for that day.
Aggregated variant (
?aggregated=true) shows the same fields inmetadatabecause both paths run through the same shared response construction; also verified.Type
New Feature
Changes
Extends the shared daily-activity read path so team daily activity surfaces prorated PTU flat cost alongside per-request spend. All changes are additive to the response shape; consumers that don't know about
flat_cost/total_flat_costsee them as zero-valued defaults and behave exactly as before.Response shape.
SpendMetricsgains aflat_cost: float = 0.0peer ofspend.DailySpendMetadatagainstotal_flat_cost: float = 0.0peer oftotal_spend. Peer of, not folded into: an auditor asking "why does this team show $206 total for a day with two requests" needs the components independently. The UI in stage 4 will compute the display total by summing the two.Row-level aggregation.
update_metricsand_record_to_spend_metricsreadptu_flat_costviagetattr(record, "ptu_flat_cost", None) or 0.0, so rows fromLiteLLM_DailyUserSpend/LiteLLM_DailyOrganizationSpend/LiteLLM_DailyTagSpend/LiteLLM_DailyAgentSpend/LiteLLM_DailyEndUserSpend(which don't have that column) pass through withflat_cost=0.0. No table-shape check needed at the row level.Grouping-sets SQL query.
_build_aggregated_sql_querynow conditionally emitsSUM(ptu_flat_cost)::float AS ptu_flat_costwhen the table islitellm_dailyteamspend, and0::float AS ptu_flat_costotherwise. Every daily-* table's query returns the same column shape so_record_to_spend_metricsdoesn't need to branch on entity.Sentinel filtering. The rollup in stage 2 writes rows with
api_key = "__ptu_reservation__". Those rows must contribute theirptu_flat_costto every parent bucket (per-day totals, per-model, per-provider, per-endpoint, per-entity) but never appear in anyapi_keys/api_key_breakdownmap — the sentinel string is not a real key alias.Two aggregator changes enforce that invariant:
update_breakdown_metrics(paginated path): setsis_ptu_sentinel = record.api_key == PTU_SENTINEL_API_KEYat the top and guards everyapi_key/api_key_breakdownwrite on it. Parent buckets (models, providers, mcp_servers, endpoints, entities) still receive the row viaupdate_metrics, soflat_costaccumulates there._aggregate_grouping_sets_records_sync(aggregated path): computesreal_api_key = record.api_key and record.api_key != PTU_SENTINEL_API_KEYper row and gates each*_API_KEYgrouping-level dispatch on it. The_GROUP_DATE,_GROUP_DATE_MODEL,_GROUP_DATE_PROVIDER, etc. levels are unaffected — sentinel rows contribute normally.get_api_key_metadataalso skips the sentinel when building the set of tokens to look up, so we don't issue a wasted Prisma query for"__ptu_reservation__".Constant relocation.
PTU_SENTINEL_API_KEYandPTU_ROLLUP_JOB_IDmoved fromlitellm/proxy/spend_tracking/ptu_reservation_rollup.pytolitellm/constants.py. The rollup module re-exports both from the top so external callers continue to work. This addresses the Greptile P2 finding on #33137.Deviation from the admin-entity pattern
None new in this stage. Stage 1's rationale for skipping
/updatestill applies; stage 3 doesn't add or remove endpoints.Behavior changes
SpendMetricsandDailySpendMetadatagrow two fields. Additive. Any consumer that deserializes the response body ignores unknown fields, and any consumer that reads specific fields keeps working.GET /team/daily/activityresponses now carryflat_costandtotal_flat_cost. Zero-valued if the feature flag is off, zero-valued for teams with no reservations, non-zero for teams whose rollup has landed rows.GET /user/daily/activity,/organization/daily/activity,/customer/daily/activity,/tag/daily/activity,/agent/daily/activity,/enduser/daily/activity,/mcp_server/daily/activity: each gains the fields too, but always zero — none of those daily tables carry PTU flat cost. Kept identical response shape across entities so downstream consumers don't need per-entity branches.api_key=<real token>naturally excludes sentinel PTU rows because the sentinel value doesn't equal any real hashed token. A client explicitly passingapi_key=__ptu_reservation__sees only PTU rows (undocumented but harmless; useful for FinOps auditors).LiteLLM_TeamTable.spend, budget enforcement, per-request spend hot path, or any Prometheus metric.Files changed
litellm/constants.py:PTU_SENTINEL_API_KEYandPTU_ROLLUP_JOB_IDconstantslitellm/proxy/spend_tracking/ptu_reservation_rollup.py: re-exports constants fromlitellm.constantslitellm/types/proxy/management_endpoints/common_daily_activity.py:flat_costonSpendMetrics,total_flat_costonDailySpendMetadatalitellm/proxy/management_endpoints/common_daily_activity.py:update_metricsand_record_to_spend_metricsreadptu_flat_costupdate_breakdown_metricsguards allapi_keywrites on sentinel_build_aggregated_sql_queryemitsSUM(ptu_flat_cost)for team,0::floatfor others_aggregate_grouping_sets_records_syncgates*_API_KEYlevels on sentinelget_daily_activityandget_daily_activity_aggregatedpopulatetotal_flat_coston the response_aggregate_spend_recordsand_aggregate_grouping_sets_recordsskip sentinel inget_api_key_metadatalookupui/litellm-dashboard/src/lib/http/schema.d.ts: regenerated (10 lines added for the two new fields)tests/test_litellm/proxy/management_endpoints/test_common_daily_activity.py: 15 new testsTests
15 new tests covering:
SpendMetrics.flat_costdefaults to 0.0update_metricsaccumulatesptu_flat_cost; handles missing attr (user/org/tag rows); handles None_record_to_spend_metricsreadsptu_flat_cost; defaults to 0.0 when absentapi_keysbreakdown but shows up inmodels,providers,endpoints,entitiesbreakdowns with correctflat_costapi_key_breakdownunder that modelget_api_key_metadatalookup excludes sentinel from the Prisma.find_manyINclauseSUM(ptu_flat_cost)for team table only,0::floatfor user/org/etc.get_daily_activityon team table with mixed real + sentinel rows returns correcttotal_spend,total_flat_cost, per-dayflat_cost, sentinel-freeapi_keysbreakdownMutation checks (locally, not permanent):
ptu_flat_costaccumulation inupdate_metricsfails 6 testsis_ptu_sentinel = Falseunconditionally fails 6 tests