mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-12 08:42:18 +00:00
fix(spend): keep free response reads and cancels off the creation spend row (#51110)
* fix(spend): track self-heal for test_with_id_security_off_reads_and_cancels_add_no_rows_and_no_replayed_turns Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(spend): preserve response turn rows when id security is disabled Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: mateo <277851410+mateo-berri@users.noreply.github.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
606490a52d
commit
c2afb07f93
3 changed files with 64 additions and 0 deletions
|
|
@ -771,6 +771,13 @@ class DBSpendUpdateWriter:
|
|||
llm_router=llm_router,
|
||||
)
|
||||
billable_cost: Final = _billable_cost(response_cost, payload)
|
||||
if (
|
||||
proxy_settings.get("disable_responses_id_security") is True
|
||||
and payload["call_type"] in {"aget_responses", "acancel_responses"}
|
||||
and payload["status"] == "success"
|
||||
and not billable_cost
|
||||
):
|
||||
return True
|
||||
payload["spend"] = billable_cost or 0.0
|
||||
payload["startTime"] = _iso_timestamp(payload["startTime"])
|
||||
payload["endTime"] = _iso_timestamp(payload["endTime"])
|
||||
|
|
|
|||
|
|
@ -448,6 +448,12 @@ def test_with_id_security_off_reads_and_cancels_add_no_rows_and_no_replayed_turn
|
|||
assert [string_value(JSON_OBJECT.validate_json(read.content)["id"]) for read in reads] == [first_id] * 3
|
||||
later_id: Final = _created_response_id(insecure, key, {"model": native, "input": "a later request"})
|
||||
assert _request_ids_for_key(insecure, key, 2) == [first_id, later_id]
|
||||
(first_turn,) = insecure.rows(
|
||||
'SELECT request_id, proxy_server_request, messages, call_type FROM "LiteLLM_SpendLogs" WHERE request_id=%s',
|
||||
(first_id,),
|
||||
)
|
||||
assert first_turn["call_type"] == "aresponses", first_turn
|
||||
assert object_value(first_turn["proxy_server_request"])["input"] == first_prompt, first_turn
|
||||
wire.drain()
|
||||
|
||||
_terminal_response_id(
|
||||
|
|
|
|||
|
|
@ -5890,6 +5890,57 @@ async def test_request_error_counts_survive_the_ledger_path_switch(monkeypatch:
|
|||
): 1}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize("call_type", ["aresponses", "aget_responses", "acancel_responses"])
|
||||
@pytest.mark.parametrize(
|
||||
("security_disabled", "status", "cost", "keep_read"),
|
||||
[
|
||||
(False, "success", 0.0, True),
|
||||
(True, "success", 0.0, False),
|
||||
(True, "success", None, False),
|
||||
(True, "success", 0.23, True),
|
||||
(True, "failure", 0.0, True),
|
||||
],
|
||||
)
|
||||
async def test_unsecured_free_response_reads_leave_the_creation_spend_row_alone(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
call_type: str,
|
||||
security_disabled: bool,
|
||||
status: str,
|
||||
cost: float | None,
|
||||
keep_read: bool,
|
||||
) -> None:
|
||||
from litellm.proxy import proxy_server
|
||||
|
||||
writer: Final = DBSpendUpdateWriter()
|
||||
prisma: Final = _tool_usage_prisma()
|
||||
prisma.get_request_status.return_value = status
|
||||
monkeypatch.setattr(proxy_server, "prisma_client", prisma)
|
||||
monkeypatch.setattr(proxy_server, "disable_spend_logs", False)
|
||||
monkeypatch.setitem(proxy_server.general_settings, "disable_responses_id_security", security_disabled)
|
||||
now: Final = datetime(2026, 11, 11, tzinfo=timezone.utc)
|
||||
assert await writer.update_database(
|
||||
token="test-token",
|
||||
user_id="test-user",
|
||||
end_user_id=None,
|
||||
team_id=None,
|
||||
org_id=None,
|
||||
kwargs={
|
||||
"model": "response-model",
|
||||
"call_type": call_type,
|
||||
"litellm_call_id": "read-call",
|
||||
"litellm_params": {"metadata": {"user_api_key": "test-token", "status": status}},
|
||||
},
|
||||
completion_response={"id": "resp-shared"},
|
||||
start_time=now,
|
||||
end_time=now,
|
||||
response_cost=cost,
|
||||
)
|
||||
await drain_logging_tasks()
|
||||
expected: Final = [call_type] if call_type == "aresponses" or keep_read else []
|
||||
assert [row["call_type"] for row in prisma.spend_log_transactions] == expected
|
||||
|
||||
|
||||
class _SessionTotalsRedis:
|
||||
def __init__(self) -> None:
|
||||
self.values: dict[str, float] = {} # mutable-ok: stands in for Redis state
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue