mirror of
https://github.com/BerriAI/litellm.git
synced 2026-08-28 05:25:59 +00:00
Merge remote-tracking branch 'origin/litellm_internal_staging' into litellm_/release-version-bump-nightly-c31ca1
# Conflicts: # uv.lock
This commit is contained in:
commit
45a0ff8207
10 changed files with 115 additions and 5 deletions
|
|
@ -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 (partial string match)",
|
||||
),
|
||||
team_id: str | None = fastapi.Query(
|
||||
default=None,
|
||||
description="Filter spend logs by team_id",
|
||||
|
|
@ -1906,6 +1910,12 @@ async def ui_view_spend_logs(
|
|||
p += 2
|
||||
sql_conditions.append(or_clause)
|
||||
|
||||
if session_id is not None and isinstance(session_id, str):
|
||||
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":
|
||||
|
|
|
|||
|
|
@ -259,6 +259,7 @@ constraint-dependencies = [
|
|||
"tornado>=6.5.6",
|
||||
"aiohttp>=3.14.1,<4.0",
|
||||
"packaging>=24.0",
|
||||
"soupsieve>=2.8.4",
|
||||
]
|
||||
override-dependencies = [
|
||||
# a2a-sdk 1.x requires packaging>=24.0; lunary 1.4.x still caps at <24.0.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import asyncio
|
||||
import collections
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
|
|
@ -98,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])
|
||||
|
|
@ -107,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:
|
||||
|
|
@ -162,7 +166,19 @@ 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):
|
||||
col = by[0]
|
||||
allowed = where.get(col, {}).get("in")
|
||||
tallied = collections.Counter(
|
||||
log[col]
|
||||
for log in mock_spend_logs
|
||||
if log.get(col) is not None and (allowed is None or log[col] in allowed)
|
||||
)
|
||||
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:
|
||||
return []
|
||||
filtered = filter_fn(_reconstruct_ui_where_from_sql(sql_query, params))
|
||||
total = len(filtered)
|
||||
if "COUNT(*)" in sql_query:
|
||||
|
|
@ -597,6 +613,73 @@ async def test_ui_view_spend_logs_with_user_id(client, monkeypatch):
|
|||
assert data["data"][0]["user"] == "test_user_1"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@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_id,
|
||||
"spend": 0.05,
|
||||
"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):
|
||||
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",
|
||||
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_id_query,
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
},
|
||||
headers={"Authorization": "Bearer sk-test"},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
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.
|
||||
# req_a: spend=0.10, tokens=500, start/end earliest
|
||||
# req_b: spend=0.05, tokens=200, start/end 2nd
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"@typescript-eslint/no-explicit-any": 1982,
|
||||
"complexity": 128,
|
||||
"local/no-large-inline-object-arg": 512,
|
||||
"local/no-large-inline-object-arg": 519,
|
||||
"local/no-long-condition-chain": 233,
|
||||
"max-depth": 59,
|
||||
"no-console": 15
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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" },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
4
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
4
ui/litellm-dashboard/src/lib/http/schema.d.ts
generated
vendored
|
|
@ -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 (partial string match) */
|
||||
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 (partial string match) */
|
||||
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 */
|
||||
|
|
|
|||
9
uv.lock
generated
9
uv.lock
generated
|
|
@ -9,7 +9,7 @@ resolution-markers = [
|
|||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "2026-07-06T16:10:51.214184Z"
|
||||
exclude-newer = "2026-07-06T18:05:33.611729Z"
|
||||
exclude-newer-span = "P3D"
|
||||
|
||||
[manifest]
|
||||
|
|
@ -21,6 +21,7 @@ members = [
|
|||
constraints = [
|
||||
{ name = "aiohttp", specifier = ">=3.14.1,<4.0" },
|
||||
{ name = "packaging", specifier = ">=24.0" },
|
||||
{ name = "soupsieve", specifier = ">=2.8.4" },
|
||||
{ name = "tornado", specifier = ">=6.5.6" },
|
||||
]
|
||||
overrides = [{ name = "packaging", specifier = ">=24.0" }]
|
||||
|
|
@ -7076,11 +7077,11 @@ wheels = [
|
|||
|
||||
[[package]]
|
||||
name = "soupsieve"
|
||||
version = "2.8.3"
|
||||
version = "2.8.4"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/7b/ae/2d9c981590ed9999a0d91755b47fc74f74de286b0f5cee14c9269041e6c4/soupsieve-2.8.3.tar.gz", hash = "sha256:3267f1eeea4251fb42728b6dfb746edc9acaffc4a45b27e19450b676586e8349", size = 118627, upload-time = "2026-01-20T04:27:02.457Z" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/47/2c/0a5f6f8ee0d5589e48c7640213ed5175d52cf540a06725b628cc1a45d6ce/soupsieve-2.8.4.tar.gz", hash = "sha256:e121fd02e975c695e4e9e8774a5ee35d74714b59307868dcc5319ad2d9e3328e", size = 121110, upload-time = "2026-05-24T13:55:57.154Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/46/2c/1462b1d0a634697ae9e55b3cecdcb64788e8b7d63f54d923fcd0bb140aed/soupsieve-2.8.3-py3-none-any.whl", hash = "sha256:ed64f2ba4eebeab06cc4962affce381647455978ffc1e36bb79a545b91f45a95", size = 37016, upload-time = "2026-01-20T04:27:01.012Z" },
|
||||
{ url = "https://files.pythonhosted.org/packages/5e/f5/0c41cb68dcae6b7de4fac4188a3a9589e21fb31df21ea3a2e888db95e6c9/soupsieve-2.8.4-py3-none-any.whl", hash = "sha256:e7e6b0769c8f51ed59acab6e994b00621096cfb1c640a7509295987388fbaf65", size = 37304, upload-time = "2026-05-24T13:55:55.406Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue