diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 819a38eec19..9e3cd18ff55 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -1997,7 +1997,12 @@ class TeamRequest(LiteLLMPydanticObjectBase): class LiteLLM_BudgetTable(LiteLLMPydanticObjectBase): - """Represents user-controllable params for a LiteLLM_BudgetTable record""" + """Represents user-controllable params for a LiteLLM_BudgetTable record. + + Budget-write paths use `model_fields.keys()` on this class as an allowlist + for user input. Keep server-managed fields (e.g. `budget_reset_at`) on + `LiteLLM_BudgetTableFull` so they aren't user-settable. + """ budget_id: Optional[str] = None soft_budget: Optional[float] = None @@ -2007,7 +2012,6 @@ class LiteLLM_BudgetTable(LiteLLMPydanticObjectBase): rpm_limit: Optional[int] = None model_max_budget: Optional[dict] = None budget_duration: Optional[str] = None - budget_reset_at: Optional[datetime] = None allowed_models: Optional[List[str]] = ( None # per-member model scope; empty = inherit team models ) @@ -2016,8 +2020,9 @@ class LiteLLM_BudgetTable(LiteLLMPydanticObjectBase): class LiteLLM_BudgetTableFull(LiteLLM_BudgetTable): - """Represents all params for a LiteLLM_BudgetTable record""" + """LiteLLM_BudgetTable + server-managed fields returned on API responses.""" + budget_reset_at: Optional[datetime] = None created_at: datetime @@ -3696,7 +3701,12 @@ class LiteLLM_TeamMembership(LiteLLMPydanticObjectBase): budget_id: Optional[str] = None spend: Optional[float] = 0.0 total_spend: Optional[float] = 0.0 - litellm_budget_table: Optional[LiteLLM_BudgetTable] + # Union so Pydantic picks Full when data has server-managed fields + # (/team/info) and Base when callers/tests construct with only + # user-settable fields. + litellm_budget_table: Optional[ + Union[LiteLLM_BudgetTableFull, LiteLLM_BudgetTable] + ] def safe_get_team_member_rpm_limit(self) -> Optional[int]: if self.litellm_budget_table is not None: