mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-04 07:27:51 +00:00
fix(proxy): stop 500 on /tag/update when the request carries budget_id
handle_budget_for_entity collects the update fields from LiteLLM_BudgetTable.model_fields, which includes budget_id, then calls BudgetNewRequest(budget_id=existing_budget_id, **fields) - so any update carrying budget_id, even the entity's own unchanged id, passed it twice and the endpoint 500'd before writing anything. budget_id is the row identity, already handled separately via data_budget_id; exclude it from the collected fields. Fixes #46999
This commit is contained in:
parent
b6ca57b36d
commit
af63104744
2 changed files with 39 additions and 2 deletions
|
|
@ -215,8 +215,10 @@ async def handle_budget_for_entity(
|
|||
update_budget,
|
||||
)
|
||||
|
||||
# Get all budget field names
|
||||
budget_params: Final = LiteLLM_BudgetTable.model_fields.keys()
|
||||
# Get all budget field names, except budget_id: that is the row identity,
|
||||
# handled separately via data_budget_id. Collecting it here passes it twice
|
||||
# to BudgetNewRequest on the update path and 500s the request.
|
||||
budget_params: Final = LiteLLM_BudgetTable.model_fields.keys() - {"budget_id"}
|
||||
|
||||
# Extract budget fields from data
|
||||
_json_data: Final = _BUDGET_DATA_MAPPING.validate_python(
|
||||
|
|
|
|||
|
|
@ -309,6 +309,41 @@ async def test_handle_budget_for_entity_updates_only_cleared_or_supplied_fields(
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_handle_budget_for_entity_update_carrying_own_budget_id() -> None:
|
||||
"""Regression for #46999: a /tag/update that carries the tag's own budget_id
|
||||
500'd with "multiple values for keyword argument 'budget_id'" because the id
|
||||
was collected into the update fields and also passed explicitly. The id is
|
||||
the row identity, not an updatable field, and the update must go through."""
|
||||
from litellm.proxy.management_helpers.utils import handle_budget_for_entity
|
||||
from litellm.types.tag_management import TagUpdateRequest
|
||||
|
||||
tag: Final = TagUpdateRequest.model_validate({"name": "budget-tag", "budget_id": "budget-1", "max_budget": 10})
|
||||
budget_update: Final = AsyncMock()
|
||||
prisma_client: Final = cast(
|
||||
PrismaClient,
|
||||
SimpleNamespace(db=SimpleNamespace(litellm_budgettable=SimpleNamespace(update=budget_update))),
|
||||
)
|
||||
|
||||
with (
|
||||
patch("litellm.proxy.proxy_server.prisma_client", prisma_client),
|
||||
patch("litellm.proxy.proxy_server.litellm_proxy_admin_name", "admin"),
|
||||
):
|
||||
returned: Final = await handle_budget_for_entity(
|
||||
data=tag,
|
||||
existing_budget_id="budget-1",
|
||||
user_api_key_dict=UserAPIKeyAuth(user_id="admin"),
|
||||
prisma_client=prisma_client,
|
||||
litellm_proxy_admin_name="admin",
|
||||
cleared_budget_fields=frozenset(),
|
||||
)
|
||||
|
||||
assert returned == "budget-1"
|
||||
budget_update.assert_awaited_once()
|
||||
update_data: Final = cast(Mapping[str, object], budget_update.await_args.kwargs["data"])
|
||||
assert update_data.get("max_budget") == 10
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_new_member_no_budget_when_default_budget_row_is_missing():
|
||||
from litellm.proxy._types import LitellmUserRoles
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue