mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(accounting): isolate a spend log row the insert statement rejects
The raw SpendLogs insert validates columns and JSON before Postgres sees the row, so a row that fails there is now bisected out and dropped alone while its charge is kept, instead of failing and replaying the whole flush. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d7815814eb
commit
da1f285196
2 changed files with 38 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue