mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix: org budget update fix (#14541)
This commit is contained in:
parent
6e90f12e64
commit
18b09eb594
3 changed files with 83 additions and 2 deletions
|
|
@ -1918,7 +1918,7 @@ class LiteLLM_OrganizationMembershipTable(LiteLLMPydanticObjectBase):
|
|||
model_config = ConfigDict(protected_namespaces=())
|
||||
|
||||
|
||||
class LiteLLM_OrganizationTableUpdate(LiteLLMPydanticObjectBase):
|
||||
class LiteLLM_OrganizationTableUpdate(LiteLLM_BudgetTable):
|
||||
"""Represents user-controllable params for a LiteLLM_OrganizationTable record"""
|
||||
|
||||
organization_id: Optional[str] = None
|
||||
|
|
|
|||
|
|
@ -36,6 +36,28 @@ from litellm.proxy.utils import PrismaClient
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
def handle_nested_budget_structure_in_organization_update_request(raw_data: dict) -> dict:
|
||||
"""
|
||||
Transform organization update request to handle UI payload format.
|
||||
|
||||
The UI sends nested budget data in 'litellm_budget_table', but our
|
||||
model expects flat budget fields at the top level.
|
||||
"""
|
||||
transformed_data = raw_data.copy()
|
||||
|
||||
# Handle nested budget structure from UI
|
||||
if 'litellm_budget_table' in transformed_data:
|
||||
budget_data = transformed_data.pop('litellm_budget_table', {})
|
||||
if budget_data:
|
||||
# Extract valid budget fields and merge into top level
|
||||
budget_fields = LiteLLM_BudgetTable.model_fields.keys()
|
||||
for key, value in budget_data.items():
|
||||
if key in budget_fields and value is not None:
|
||||
transformed_data[key] = value
|
||||
|
||||
return transformed_data
|
||||
|
||||
|
||||
@router.post(
|
||||
"/organization/new",
|
||||
tags=["organization management"],
|
||||
|
|
@ -248,7 +270,7 @@ async def _set_object_permission(
|
|||
response_model=LiteLLM_OrganizationTableWithMembers,
|
||||
)
|
||||
async def update_organization(
|
||||
data: LiteLLM_OrganizationTableUpdate,
|
||||
request: Request,
|
||||
user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth),
|
||||
):
|
||||
"""
|
||||
|
|
@ -270,6 +292,13 @@ async def update_organization(
|
|||
},
|
||||
)
|
||||
|
||||
# Transform UI payload to expected format
|
||||
raw_data = await request.json()
|
||||
raw_data_with_flat_budget_fields = handle_nested_budget_structure_in_organization_update_request(raw_data)
|
||||
|
||||
# Create validated data model
|
||||
data = LiteLLM_OrganizationTableUpdate(**raw_data_with_flat_budget_fields)
|
||||
|
||||
if data.updated_by is None:
|
||||
data.updated_by = user_api_key_dict.user_id
|
||||
|
||||
|
|
@ -293,6 +322,23 @@ async def update_organization(
|
|||
existing_organization_row=existing_organization_row,
|
||||
)
|
||||
|
||||
# Handle budget updates if budget fields are provided
|
||||
budget_fields = {k: v for k, v in data.model_dump().items()
|
||||
if k in LiteLLM_BudgetTable.model_fields.keys() and v is not None}
|
||||
|
||||
if budget_fields and existing_organization_row.budget_id:
|
||||
await update_budget(
|
||||
budget_obj=BudgetNewRequest(
|
||||
budget_id=existing_organization_row.budget_id,
|
||||
**budget_fields
|
||||
),
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
)
|
||||
|
||||
# Remove budget fields from organization update data
|
||||
for field in LiteLLM_BudgetTable.model_fields.keys():
|
||||
updated_organization_row.pop(field, None)
|
||||
|
||||
response = await prisma_client.db.litellm_organizationtable.update(
|
||||
where={"organization_id": data.organization_id},
|
||||
data=updated_organization_row,
|
||||
|
|
|
|||
|
|
@ -235,3 +235,38 @@ async def test_organization_update_object_permissions_missing_permission_record(
|
|||
|
||||
# Verify upsert was called to create new record
|
||||
mock_prisma_client.db.litellm_objectpermissiontable.upsert.assert_called_once()
|
||||
|
||||
|
||||
def test_transform_organization_update_request():
|
||||
"""
|
||||
Test the request transformer handles nested litellm_budget_table structure from UI.
|
||||
"""
|
||||
from litellm.proxy.management_endpoints.organization_endpoints import (
|
||||
transform_organization_update_request,
|
||||
)
|
||||
|
||||
# Test UI payload with nested budget structure
|
||||
ui_payload = {
|
||||
"organization_id": "test_org_id",
|
||||
"organization_alias": "Test Org",
|
||||
"litellm_budget_table": {
|
||||
"max_budget": "1000.0",
|
||||
"tpm_limit": 5000,
|
||||
"rpm_limit": None
|
||||
}
|
||||
}
|
||||
|
||||
# Transform the payload
|
||||
transformed = transform_organization_update_request(ui_payload)
|
||||
|
||||
# Verify nested structure was flattened to top-level fields
|
||||
assert transformed["max_budget"] == "1000.0"
|
||||
assert transformed["tpm_limit"] == 5000
|
||||
assert "rpm_limit" not in transformed # None values shouldn't be included
|
||||
|
||||
# Verify nested field was removed
|
||||
assert "litellm_budget_table" not in transformed
|
||||
|
||||
# Verify other fields preserved
|
||||
assert transformed["organization_id"] == "test_org_id"
|
||||
assert transformed["organization_alias"] == "Test Org"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue