mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(proxy): count when a grouped log page starts past the last one
An out-of-range cursorless page returns nothing, and reading its total off the offset reported more sessions than exist (page 4 of 100 sessions at page size 50 claimed 150). Only a page that holds rows, or the first page, ends the list; anything past it falls back to the bounded count. Claude-Session: https://claude.ai/code/session_01ESi9JwaXDww1vP3Qsrr4Mz
This commit is contained in:
parent
4bcd60e72b
commit
06fe2691c3
2 changed files with 41 additions and 4 deletions
|
|
@ -2979,9 +2979,10 @@ async def _ui_session_grouped_spend_logs(
|
|||
by its newest non-MCP row, enriched by ``_build_ui_spend_logs_response``
|
||||
exactly like the flat listing, and the response carries
|
||||
``next_session_cursor`` / ``has_more`` while ``total`` counts sessions
|
||||
(capped like the flat total). A page that runs out of sessions is itself
|
||||
the end of the list, so its ``total`` is ``offset + len(page)`` and the
|
||||
grouped count query is skipped.
|
||||
(capped like the flat total). A page that runs out of sessions while still
|
||||
holding some is itself the end of the list, so its ``total`` is
|
||||
``offset + len(page)`` and the grouped count query is skipped; a page that
|
||||
starts past the end says nothing about the total, so that one is counted.
|
||||
"""
|
||||
where_clause: Final = " AND ".join(sql_conditions) if sql_conditions else "TRUE"
|
||||
cmp_op: Final = "<" if sort_desc else ">"
|
||||
|
|
@ -3026,7 +3027,8 @@ async def _ui_session_grouped_spend_logs(
|
|||
else None
|
||||
)
|
||||
|
||||
page_ends_the_list: Final = cursor is None and page_limit > 0 and not has_more
|
||||
page_starts_inside_the_list: Final = offset == 0 or len(page_rows) > 0
|
||||
page_ends_the_list: Final = cursor is None and page_limit > 0 and not has_more and page_starts_inside_the_list
|
||||
total_records, total_is_capped = (
|
||||
(offset + len(page_rows), False)
|
||||
if page_ends_the_list
|
||||
|
|
|
|||
|
|
@ -6839,6 +6839,41 @@ async def test_ui_view_spend_logs_group_by_session_short_page_totals_itself(clie
|
|||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_page_past_the_end_keeps_the_real_total(client, monkeypatch):
|
||||
"""An empty page past the last one says nothing about the total, so it is counted rather than inferred."""
|
||||
sessions = tuple((f"sess-{index:02d}", f"2026-08-29 10:{59 - index:02d}:00") for index in range(100))
|
||||
mock_prisma = _session_grouped_paginating_prisma(sessions)
|
||||
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma)
|
||||
monkeypatch.setattr(
|
||||
"litellm.proxy.spend_tracking.spend_management_endpoints._is_admin_view_safe",
|
||||
lambda user_api_key_dict: True,
|
||||
)
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
try:
|
||||
start_date, end_date = _default_date_range()
|
||||
response = client.get(
|
||||
"/spend/logs/ui",
|
||||
params={
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"group_by_session": "true",
|
||||
"page": 4,
|
||||
"page_size": 50,
|
||||
},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
assert response.status_code == 200, response.text
|
||||
data = response.json()
|
||||
assert data["data"] == []
|
||||
assert data["total"] == 100, "the empty page's offset is not a total"
|
||||
assert data["total_pages"] == 2
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_ui_view_spend_logs_group_by_session_page_past_count_cap_is_empty(client, monkeypatch):
|
||||
"""The last page inside the capped total still lists sessions; the page after it is empty and costs no query."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue