diff --git a/litellm/proxy/utils.py b/litellm/proxy/utils.py index 88ff8ab55db..4b3e4cfd6fb 100644 --- a/litellm/proxy/utils.py +++ b/litellm/proxy/utils.py @@ -9174,7 +9174,7 @@ async def _keep_charges_of_dropped_logs(repo: SpendLogsRepository, rows: Sequenc try: await repo.insert_with_deliveries(charged) except Exception as e: # noqa: BLE001 # the request log is already lost; keep the flush going for other rows - if not (PrismaDBExceptionHandler.is_rejected_row_error(e) or isinstance(e, UnicodeEncodeError)): + if not (PrismaDBExceptionHandler.is_rejected_row_error(e) or isinstance(e, (UnicodeEncodeError, ValueError))): raise spend_log_error( "Spend tracking - could not keep the charges of %d dropped spend log rows. error=%s", @@ -9215,7 +9215,7 @@ async def _create_spend_logs_with_poison_isolation( await repo.insert_with_deliveries(rows) return failure_budget except Exception as e: - if not (PrismaDBExceptionHandler.is_rejected_row_error(e) or isinstance(e, UnicodeEncodeError)): + if not (PrismaDBExceptionHandler.is_rejected_row_error(e) or isinstance(e, (UnicodeEncodeError, ValueError))): raise if PrismaDBExceptionHandler.is_database_service_unavailable_error(e): raise diff --git a/tests/unit/proxy/test_update_spend.py b/tests/unit/proxy/test_update_spend.py index 836ae7a1e33..e2c027408c1 100644 --- a/tests/unit/proxy/test_update_spend.py +++ b/tests/unit/proxy/test_update_spend.py @@ -437,3 +437,39 @@ async def test_a_rejected_charge_rescue_is_logged_and_the_flush_continues(): rows = [{"request_id": "rejected", "accounting_payload": {"spend": 1.0}}] assert await _create_spend_logs_with_poison_isolation(repo, rows, failure_budget=10) == 9 + + +class _StatementValidatingRepository: + def __init__(self): + self.written = [] + self.charges_kept = [] + + async def insert_with_deliveries(self, rows): + from litellm.repositories.spend_log_insert import spend_log_insert_statement + + if all(row.get("accounting_log_disabled") for row in rows): + self.charges_kept.extend(row["request_id"] for row in rows) + return len(rows) + spend_log_insert_statement(rows) + self.written.extend(row["request_id"] for row in rows) + return len(rows) + + +@pytest.mark.asyncio +async def test_a_row_the_insert_statement_rejects_is_dropped_alone_and_keeps_its_charge(): + from litellm.proxy.utils import _create_spend_logs_with_poison_isolation + + repo = _StatementValidatingRepository() + rows = [ + {"request_id": "good", "call_type": "acompletion", "messages": "[]"}, + { + "request_id": "unparseable", + "call_type": "acompletion", + "messages": "not json", + "accounting_payload": {"spend": 1.0}, + }, + ] + + assert await _create_spend_logs_with_poison_isolation(repo, rows, failure_budget=10) < 10 + assert repo.written == ["good"] + assert repo.charges_kept == ["unparseable"]