mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-13 08:52:02 +00:00
fix(proxy): retain known spend when no-DB reconcile fails
When BudgetReservationReconcileLogger cannot settle a reservation and response_cost is positive, do not release_or_invalidate (which refunds to 0.0 and finalizes). Without Prisma there is no ledger to restore that spend; leave the reservation open so settlement can retry and budgets fail closed. Zero-cost failures still release as before.
This commit is contained in:
parent
ccdb2b76b8
commit
e6bbb744c2
2 changed files with 41 additions and 1 deletions
|
|
@ -63,7 +63,17 @@ class BudgetReservationReconcileLogger(CustomLogger):
|
|||
budget_reservation=budget_reservation,
|
||||
actual_cost=actual_cost,
|
||||
)
|
||||
except Exception: # noqa: BLE001 # a counter backend failure must still release the reservation
|
||||
except Exception: # noqa: BLE001 # counter failure: refund only when no known spend
|
||||
if actual_cost > 0:
|
||||
# No Prisma ledger on this path. Releasing to 0.0 / finalizing would
|
||||
# permanently refund completed work and block retry; leave the
|
||||
# reservation open so settlement can retry and budgets fail closed.
|
||||
verbose_proxy_logger.exception(
|
||||
"Failed to reconcile budget reservation without a database logger; "
|
||||
"retaining known spend %s for retry",
|
||||
actual_cost,
|
||||
)
|
||||
return
|
||||
verbose_proxy_logger.exception(
|
||||
"Failed to reconcile budget reservation without a database logger; releasing instead"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -151,6 +151,36 @@ async def test_success_falls_back_to_release_when_reconcile_fails(
|
|||
release.assert_awaited_once_with(budget_reservation=reservation)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_success_retains_known_spend_when_reconcile_fails(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
reservation = _reservation()
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.budget_reservation.reconcile_budget_reservation",
|
||||
AsyncMock(side_effect=RuntimeError("counter down")),
|
||||
)
|
||||
release = AsyncMock()
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.budget_reservation.release_or_invalidate_budget_reservation",
|
||||
release,
|
||||
)
|
||||
logger = BudgetReservationReconcileLogger()
|
||||
|
||||
await logger.async_log_success_event(
|
||||
{
|
||||
"litellm_params": {"metadata": {"user_api_key_budget_reservation": reservation}},
|
||||
"response_cost": 0.004,
|
||||
},
|
||||
response_obj=object(),
|
||||
start_time=datetime.now(UTC),
|
||||
end_time=datetime.now(UTC),
|
||||
)
|
||||
|
||||
release.assert_not_awaited()
|
||||
assert reservation["finalized"] is False
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def spend_counter_cache(monkeypatch: pytest.MonkeyPatch) -> DualCache:
|
||||
cache: Final = DualCache()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue