From f639769ca9b112e756c38a1f49797a9392c299d6 Mon Sep 17 00:00:00 2001 From: Ryan Crabbe Date: Wed, 15 Apr 2026 21:38:18 -0700 Subject: [PATCH] fix(proxy): use flat soft_budget field for virtual key projected-spend alerts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The projected-spend alert in _update_key_cache read from existing_spend_obj.litellm_budget_table["soft_budget"], but the nested dict is never populated for virtual keys (the combined_view SQL maps budget fields to flat top-level attributes instead). This made the check dead code — it silently short-circuited on every request, and when unblocked, crashed update_cache with a Pydantic ValidationError because _get_projected_spend_over_limit returns a date object but CallInfo.projected_exceeded_date expects str. Fixes: read from the flat existing_spend_obj.soft_budget field that IS populated, and stringify projected_exceeded_date. Also marks team soft budget email alerts as enterprise in docs. Closes #20324 --- .../docs/proxy/ui_team_soft_budget_alerts.md | 10 ++++++++++ litellm/proxy/proxy_server.py | 16 +++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/my-website/docs/proxy/ui_team_soft_budget_alerts.md b/docs/my-website/docs/proxy/ui_team_soft_budget_alerts.md index 17c42e57c9a..413457ccb82 100644 --- a/docs/my-website/docs/proxy/ui_team_soft_budget_alerts.md +++ b/docs/my-website/docs/proxy/ui_team_soft_budget_alerts.md @@ -2,6 +2,16 @@ import Image from '@theme/IdealImage'; # Team Soft Budget Alerts +:::info + +✨ This is an Enterprise feature. Email budget alerts require an enterprise license. + +[Enterprise Pricing](https://www.litellm.ai/#pricing) + +[Get free 7-day trial key](https://www.litellm.ai/enterprise#trial) + +::: + Set a soft budget on a team and get email alerts when spending crosses the threshold — without blocking any requests. ## Overview diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 9981c049c18..749551fb656 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -1870,26 +1870,20 @@ async def update_cache( # noqa: PLR0915 ## CHECK IF USER PROJECTED SPEND > SOFT LIMIT if ( existing_spend_obj.soft_budget_cooldown is False - and existing_spend_obj.litellm_budget_table is not None + and existing_spend_obj.soft_budget is not None and ( _is_projected_spend_over_limit( current_spend=new_spend, - soft_budget_limit=existing_spend_obj.litellm_budget_table[ - "soft_budget" - ], + soft_budget_limit=existing_spend_obj.soft_budget, ) is True ) ): projected_spend, projected_exceeded_date = _get_projected_spend_over_limit( current_spend=new_spend, - soft_budget_limit=existing_spend_obj.litellm_budget_table.get( - "soft_budget", None - ), + soft_budget_limit=existing_spend_obj.soft_budget, ) # type: ignore - soft_limit = existing_spend_obj.litellm_budget_table.get( - "soft_budget", float("inf") - ) + soft_limit = existing_spend_obj.soft_budget call_info = CallInfo( token=existing_spend_obj.token or "", spend=new_spend, @@ -1897,7 +1891,7 @@ async def update_cache( # noqa: PLR0915 max_budget=soft_limit, user_id=existing_spend_obj.user_id, projected_spend=projected_spend, - projected_exceeded_date=projected_exceeded_date, + projected_exceeded_date=str(projected_exceeded_date), event_group=Litellm_EntityType.KEY, ) # alert user