diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index b7161a16ca2..4923c5da02a 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -1473,7 +1473,7 @@ async def common_checks( user_spend: Final = await get_current_spend( counter_key=user_counter(user_object.user_id), fallback_spend=user_object.spend or 0.0, - max_budget=user_budget if user_budget is not None else user_object.soft_budget, + max_budget=user_budget, ) _user_soft_budget_check(user_object=user_object, user_spend=user_spend, proxy_logging_obj=proxy_logging_obj) if user_budget is None: diff --git a/litellm/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users/user_info_view.tsx b/litellm/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users/user_info_view.tsx index 852e0021325..b429149a263 100644 --- a/litellm/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users/user_info_view.tsx +++ b/litellm/ui/litellm-dashboard/src/app/(dashboard)/users/_components/view_users/user_info_view.tsx @@ -35,7 +35,7 @@ import { teamDetailHref } from "@/utils/entityLinks"; import { BadgeLink } from "@/components/shared/BadgeLink"; import { UserEditView } from "../user_edit_view"; import OnboardingModal, { InvitationLink } from "@/components/onboarding_link"; -import { formatNumberWithCommas, copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils"; +import { formatBudgetValue, formatNumberWithCommas, copyToClipboard as utilCopyToClipboard } from "@/utils/dataUtils"; import { ArrowLeft, CheckIcon, CopyIcon, Plus, RefreshCw, Trash2 } from "lucide-react"; import { toast } from "@/lib/toast"; import { getBudgetDurationLabel } from "@/components/common_components/budget_duration_dropdown"; @@ -51,9 +51,6 @@ import { AutoRouterUsageView } from "@/app/(dashboard)/cost-optimization/_compon import { useActivityDateRange } from "@/app/(dashboard)/cost-optimization/_components/useDailyActivityRange"; import { BudgetPeriodTooltip } from "@/components/shared/BudgetPeriodTooltip"; -const formatBudgetValue = (value: number | null | undefined, fallback: string, precision = 4): string => - value !== null && value !== undefined ? `$${formatNumberWithCommas(value, precision)}` : fallback; - interface UserInfoViewProps { userId: string; onClose: () => void; diff --git a/litellm/ui/litellm-dashboard/src/utils/dataUtils.ts b/litellm/ui/litellm-dashboard/src/utils/dataUtils.ts index 08c5051d671..bc06cfd358e 100644 --- a/litellm/ui/litellm-dashboard/src/utils/dataUtils.ts +++ b/litellm/ui/litellm-dashboard/src/utils/dataUtils.ts @@ -48,6 +48,9 @@ export const formatNumberWithCommas = ( return `${sign}${scaled.toLocaleString("en-US", opts)}${suffix}`; }; +export const formatBudgetValue = (value: number | null | undefined, fallback: string, precision = 4): string => + value !== null && value !== undefined ? `$${formatNumberWithCommas(value, precision)}` : fallback; + export const getSpendString = (value: number | null | undefined, decimals: number = 6): string => { if (value === null || value === undefined || !Number.isFinite(value) || value === 0) { return "-"; diff --git a/tests/unit/proxy/auth/test_auth_checks.py b/tests/unit/proxy/auth/test_auth_checks.py index 00c09ea3fb7..6463db18116 100644 --- a/tests/unit/proxy/auth/test_auth_checks.py +++ b/tests/unit/proxy/auth/test_auth_checks.py @@ -2571,3 +2571,31 @@ async def test_common_checks_alerts_user_over_soft_budget_without_blocking(max_b assert soft_alerts[0].user_id == user_id assert soft_alerts[0].spend == 7.0 assert soft_alerts[0].soft_budget == 5.0 + + +@pytest.mark.asyncio +async def test_soft_budget_only_user_is_not_blocked_when_fail_closed_spend_is_unverifiable( + monkeypatch: pytest.MonkeyPatch, +) -> None: + import litellm.proxy.proxy_server as proxy_server + from litellm.models.user import LiteLLM_UserTable + from litellm.proxy.auth.auth_checks import common_checks + + monkeypatch.setitem(proxy_server.general_settings, "fail_closed_budget_enforcement", True) + user_id: Final = f"soft-only-{uuid.uuid4().hex}" + + result: Final = await common_checks( + request_body={"model": "gpt-4o-mini"}, + team_object=None, + user_object=LiteLLM_UserTable(user_id=user_id, soft_budget=5.0, spend=1.0), + end_user_object=None, + global_proxy_spend=None, + general_settings={}, + route="/chat/completions", + llm_router=None, + proxy_logging_obj=MagicMock(spec=ProxyLogging), + valid_token=UserAPIKeyAuth(token=f"token-{user_id}", user_id=user_id), + request=MagicMock(), + ) + + assert result is True