feat(ui): add session id filter to request logs

This commit is contained in:
Thibault Serot 2026-07-09 11:58:29 +10:00
parent 142d5aa12b
commit 9813c4bf41
7 changed files with 107 additions and 0 deletions

View file

@ -1622,6 +1622,10 @@ async def ui_view_spend_logs(
default=None,
description="request_id to get spend logs for specific request_id",
),
session_id: str | None = fastapi.Query(
default=None,
description="Filter spend logs by session_id",
),
team_id: str | None = fastapi.Query(
default=None,
description="Filter spend logs by team_id",
@ -1772,6 +1776,9 @@ 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
@ -1887,6 +1894,7 @@ 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"),

View file

@ -1,4 +1,5 @@
import asyncio
import collections
import datetime
import json
import os
@ -85,6 +86,7 @@ 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",
@ -162,7 +164,21 @@ def make_ui_spend_logs_mock_prisma(mock_spend_logs, filter_fn, team_lookup_fn=No
async def count(self, *args, **kwargs):
return len(filter_fn(kwargs.get("where", {})))
async def group_by(self, by, where, count):
allowed = set(where["session_id"]["in"])
tallied = collections.Counter(
log["session_id"]
for log in mock_spend_logs
if log.get("session_id") in allowed
)
return [
{"session_id": sid, "_count": {"session_id": n}}
for sid, n in tallied.items()
]
async def query_raw(self, sql_query, *params):
if "mcp_tool_call_count" in sql_query:
return []
filtered = filter_fn(_reconstruct_ui_where_from_sql(sql_query, params))
total = len(filtered)
if "COUNT(*)" in sql_query:
@ -597,6 +613,74 @@ async def test_ui_view_spend_logs_with_user_id(client, monkeypatch):
assert data["data"][0]["user"] == "test_user_1"
@pytest.mark.asyncio
async def test_ui_view_spend_logs_with_session_id(client, monkeypatch):
mock_spend_logs = [
{
"id": "log1",
"request_id": "req1",
"api_key": "sk-test-key",
"user": "test_user_1",
"session_id": "session-abc",
"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",
},
]
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
monkeypatch.setattr(
"litellm.proxy.proxy_server.prisma_client",
make_ui_spend_logs_mock_prisma(mock_spend_logs, filter_by_session),
)
start_date, end_date = _default_date_range()
response = client.get(
"/spend/logs/ui",
params={
"session_id": "session-abc",
"start_date": start_date,
"end_date": end_date,
},
headers={"Authorization": "Bearer sk-test"},
)
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"])
# Mock spend logs with distinct values for sorting tests.
# req_a: spend=0.10, tokens=500, start/end earliest
# req_b: spend=0.05, tokens=200, start/end 2nd

View file

@ -1929,6 +1929,7 @@ interface UiSpendLogsParams {
api_key?: string;
team_id?: string;
request_id?: string;
session_id?: string;
user_id?: string;
end_user?: string;
status_filter?: string;

View file

@ -63,6 +63,11 @@ export function getLogFilterOptions(accessToken: string): FilterOption[] {
label: "Key Hash",
isSearchable: false,
},
{
name: FILTER_KEYS.SESSION_ID,
label: "Session ID",
isSearchable: false,
},
{
name: "Model",
label: "Model",

View file

@ -218,6 +218,7 @@ describe("useLogFilterLogic", () => {
{ filterKey: "Team ID", paramName: "team_id", value: "team-a" },
{ filterKey: "Key Hash", paramName: "api_key", value: "key-x" },
{ filterKey: "Request ID", paramName: "request_id", value: "req-xyz" },
{ filterKey: "Session ID", paramName: "session_id", value: "sess-42" },
{ filterKey: "User ID", paramName: "user_id", value: "user-123" },
{ filterKey: "End User", paramName: "end_user", value: "user-a" },
{ filterKey: "Status", paramName: "status_filter", value: "error" },

View file

@ -30,6 +30,7 @@ export const FILTER_KEYS = {
TEAM_ID: "Team ID",
KEY_HASH: "Key Hash",
REQUEST_ID: "Request ID",
SESSION_ID: "Session ID",
MODEL: "Model",
/** Exact match on LiteLLM_SpendLogs.model — use for search tools and public model names. */
PUBLIC_MODEL_OR_SEARCH_TOOL: "Public model / search tool",
@ -49,6 +50,7 @@ const TEXT_FILTER_KEYS: readonly (keyof LogFilterState)[] = [
FILTER_KEYS.KEY_HASH,
FILTER_KEYS.ERROR_MESSAGE,
FILTER_KEYS.REQUEST_ID,
FILTER_KEYS.SESSION_ID,
FILTER_KEYS.USER_ID,
FILTER_KEYS.PUBLIC_MODEL_OR_SEARCH_TOOL,
];
@ -62,6 +64,7 @@ export const defaultFilters: LogFilterState = {
[FILTER_KEYS.TEAM_ID]: "",
[FILTER_KEYS.KEY_HASH]: "",
[FILTER_KEYS.REQUEST_ID]: "",
[FILTER_KEYS.SESSION_ID]: "",
[FILTER_KEYS.MODEL]: "",
[FILTER_KEYS.PUBLIC_MODEL_OR_SEARCH_TOOL]: "",
[FILTER_KEYS.USER_ID]: "",
@ -160,6 +163,7 @@ export function useLogFilterLogic({
api_key: effectiveFilters[FILTER_KEYS.KEY_HASH] || undefined,
team_id: effectiveFilters[FILTER_KEYS.TEAM_ID] || undefined,
request_id: effectiveFilters[FILTER_KEYS.REQUEST_ID] || undefined,
session_id: effectiveFilters[FILTER_KEYS.SESSION_ID] || undefined,
user_id: effectiveFilters[FILTER_KEYS.USER_ID] || (filterByCurrentUser ? userID ?? undefined : undefined),
end_user: effectiveFilters[FILTER_KEYS.END_USER] || undefined,
status_filter: effectiveFilters[FILTER_KEYS.STATUS] || undefined,

View file

@ -48633,6 +48633,8 @@ 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 */
session_id?: string | null;
/** @description Filter spend logs by team_id */
team_id?: string | null;
/** @description Filter logs with spend greater than or equal to this value */
@ -48739,6 +48741,8 @@ 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 */
session_id?: string | null;
/** @description Filter spend logs by team_id */
team_id?: string | null;
/** @description Filter logs with spend greater than or equal to this value */