diff --git a/litellm/proxy/db/db_spend_update_writer.py b/litellm/proxy/db/db_spend_update_writer.py
index 2262141f426..f582dfe832f 100644
--- a/litellm/proxy/db/db_spend_update_writer.py
+++ b/litellm/proxy/db/db_spend_update_writer.py
@@ -29,8 +29,8 @@ import litellm
from litellm._logging import verbose_proxy_logger
from litellm.caching import RedisCache
from litellm.constants import (
- DB_SPEND_UPDATE_JOB_NAME,
DB_DAILY_TAG_SPEND_UPDATE_JOB_NAME,
+ DB_SPEND_UPDATE_JOB_NAME,
)
from litellm.litellm_core_utils.safe_json_loads import safe_json_loads
from litellm.proxy._types import (
@@ -62,6 +62,7 @@ from litellm.proxy.route_llm_request import ROUTE_ENDPOINT_MAPPING
from litellm.proxy.spend_tracking.compression_savings import (
extract_compression_saved_tokens,
)
+from litellm.proxy.spend_tracking.optimization import extract_cache_read_tokens
from litellm.proxy.spend_tracking.savings import compute_savings_spend
from litellm.proxy.spend_tracking.spend_log_error_logger import spend_log_error
@@ -72,18 +73,6 @@ else:
ProxyLogging = Any
-def _extract_cache_read_tokens(usage_obj: dict) -> int:
- """
- Anthropic: top-level cache_read_input_tokens field.
- OpenAI-compatible (moonshotai, openai, deepseek, etc.): prompt_tokens_details.cached_tokens.
- """
- explicit = usage_obj.get("cache_read_input_tokens", 0) or 0
- if explicit:
- return int(explicit)
- details = usage_obj.get("prompt_tokens_details") or {}
- return int(details.get("cached_tokens", 0) or 0)
-
-
def _extract_cache_creation_tokens(usage_obj: dict) -> int:
"""
Anthropic: top-level cache_creation_input_tokens field.
@@ -1854,7 +1843,7 @@ class DBSpendUpdateWriter:
if call_type:
endpoint = ROUTE_ENDPOINT_MAPPING.get(call_type, None)
- cache_read_input_tokens = _extract_cache_read_tokens(usage_obj)
+ cache_read_input_tokens = extract_cache_read_tokens({"usage_object": usage_obj})
compression_saved_tokens = extract_compression_saved_tokens(_metadata)
savings_spend = compute_savings_spend(
model=payload.get("model", None),
diff --git a/litellm/proxy/management_endpoints/cost_optimization_endpoints.py b/litellm/proxy/management_endpoints/cost_optimization_endpoints.py
new file mode 100644
index 00000000000..44af7822ef0
--- /dev/null
+++ b/litellm/proxy/management_endpoints/cost_optimization_endpoints.py
@@ -0,0 +1,180 @@
+import json
+from collections.abc import Mapping
+from datetime import datetime, timedelta, timezone
+from typing import Awaitable, Callable, Literal, cast
+
+from fastapi import APIRouter, Depends, Query
+
+from litellm.proxy.auth.user_api_key_auth import user_api_key_auth
+from litellm.proxy.spend_tracking.compression_savings import extract_compression_saved_tokens
+from litellm.proxy.spend_tracking.optimization import extract_cache_read_tokens
+from litellm.proxy.spend_tracking.savings import compute_savings_spend
+from litellm.types.proxy.management_endpoints.cost_optimization import (
+ OptimizedRequestLog,
+ OptimizedRequestLogsResponse,
+)
+
+router = APIRouter()
+
+
+def _metadata_dict(value: object) -> Mapping[str, object]:
+ if isinstance(value, dict):
+ return cast(Mapping[str, object], value) # cast-ok: JSON metadata is validated as a mapping above
+ if isinstance(value, str):
+ try:
+ parsed = json.loads(value)
+ return (
+ cast(Mapping[str, object], parsed) # cast-ok: parsed JSON is checked as a dict
+ if isinstance(parsed, dict)
+ else {}
+ )
+ except (TypeError, ValueError):
+ return {}
+ return {}
+
+
+def _nonnegative_int(value: object) -> int:
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
+ return 0
+ return max(int(value), 0)
+
+
+def build_optimized_request_log(row: Mapping[str, object]) -> OptimizedRequestLog | None:
+ metadata = _metadata_dict(row.get("metadata"))
+ cache_read_tokens = extract_cache_read_tokens(metadata)
+ tokens_saved = extract_compression_saved_tokens(metadata)
+ if cache_read_tokens <= 0 and tokens_saved <= 0:
+ return None
+
+ model = str(row.get("model", "") or "")
+ provider_value = row.get("custom_llm_provider")
+ provider = str(provider_value) if provider_value is not None else None
+ savings_spend = compute_savings_spend(
+ model=model,
+ custom_llm_provider=provider,
+ compression_saved_tokens=tokens_saved,
+ cache_read_input_tokens=cache_read_tokens,
+ )
+ compression_savings = float(savings_spend.compression)
+ caching_savings = float(savings_spend.prompt_caching)
+ if tokens_saved > 0 and cache_read_tokens > 0:
+ optimization_type: Literal["compression", "caching", "both"] = "both"
+ elif tokens_saved > 0:
+ optimization_type = "compression"
+ else:
+ optimization_type = "caching"
+
+ spend_value = row.get("spend")
+ spend = float(spend_value) if isinstance(spend_value, (int, float)) else 0.0
+ timestamp = row.get("startTime")
+ if isinstance(timestamp, datetime):
+ timestamp = timestamp.isoformat()
+ else:
+ timestamp = str(timestamp or "")
+
+ return OptimizedRequestLog(
+ request_id=str(row.get("request_id", "")),
+ timestamp=timestamp,
+ model=str(model or ""),
+ total_tokens=_nonnegative_int(row.get("total_tokens")),
+ optimization_type=optimization_type,
+ spend=spend,
+ savings=compression_savings + caching_savings,
+ original_cost=spend + compression_savings + caching_savings,
+ compression_savings_spend=compression_savings,
+ prompt_caching_savings_spend=caching_savings,
+ tokens_saved=tokens_saved,
+ cache_read_tokens=cache_read_tokens,
+ )
+
+
+def _cache_read_filter_sql() -> str:
+ candidates = (
+ "(metadata->'usage_object'->>'cache_read_input_tokens')",
+ "(metadata->'usage_object'->'prompt_tokens_details'->>'cached_tokens')",
+ "(metadata->'additional_usage_values'->>'cache_read_input_tokens')",
+ "(metadata->'additional_usage_values'->'prompt_tokens_details'->>'cached_tokens')",
+ )
+ return " OR ".join(f"({candidate}) ~ '^[0-9]+$' AND ({candidate})::bigint > 0" for candidate in candidates)
+
+
+def _optimized_where_sql() -> str:
+ return f"""(
+ (metadata->'compression_savings' IS NOT NULL
+ AND metadata->'compression_savings' <> 'null'::jsonb)
+ OR {_cache_read_filter_sql()}
+ )"""
+
+
+def _parse_date(value: str | None, *, end: bool = False) -> datetime | None:
+ if not value:
+ return None
+ parsed = datetime.strptime(value, "%Y-%m-%d %H:%M:%S" if " " in value else "%Y-%m-%d")
+ parsed = parsed.replace(tzinfo=timezone.utc)
+ if end and " " not in value:
+ parsed += timedelta(days=1)
+ return parsed
+
+
+@router.get(
+ "/cost_optimization/usage/logs",
+ tags=["Cost Optimization"],
+ dependencies=[Depends(user_api_key_auth)],
+ response_model=OptimizedRequestLogsResponse,
+)
+async def cost_optimization_usage_logs(
+ page: int = Query(default=1, ge=1),
+ page_size: int = Query(default=50, ge=1, le=100),
+ start_date: str | None = Query(default=None),
+ end_date: str | None = Query(default=None),
+) -> OptimizedRequestLogsResponse:
+ from litellm.proxy.proxy_server import prisma_client
+
+ if prisma_client is None:
+ return OptimizedRequestLogsResponse(logs=[], total=0, page=page, page_size=page_size, total_pages=0)
+
+ conditions = [_optimized_where_sql()]
+ params: list[object] = []
+ if start_date:
+ conditions.append('"startTime" >= $%d' % (len(params) + 1))
+ params.append(_parse_date(start_date))
+ if end_date:
+ conditions.append('"startTime" < $%d' % (len(params) + 1))
+ params.append(_parse_date(end_date, end=True))
+ where_sql = " AND ".join(conditions)
+
+ query_raw: Callable[..., Awaitable[object]] = cast( # cast-ok: Prisma exposes query_raw dynamically
+ Callable[..., Awaitable[object]], prisma_client.db.query_raw
+ )
+ total_rows = cast( # cast-ok: Prisma raw query returns rows with the selected columns
+ list[Mapping[str, object]],
+ await query_raw(
+ f'SELECT COUNT(*)::bigint AS total FROM "LiteLLM_SpendLogs" WHERE {where_sql}',
+ *params,
+ ),
+ )
+ total_value = total_rows[0].get("total") if total_rows else 0
+ total = int(total_value) if isinstance(total_value, (int, float)) else 0
+ offset_placeholder = len(params) + 1
+ limit_placeholder = len(params) + 2
+ rows = cast( # cast-ok: Prisma raw query returns rows with the selected columns
+ list[Mapping[str, object]],
+ await query_raw(
+ f"""SELECT request_id, "startTime", model, custom_llm_provider, total_tokens, spend, metadata
+ FROM "LiteLLM_SpendLogs"
+ WHERE {where_sql}
+ ORDER BY "startTime" DESC
+ LIMIT ${limit_placeholder} OFFSET ${offset_placeholder}""",
+ *params,
+ page_size,
+ (page - 1) * page_size,
+ ),
+ )
+ logs = [entry for row in rows if (entry := build_optimized_request_log(row)) is not None]
+ return OptimizedRequestLogsResponse(
+ logs=logs,
+ total=total,
+ page=page,
+ page_size=page_size,
+ total_pages=(total + page_size - 1) // page_size,
+ )
diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py
index 3b40abed19e..6104a3a756d 100644
--- a/litellm/proxy/proxy_server.py
+++ b/litellm/proxy/proxy_server.py
@@ -506,6 +506,9 @@ from litellm.proxy.spend_tracking.budget_reservation import get_budget_window_st
from litellm.proxy.spend_tracking.spend_management_endpoints import (
router as spend_management_router,
)
+from litellm.proxy.management_endpoints.cost_optimization_endpoints import (
+ router as cost_optimization_router,
+)
from litellm.proxy.spend_tracking.spend_tracking_utils import get_logging_payload
from litellm.proxy.types_utils.utils import get_instance_fn
from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import (
@@ -16191,6 +16194,7 @@ app.include_router(ui_sso_router)
app.include_router(organization_router)
app.include_router(customer_router)
app.include_router(spend_management_router)
+app.include_router(cost_optimization_router)
app.include_router(caching_router)
app.include_router(analytics_router)
app.include_router(callback_management_endpoints_router)
diff --git a/litellm/proxy/spend_tracking/optimization.py b/litellm/proxy/spend_tracking/optimization.py
new file mode 100644
index 00000000000..7fe4b0662b4
--- /dev/null
+++ b/litellm/proxy/spend_tracking/optimization.py
@@ -0,0 +1,29 @@
+from collections.abc import Mapping
+from typing import cast
+
+
+def _positive_int(value: object) -> int:
+ if isinstance(value, bool) or not isinstance(value, (int, float)):
+ return 0
+ return max(int(value), 0)
+
+
+def _extract_cache_read_tokens_from_usage(usage_obj: object) -> int:
+ if not isinstance(usage_obj, Mapping):
+ return 0
+ typed_usage = cast(Mapping[str, object], usage_obj) # cast-ok: usage object is checked as a mapping above
+ explicit = _positive_int(typed_usage.get("cache_read_input_tokens"))
+ if explicit:
+ return explicit
+ details = typed_usage.get("prompt_tokens_details")
+ if isinstance(details, Mapping):
+ typed_details = cast(Mapping[str, object], details) # cast-ok: details is checked as a mapping above
+ return _positive_int(typed_details.get("cached_tokens"))
+ return 0
+
+
+def extract_cache_read_tokens(metadata: Mapping[str, object]) -> int:
+ return max(
+ _extract_cache_read_tokens_from_usage(metadata.get("usage_object")),
+ _extract_cache_read_tokens_from_usage(metadata.get("additional_usage_values")),
+ )
diff --git a/litellm/types/proxy/management_endpoints/cost_optimization.py b/litellm/types/proxy/management_endpoints/cost_optimization.py
new file mode 100644
index 00000000000..747a05c6a75
--- /dev/null
+++ b/litellm/types/proxy/management_endpoints/cost_optimization.py
@@ -0,0 +1,26 @@
+from typing import List, Literal
+
+from pydantic import BaseModel
+
+
+class OptimizedRequestLog(BaseModel):
+ request_id: str
+ timestamp: str
+ model: str
+ total_tokens: int
+ optimization_type: Literal["compression", "caching", "both"]
+ spend: float
+ savings: float
+ original_cost: float
+ compression_savings_spend: float
+ prompt_caching_savings_spend: float
+ tokens_saved: int
+ cache_read_tokens: int
+
+
+class OptimizedRequestLogsResponse(BaseModel):
+ logs: List[OptimizedRequestLog]
+ total: int
+ page: int
+ page_size: int
+ total_pages: int
diff --git a/tests/test_litellm/proxy/management_endpoints/test_cost_optimization_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_cost_optimization_endpoints.py
new file mode 100644
index 00000000000..33bdaa81dbc
--- /dev/null
+++ b/tests/test_litellm/proxy/management_endpoints/test_cost_optimization_endpoints.py
@@ -0,0 +1,83 @@
+from collections.abc import Mapping
+from datetime import datetime, timezone
+from unittest.mock import AsyncMock, MagicMock, patch
+
+import pytest
+
+from litellm.proxy.management_endpoints.cost_optimization_endpoints import (
+ build_optimized_request_log,
+ cost_optimization_usage_logs,
+)
+
+
+def _row(metadata: Mapping[str, object], **overrides: object) -> dict[str, object]:
+ return {
+ "request_id": "req-1",
+ "startTime": datetime(2026, 7, 13, 12, 0, tzinfo=timezone.utc),
+ "model": "test-model",
+ "custom_llm_provider": "openai",
+ "total_tokens": 150,
+ "spend": 0.02,
+ "metadata": metadata,
+ **overrides,
+ }
+
+
+def test_build_optimized_request_log_computes_both_savings():
+ row = _row(
+ {
+ "compression_savings": {"tokens_saved": 100},
+ "usage_object": {"cache_read_input_tokens": 50},
+ }
+ )
+ with patch(
+ "litellm.proxy.spend_tracking.savings._input_and_cache_read_cost",
+ return_value=(0.001, 0.0002),
+ ):
+ result = build_optimized_request_log(row)
+
+ assert result is not None
+ assert result.optimization_type == "both"
+ assert result.tokens_saved == 100
+ assert result.cache_read_tokens == 50
+ assert result.compression_savings_spend == 0.1
+ assert result.prompt_caching_savings_spend == 0.04
+ assert result.savings == 0.14
+ assert result.original_cost == 0.16
+
+
+def test_build_optimized_request_log_ignores_unoptimized_row():
+ assert build_optimized_request_log(_row({"usage_object": {"prompt_tokens": 10}})) is None
+
+
+@pytest.mark.asyncio
+async def test_cost_optimization_usage_logs_returns_paginated_entries():
+ db = MagicMock()
+ query_raw_mock = AsyncMock(
+ side_effect=[
+ [{"total": 1}],
+ [_row({"compression_savings": {"tokens_saved": 25}})],
+ ]
+ )
+ db.query_raw = query_raw_mock
+ prisma = MagicMock()
+ prisma.db = db
+ with (
+ patch("litellm.proxy.proxy_server.prisma_client", prisma),
+ patch(
+ "litellm.proxy.spend_tracking.savings._input_and_cache_read_cost",
+ return_value=(0.001, 0.001),
+ ),
+ ):
+ result = await cost_optimization_usage_logs(
+ page=1,
+ page_size=50,
+ start_date="2026-07-01",
+ end_date="2026-07-31",
+ )
+
+ assert result.total == 1
+ assert result.total_pages == 1
+ assert result.logs[0].optimization_type == "compression"
+ assert result.logs[0].tokens_saved == 25
+ assert query_raw_mock.await_count == 2
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx
index 92426d3ce04..8a2ca8e5a05 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-optimization/_components/CostOptimizationView.test.tsx
@@ -4,6 +4,11 @@ import { describe, expect, it, vi } from "vitest";
import type { DailyData, SpendMetrics } from "@/components/UsagePage/types";
const mockUsePaginatedDailyActivity = vi.fn();
+const mockUseQuery = vi.fn();
+
+vi.mock("@tanstack/react-query", () => ({
+ useQuery: (args: unknown) => mockUseQuery(args),
+}));
vi.mock("@/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity", () => ({
usePaginatedDailyActivity: (args: unknown) => mockUsePaginatedDailyActivity(args),
@@ -11,6 +16,7 @@ vi.mock("@/app/(dashboard)/usage/_components/hooks/usePaginatedDailyActivity", (
vi.mock("@/components/networking", () => ({
userDailyActivityCall: vi.fn(),
+ getCostOptimizationUsageLogs: vi.fn(),
}));
vi.mock("@/components/shared/advanced_date_picker", () => ({
@@ -57,6 +63,29 @@ const day = (date: string, metrics: Partial
+ Page {data.page} of {data.total_pages}
+
+
+
+
+
+
+
+ {data.logs.map((log) => (
+ Request ID
+ Timestamp
+ Model
+ Tokens
+ Type
+ Original Cost
+ Optimized Cost
+ Savings
+
+
+ ))}
+
+
+ {log.request_id}
+
+
+ {new Date(log.timestamp).toLocaleString()}
+
+
+ {log.model}
+
+ {formatNumberWithCommas(log.total_tokens)}
+
+
+ {optimizationTypeLabel(log.optimization_type)}
+
+
+ {usd(log.original_cost)}
+ {usd(log.spend)}
+ {usd(log.savings)}
+
{logsDescription}
+