Fix credit model: allowance is a floor, credits add on top (max_budget = allowance + credits, not reduced by cycle overspend)

This commit is contained in:
inference-bot committed 2026-10-03 13:06:02 -06:00
1 parent 563881d63c
commit 052a109040
1 file changed
+10 -14
+10 -14
View File
@@ -584,32 +584,28 @@ def add_credits(email: str, delta: float, reason: str, reference: str | None = N
def compute_max_budget(allowance: float, credit_balance: float, team_spend: float | None) -> dict: def compute_max_budget(allowance: float, credit_balance: float, team_spend: float | None) -> dict:
"""Compute the effective LiteLLM max_budget from the two buckets. """Compute the effective LiteLLM max_budget from the two buckets.
Model: the member has a monthly ALLOWANCE (resets via LiteLLM's Model: the member has a monthly ALLOWANCE (renews on each membership
budget_duration) plus a NON-EXPIRING credit balance. Spend draws the payment) plus a credit balance that adds spendable headroom ON TOP of the
allowance first; once spend exceeds the allowance within a cycle, credits allowance. The total spendable cap is simply `allowance + credit_balance`
cover the overflow. — credits never shrink the allowance; they extend it.
`team_spend` is the member's spend so far this cycle (from LiteLLM). `team_spend` is the member's spend so far this cycle (from LiteLLM). It is
Credits already consumed this cycle are excluded from the available pool used only to split the spend between allowance vs credits for display, NOT
so the pushed max_budget reflects exactly what's left: to reduce the cap.
max_budget = allowance + credits_remaining
Returns {"max_budget", "allowance_used", "credits_used"} for dashboards. Returns {"max_budget", "allowance_used", "credits_used"} for dashboards.
""" """
max_budget = allowance + credit_balance
if team_spend is None: if team_spend is None:
# Unknown spend: be permissive (full allowance + full credits) so we
# never wrongly block a member over a metrics gap.
return { return {
"max_budget": allowance + credit_balance, "max_budget": max_budget,
"allowance_used": 0.0, "allowance_used": 0.0,
"credits_used": 0.0, "credits_used": 0.0,
} }
allowance_used = min(max(team_spend, 0.0), allowance) allowance_used = min(max(team_spend, 0.0), allowance)
credits_used = max(0.0, team_spend - allowance) credits_used = max(0.0, team_spend - allowance)
credits_remaining = max(0.0, credit_balance - credits_used)
return { return {
"max_budget": allowance + credits_remaining, "max_budget": max_budget,
"allowance_used": allowance_used, "allowance_used": allowance_used,
"credits_used": credits_used, "credits_used": credits_used,
} }