mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-06 08:16:43 +00:00
/global/activity/cache_hits now returns an error_breakdown: failed spend logs bucketed per call_type by error code and error class, read from metadata->error_information. Clicking a red failed-requests segment on the cache activity chart opens a per-code bar chart; hovering a bar lists the error classes behind that code.
169 lines
6.4 KiB
Python
169 lines
6.4 KiB
Python
import asyncio
|
|
import json
|
|
from collections.abc import Sequence
|
|
from datetime import datetime
|
|
from typing import TYPE_CHECKING, Final
|
|
|
|
from pydantic import BaseModel, TypeAdapter
|
|
|
|
if TYPE_CHECKING:
|
|
from litellm.proxy.utils import PrismaClient
|
|
|
|
UNKNOWN_CALL_TYPE: Final = "Unknown"
|
|
|
|
|
|
class CacheActivityGroup(BaseModel):
|
|
call_type: str
|
|
api_requests: int
|
|
cache_hits: int
|
|
failed_requests: int
|
|
cached_completion_tokens: int
|
|
generated_completion_tokens: int
|
|
|
|
|
|
class CacheActivityTotals(BaseModel):
|
|
api_requests: int
|
|
cache_hits: int
|
|
failed_requests: int
|
|
cached_completion_tokens: int
|
|
cache_hit_ratio: float
|
|
|
|
|
|
class CacheActivityFilterOptions(BaseModel):
|
|
key_aliases: list[str]
|
|
models: list[str]
|
|
|
|
|
|
class CacheActivityErrorBucket(BaseModel):
|
|
call_type: str
|
|
error_code: str
|
|
error_class: str
|
|
count: int
|
|
|
|
|
|
class CacheActivityResponse(BaseModel):
|
|
groups: list[CacheActivityGroup]
|
|
totals: CacheActivityTotals
|
|
filter_options: CacheActivityFilterOptions
|
|
error_breakdown: tuple[CacheActivityErrorBucket, ...]
|
|
|
|
|
|
GROUPS_SQL: Final = """
|
|
SELECT
|
|
CASE WHEN sl."call_type" = '' THEN 'Unknown' ELSE sl."call_type" END AS call_type,
|
|
(COUNT(*)
|
|
- SUM(CASE WHEN COALESCE(sl."cache_hit", '') = 'True' THEN 1 ELSE 0 END)
|
|
- SUM(CASE WHEN sl."status" = 'failure' THEN 1 ELSE 0 END))::int AS api_requests,
|
|
SUM(CASE WHEN COALESCE(sl."cache_hit", '') = 'True' THEN 1 ELSE 0 END)::int AS cache_hits,
|
|
SUM(CASE WHEN sl."status" = 'failure' THEN 1 ELSE 0 END)::int AS failed_requests,
|
|
SUM(CASE WHEN COALESCE(sl."cache_hit", '') = 'True' THEN sl."completion_tokens" ELSE 0 END)::int
|
|
AS cached_completion_tokens,
|
|
SUM(CASE WHEN COALESCE(sl."cache_hit", '') != 'True' THEN sl."completion_tokens" ELSE 0 END)::int
|
|
AS generated_completion_tokens
|
|
FROM "LiteLLM_SpendLogs" sl
|
|
LEFT JOIN "LiteLLM_VerificationToken" vt ON sl."api_key" = vt."token"
|
|
WHERE
|
|
sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC')
|
|
AND sl."startTime" < (($2::timestamptz + INTERVAL '1 day') AT TIME ZONE 'UTC')
|
|
AND ($3::jsonb = '[]'::jsonb
|
|
OR COALESCE(vt."key_alias", 'Unnamed Key') IN (SELECT jsonb_array_elements_text($3::jsonb)))
|
|
AND ($4::jsonb = '[]'::jsonb
|
|
OR sl."model" IN (SELECT jsonb_array_elements_text($4::jsonb)))
|
|
GROUP BY 1
|
|
ORDER BY (COUNT(*)) DESC
|
|
"""
|
|
|
|
ERROR_BREAKDOWN_SQL: Final = """
|
|
SELECT
|
|
CASE WHEN sl."call_type" = '' THEN 'Unknown' ELSE sl."call_type" END AS call_type,
|
|
COALESCE(NULLIF(sl."metadata"->'error_information'->>'error_code', ''), 'Unknown') AS error_code,
|
|
COALESCE(NULLIF(sl."metadata"->'error_information'->>'error_class', ''), 'Unknown') AS error_class,
|
|
COUNT(*)::int AS count
|
|
FROM "LiteLLM_SpendLogs" sl
|
|
LEFT JOIN "LiteLLM_VerificationToken" vt ON sl."api_key" = vt."token"
|
|
WHERE
|
|
sl."status" = 'failure'
|
|
AND sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC')
|
|
AND sl."startTime" < (($2::timestamptz + INTERVAL '1 day') AT TIME ZONE 'UTC')
|
|
AND ($3::jsonb = '[]'::jsonb
|
|
OR COALESCE(vt."key_alias", 'Unnamed Key') IN (SELECT jsonb_array_elements_text($3::jsonb)))
|
|
AND ($4::jsonb = '[]'::jsonb
|
|
OR sl."model" IN (SELECT jsonb_array_elements_text($4::jsonb)))
|
|
GROUP BY 1, 2, 3
|
|
ORDER BY (COUNT(*)) DESC
|
|
"""
|
|
|
|
KEY_ALIAS_OPTIONS_SQL: Final = """
|
|
SELECT DISTINCT COALESCE(vt."key_alias", 'Unnamed Key') AS key_alias
|
|
FROM "LiteLLM_SpendLogs" sl
|
|
LEFT JOIN "LiteLLM_VerificationToken" vt ON sl."api_key" = vt."token"
|
|
WHERE
|
|
sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC')
|
|
AND sl."startTime" < (($2::timestamptz + INTERVAL '1 day') AT TIME ZONE 'UTC')
|
|
ORDER BY 1
|
|
"""
|
|
|
|
MODEL_OPTIONS_SQL: Final = """
|
|
SELECT DISTINCT sl."model" AS model
|
|
FROM "LiteLLM_SpendLogs" sl
|
|
WHERE
|
|
sl."startTime" >= ($1::timestamptz AT TIME ZONE 'UTC')
|
|
AND sl."startTime" < (($2::timestamptz + INTERVAL '1 day') AT TIME ZONE 'UTC')
|
|
AND sl."model" != ''
|
|
ORDER BY 1
|
|
"""
|
|
|
|
|
|
class _KeyAliasRow(BaseModel):
|
|
key_alias: str
|
|
|
|
|
|
class _ModelRow(BaseModel):
|
|
model: str
|
|
|
|
|
|
_groups_adapter: Final = TypeAdapter(list[CacheActivityGroup])
|
|
_error_buckets_adapter: Final = TypeAdapter(tuple[CacheActivityErrorBucket, ...])
|
|
_key_alias_rows_adapter: Final = TypeAdapter(list[_KeyAliasRow])
|
|
_model_rows_adapter: Final = TypeAdapter(list[_ModelRow])
|
|
|
|
|
|
def compute_totals(groups: Sequence[CacheActivityGroup]) -> CacheActivityTotals:
|
|
api_requests: Final = sum(group.api_requests for group in groups)
|
|
cache_hits: Final = sum(group.cache_hits for group in groups)
|
|
failed_requests: Final = sum(group.failed_requests for group in groups)
|
|
all_requests: Final = api_requests + cache_hits + failed_requests
|
|
return CacheActivityTotals(
|
|
api_requests=api_requests,
|
|
cache_hits=cache_hits,
|
|
failed_requests=failed_requests,
|
|
cached_completion_tokens=sum(group.cached_completion_tokens for group in groups),
|
|
cache_hit_ratio=(cache_hits / all_requests) * 100 if all_requests > 0 else 0.0,
|
|
)
|
|
|
|
|
|
async def get_cache_activity(
|
|
prisma_client: "PrismaClient",
|
|
start_date: datetime,
|
|
end_date: datetime,
|
|
key_aliases: Sequence[str],
|
|
models: Sequence[str],
|
|
) -> CacheActivityResponse:
|
|
key_aliases_json: Final = json.dumps(list(key_aliases))
|
|
models_json: Final = json.dumps(list(models))
|
|
group_rows, error_rows, key_alias_rows, model_rows = await asyncio.gather(
|
|
prisma_client.db.query_raw(GROUPS_SQL, start_date, end_date, key_aliases_json, models_json),
|
|
prisma_client.db.query_raw(ERROR_BREAKDOWN_SQL, start_date, end_date, key_aliases_json, models_json),
|
|
prisma_client.db.query_raw(KEY_ALIAS_OPTIONS_SQL, start_date, end_date),
|
|
prisma_client.db.query_raw(MODEL_OPTIONS_SQL, start_date, end_date),
|
|
)
|
|
groups: Final = _groups_adapter.validate_python(group_rows or [])
|
|
return CacheActivityResponse(
|
|
groups=groups,
|
|
totals=compute_totals(groups),
|
|
filter_options=CacheActivityFilterOptions(
|
|
key_aliases=[row.key_alias for row in _key_alias_rows_adapter.validate_python(key_alias_rows or [])],
|
|
models=[row.model for row in _model_rows_adapter.validate_python(model_rows or [])],
|
|
),
|
|
error_breakdown=_error_buckets_adapter.validate_python(error_rows or []),
|
|
)
|