feat(ui): support partial match on session id filter

This commit is contained in:
Thibault Serot 2026-07-09 12:39:13 +10:00
parent 9813c4bf41
commit f33403cb4b
3 changed files with 54 additions and 53 deletions

View file

@ -1624,7 +1624,7 @@ async def ui_view_spend_logs(
),
session_id: str | None = fastapi.Query(
default=None,
description="Filter spend logs by session_id",
description="Filter spend logs by session_id (partial string match)",
),
team_id: str | None = fastapi.Query(
default=None,
@ -1776,9 +1776,6 @@ async def ui_view_spend_logs(
if request_id is not None:
where_conditions["request_id"] = request_id
if session_id is not None:
where_conditions["session_id"] = session_id
if model is not None:
where_conditions["model"] = model
@ -1894,7 +1891,6 @@ async def ui_view_spend_logs(
('"user"', "user"),
("api_key", "api_key"),
("request_id", "request_id"),
("session_id", "session_id"),
("model", "model"),
("model_id", "model_id"),
("model_group", "model_group"),
@ -1914,6 +1910,12 @@ async def ui_view_spend_logs(
p += 2
sql_conditions.append(or_clause)
if session_id is not None:
like_escaped_session_id = session_id.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
sql_conditions.append(f"session_id LIKE ${p}")
sql_params.append(f"%{like_escaped_session_id}%")
p += 1
# Status filter
if status_filter is not None:
if status_filter == "success":

View file

@ -86,7 +86,6 @@ def _reconstruct_ui_where_from_sql(sql_query, params):
'"user"': "user",
"api_key": "api_key",
"request_id": "request_id",
"session_id": "session_id",
"model": "model",
"model_id": "model_id",
"model_group": "model_group",
@ -100,6 +99,7 @@ def _reconstruct_ui_where_from_sql(sql_query, params):
alias = re.search(r"user_api_key_alias' LIKE \$(\d+)", cond)
code = re.search(r"error_code' = \$(\d+)", cond)
msg = re.search(r"error_message' LIKE \$(\d+)", cond)
sess = re.fullmatch(r"session_id LIKE \$(\d+)", cond)
status = re.fullmatch(r"status = \$(\d+)", cond)
if gte:
date_bounds["gte"] = _iso(params[int(gte.group(1)) - 1])
@ -109,6 +109,8 @@ def _reconstruct_ui_where_from_sql(sql_query, params):
where["OR"] = where.get("OR", []) + [{"multi_team": True}]
elif "status = 'success'" in cond:
where["OR"] = where.get("OR", []) + [{"status": "success"}]
elif sess:
where["session_id"] = {"contains": str(params[int(sess.group(1)) - 1]).strip("%")}
elif status:
where["status"] = {"equals": params[int(status.group(1)) - 1]}
elif alias:
@ -165,16 +167,14 @@ def make_ui_spend_logs_mock_prisma(mock_spend_logs, filter_fn, team_lookup_fn=No
return len(filter_fn(kwargs.get("where", {})))
async def group_by(self, by, where, count):
allowed = set(where["session_id"]["in"])
col = by[0]
allowed = where.get(col, {}).get("in")
tallied = collections.Counter(
log["session_id"]
log[col]
for log in mock_spend_logs
if log.get("session_id") in allowed
if log.get(col) is not None and (allowed is None or log[col] in allowed)
)
return [
{"session_id": sid, "_count": {"session_id": n}}
for sid, n in tallied.items()
]
return [{col: value, "_count": {col: n}} for value, n in tallied.items()]
async def query_raw(self, sql_query, *params):
if "mcp_tool_call_count" in sql_query:
@ -614,48 +614,47 @@ async def test_ui_view_spend_logs_with_user_id(client, monkeypatch):
@pytest.mark.asyncio
async def test_ui_view_spend_logs_with_session_id(client, monkeypatch):
mock_spend_logs = [
{
"id": "log1",
"request_id": "req1",
@pytest.mark.parametrize(
"session_id_query,expected_request_ids",
[
("session-filter-demo-1", {"req1", "req2"}),
("session-filter-demo-2", {"req3"}),
("session-filter", {"req1", "req2", "req3"}),
("demo", {"req1", "req2", "req3"}),
("no-such-session", set()),
],
)
async def test_ui_view_spend_logs_with_session_id(
client, monkeypatch, session_id_query, expected_request_ids
):
def make_log(request_id, session_id):
return {
"id": f"log-{request_id}",
"request_id": request_id,
"api_key": "sk-test-key",
"user": "test_user_1",
"session_id": "session-abc",
"session_id": session_id,
"spend": 0.05,
"startTime": datetime.datetime.now(timezone.utc).isoformat(),
"model": "gpt-4",
},
{
"id": "log2",
"request_id": "req2",
"api_key": "sk-test-key",
"user": "test_user_1",
"session_id": "session-abc",
"spend": 0.10,
"startTime": datetime.datetime.now(timezone.utc).isoformat(),
"model": "gpt-4",
},
{
"id": "log3",
"request_id": "req3",
"api_key": "sk-test-key",
"user": "test_user_1",
"session_id": "session-other",
"spend": 0.02,
"startTime": datetime.datetime.now(timezone.utc).isoformat(),
"model": "gpt-4",
},
}
mock_spend_logs = [
make_log("req1", "session-filter-demo-1"),
make_log("req2", "session-filter-demo-1"),
make_log("req3", "session-filter-demo-2"),
make_log("req4", "unrelated-abc"),
]
def filter_by_session(where):
if "session_id" in where:
return [
log
for log in mock_spend_logs
if log["session_id"] == where["session_id"]
]
return mock_spend_logs
session_filter = where.get("session_id")
if session_filter is None:
return mock_spend_logs
return [
log
for log in mock_spend_logs
if session_filter["contains"] in log["session_id"]
]
monkeypatch.setattr(
"litellm.proxy.proxy_server.prisma_client",
@ -667,7 +666,7 @@ async def test_ui_view_spend_logs_with_session_id(client, monkeypatch):
response = client.get(
"/spend/logs/ui",
params={
"session_id": "session-abc",
"session_id": session_id_query,
"start_date": start_date,
"end_date": end_date,
},
@ -676,9 +675,9 @@ async def test_ui_view_spend_logs_with_session_id(client, monkeypatch):
assert response.status_code == 200
data = response.json()
assert data["total"] == 2
assert {log["request_id"] for log in data["data"]} == {"req1", "req2"}
assert all(log["session_id"] == "session-abc" for log in data["data"])
assert data["total"] == len(expected_request_ids)
assert {log["request_id"] for log in data["data"]} == expected_request_ids
assert all(session_id_query in log["session_id"] for log in data["data"])
# Mock spend logs with distinct values for sorting tests.

View file

@ -48633,7 +48633,7 @@ export interface operations {
user_id?: string | null;
/** @description request_id to get spend logs for specific request_id */
request_id?: string | null;
/** @description Filter spend logs by session_id */
/** @description Filter spend logs by session_id (partial string match) */
session_id?: string | null;
/** @description Filter spend logs by team_id */
team_id?: string | null;
@ -48741,7 +48741,7 @@ export interface operations {
user_id?: string | null;
/** @description request_id to get spend logs for specific request_id */
request_id?: string | null;
/** @description Filter spend logs by session_id */
/** @description Filter spend logs by session_id (partial string match) */
session_id?: string | null;
/** @description Filter spend logs by team_id */
team_id?: string | null;