feat(cost-optimization): show recent optimized requests on dashboard

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Krrish Dholakia 2026-07-19 03:33:32 +00:00
parent ac1b4b35ce
commit 20cf663a1c
10 changed files with 680 additions and 16 deletions

View file

@ -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),

View file

@ -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,
)

View file

@ -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)

View file

@ -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")),
)

View file

@ -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

View file

@ -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

View file

@ -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<SpendMetrics>): DailyData => ({
const renderWith = (results: DailyData[]) => {
mockUsePaginatedDailyActivity.mockReturnValue({ data: { results }, loading: false, isFetchingMore: false });
mockUseQuery.mockReturnValue({
data: {
logs: [
{
request_id: "req-123456789",
timestamp: "2026-07-13T12:00:00Z",
model: "test-model",
total_tokens: 150,
optimization_type: "both",
spend: 0.02,
savings: 0.14,
original_cost: 0.16,
},
],
total: 1,
page: 1,
page_size: 50,
total_pages: 1,
},
isLoading: false,
isFetching: false,
error: null,
});
return render(<CostOptimizationView accessToken="test-token" userId="u1" userRole="proxy_admin" />);
};
@ -106,4 +135,15 @@ describe("CostOptimizationView", () => {
const slices = JSON.parse(getByTestId("donut-chart").getAttribute("data-slices") ?? "[]");
expect(slices).toEqual([{ driver: "Compression", usd: expect.closeTo(0.04, 5) }]);
});
it("renders recent optimized requests with savings and optimization type", () => {
const { getByText } = renderWith([day("2026-07-12", { compression_savings_spend: 0.04 })]);
expect(getByText("Recent Optimized Requests")).toBeInTheDocument();
expect(getByText("req-123456789")).toBeInTheDocument();
expect(getByText("Both")).toBeInTheDocument();
expect(getByText("$0.1600")).toBeInTheDocument();
expect(getByText("$0.0200")).toBeInTheDocument();
expect(getByText("$0.1400")).toBeInTheDocument();
});
});

View file

@ -2,11 +2,17 @@
import React, { useMemo, useState } from "react";
import { PiggyBank } from "lucide-react";
import { useQuery } from "@tanstack/react-query";
import { AreaChart, DonutChart } from "@/components/shared/charts";
import AdvancedDatePicker from "@/components/shared/advanced_date_picker";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { userDailyActivityCall } from "@/components/networking";
import {
getCostOptimizationUsageLogs,
type OptimizedRequestLog,
type OptimizedRequestLogsResponse,
userDailyActivityCall,
} from "@/components/networking";
import { DailyData, SpendMetrics } from "@/components/UsagePage/types";
import { formatNumberWithCommas } from "@/utils/dataUtils";
import { all_admin_roles } from "@/utils/roles";
@ -46,6 +52,112 @@ const SummaryCard = ({ label, value, hint }: { label: string; value: string; hin
</Card>
);
const optimizationTypeLabel = (type: OptimizedRequestLog["optimization_type"]): string => {
if (type === "both") return "Both";
if (type === "caching") return "Caching";
return "Compression";
};
const optimizationTypeClass = (type: OptimizedRequestLog["optimization_type"]): string => {
if (type === "both") return "bg-purple-50 text-purple-700 border-purple-200";
if (type === "caching") return "bg-green-50 text-green-700 border-green-200";
return "bg-blue-50 text-blue-700 border-blue-200";
};
const optimizedLogsDescription = (
loading: boolean,
fetching: boolean,
error: unknown,
data: OptimizedRequestLogsResponse | undefined,
): string => {
if (loading || fetching) return "Loading...";
if (error) return "Failed to load optimized requests";
if (data?.total) return `Showing ${data.logs.length} of ${data.total} requests`;
return "No optimized requests for this period";
};
const OptimizedRequestsTable = ({
data,
logsPage,
onPrevious,
onNext,
}: {
data: OptimizedRequestLogsResponse;
logsPage: number;
onPrevious: () => void;
onNext: () => void;
}) => (
<>
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="border-b text-left text-xs text-muted-foreground">
<th className="px-3 py-3 font-medium">Request ID</th>
<th className="px-3 py-3 font-medium">Timestamp</th>
<th className="px-3 py-3 font-medium">Model</th>
<th className="px-3 py-3 text-right font-medium">Tokens</th>
<th className="px-3 py-3 font-medium">Type</th>
<th className="px-3 py-3 text-right font-medium">Original Cost</th>
<th className="px-3 py-3 text-right font-medium">Optimized Cost</th>
<th className="px-3 py-3 text-right font-medium">Savings</th>
</tr>
</thead>
<tbody>
{data.logs.map((log) => (
<tr key={log.request_id} className="border-b last:border-0">
<td className="max-w-40 truncate px-3 py-3 font-mono text-xs" title={log.request_id}>
{log.request_id}
</td>
<td className="whitespace-nowrap px-3 py-3 text-muted-foreground">
{new Date(log.timestamp).toLocaleString()}
</td>
<td className="max-w-48 truncate px-3 py-3" title={log.model}>
{log.model}
</td>
<td className="px-3 py-3 text-right">{formatNumberWithCommas(log.total_tokens)}</td>
<td className="px-3 py-3">
<span
className={`inline-flex rounded border px-2 py-0.5 text-xs font-medium ${optimizationTypeClass(log.optimization_type)}`}
>
{optimizationTypeLabel(log.optimization_type)}
</span>
</td>
<td className="px-3 py-3 text-right text-muted-foreground line-through">{usd(log.original_cost)}</td>
<td className="px-3 py-3 text-right">{usd(log.spend)}</td>
<td className="px-3 py-3 text-right font-medium text-emerald-600">{usd(log.savings)}</td>
</tr>
))}
</tbody>
</table>
</div>
{data.total_pages > 1 && (
<div className="mt-4 flex items-center justify-between">
<p className="text-xs text-muted-foreground">
Page {data.page} of {data.total_pages}
</p>
<div className="flex gap-2">
<button
type="button"
className="rounded border px-3 py-1 text-sm disabled:cursor-not-allowed disabled:opacity-50"
disabled={logsPage === 1}
onClick={onPrevious}
>
Previous
</button>
<button
type="button"
className="rounded border px-3 py-1 text-sm disabled:cursor-not-allowed disabled:opacity-50"
disabled={logsPage >= data.total_pages}
onClick={onNext}
>
Next
</button>
</div>
</div>
)}
</>
);
const CostOptimizationView: React.FC<CostOptimizationViewProps> = ({ accessToken, userId, userRole }) => {
const initialFrom = useMemo(() => new Date(new Date().getTime() - THIRTY_DAYS_MS), []);
const initialTo = useMemo(() => new Date(), []);
@ -68,6 +180,32 @@ const CostOptimizationView: React.FC<CostOptimizationViewProps> = ({ accessToken
const cachingTotal = useMemo(() => results.reduce((sum, d) => sum + cachingOf(d.metrics), 0), [results]);
const savedTokensTotal = useMemo(() => results.reduce((sum, d) => sum + savedTokensOf(d.metrics), 0), [results]);
const totalSaved = compressionTotal + cachingTotal;
const [logsPage, setLogsPage] = useState(1);
const startDate = startTime ? startTime.toISOString().slice(0, 10) : "";
const endDate = endTime ? endTime.toISOString().slice(0, 10) : "";
const {
data: optimizedLogsData,
isLoading: optimizedLogsLoading,
isFetching: optimizedLogsFetching,
error: optimizedLogsError,
} = useQuery({
queryKey: ["cost-optimization-usage-logs", startDate, endDate, logsPage],
queryFn: () =>
getCostOptimizationUsageLogs({
accessToken: accessToken!,
startDate,
endDate,
page: logsPage,
pageSize: 50,
}),
enabled: !!accessToken && !!startDate && !!endDate,
});
const logsDescription = optimizedLogsDescription(
optimizedLogsLoading,
optimizedLogsFetching,
optimizedLogsError,
optimizedLogsData,
);
const overTime = useMemo(
() =>
@ -100,7 +238,13 @@ const CostOptimizationView: React.FC<CostOptimizationViewProps> = ({ accessToken
Money saved by prompt compression and prompt caching across your requests
</p>
</div>
<AdvancedDatePicker value={dateValue} onValueChange={(v) => setDateValue(v)} />
<AdvancedDatePicker
value={dateValue}
onValueChange={(v) => {
setDateValue(v);
setLogsPage(1);
}}
/>
</div>
<div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
@ -150,6 +294,32 @@ const CostOptimizationView: React.FC<CostOptimizationViewProps> = ({ accessToken
</CardContent>
</Card>
</div>
<Card>
<CardHeader>
<CardTitle>Recent Optimized Requests</CardTitle>
<p className="text-sm text-muted-foreground">{logsDescription}</p>
</CardHeader>
<CardContent>
{optimizedLogsLoading && (
<div className="py-10 text-center text-sm text-muted-foreground">Loading optimized requests...</div>
)}
{!optimizedLogsLoading && optimizedLogsError && (
<div className="py-10 text-center text-sm text-red-600">Failed to load optimized requests.</div>
)}
{!optimizedLogsLoading && !optimizedLogsError && optimizedLogsData?.logs.length ? (
<OptimizedRequestsTable
data={optimizedLogsData}
logsPage={logsPage}
onPrevious={() => setLogsPage((page) => Math.max(1, page - 1))}
onNext={() => setLogsPage((page) => Math.min(optimizedLogsData.total_pages, page + 1))}
/>
) : null}
{!optimizedLogsLoading && !optimizedLogsError && !optimizedLogsData?.logs.length && (
<div className="py-10 text-center text-sm text-muted-foreground">No optimized requests to display.</div>
)}
</CardContent>
</Card>
</div>
);
};

View file

@ -1403,6 +1403,55 @@ export const userDailyActivityCall = async (
});
};
export interface OptimizedRequestLog {
request_id: string;
timestamp: string;
model: string;
total_tokens: number;
optimization_type: "compression" | "caching" | "both";
spend: number;
savings: number;
original_cost: number;
compression_savings_spend: number;
prompt_caching_savings_spend: number;
tokens_saved: number;
cache_read_tokens: number;
}
export interface OptimizedRequestLogsResponse {
logs: OptimizedRequestLog[];
total: number;
page: number;
page_size: number;
total_pages: number;
}
export interface CostOptimizationUsageLogsArgs {
accessToken: string;
startDate: string;
endDate: string;
page?: number;
pageSize?: number;
}
export const getCostOptimizationUsageLogs = async ({
accessToken,
startDate,
endDate,
page = 1,
pageSize = 50,
}: CostOptimizationUsageLogsArgs): Promise<OptimizedRequestLogsResponse> => {
return apiClient.get<OptimizedRequestLogsResponse>("/cost_optimization/usage/logs", {
accessToken,
query: {
start_date: startDate,
end_date: endDate,
page,
page_size: pageSize,
},
});
};
export const tagDailyActivityCall = async (
accessToken: string,
startTime: Date,

View file

@ -2474,6 +2474,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/cost_optimization/usage/logs": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Cost Optimization Usage Logs */
get: operations["cost_optimization_usage_logs_cost_optimization_usage_logs_get"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/credentials": {
parameters: {
query?: never;
@ -28573,6 +28590,49 @@ export interface components {
*/
type: "openIdConnect";
};
/** OptimizedRequestLog */
OptimizedRequestLog: {
/** Cache Read Tokens */
cache_read_tokens: number;
/** Compression Savings Spend */
compression_savings_spend: number;
/** Model */
model: string;
/**
* Optimization Type
* @enum {string}
*/
optimization_type: "compression" | "caching" | "both";
/** Original Cost */
original_cost: number;
/** Prompt Caching Savings Spend */
prompt_caching_savings_spend: number;
/** Request Id */
request_id: string;
/** Savings */
savings: number;
/** Spend */
spend: number;
/** Timestamp */
timestamp: string;
/** Tokens Saved */
tokens_saved: number;
/** Total Tokens */
total_tokens: number;
};
/** OptimizedRequestLogsResponse */
OptimizedRequestLogsResponse: {
/** Logs */
logs: components["schemas"]["OptimizedRequestLog"][];
/** Page */
page: number;
/** Page Size */
page_size: number;
/** Total */
total: number;
/** Total Pages */
total_pages: number;
};
/** OrgMember */
OrgMember: {
/**
@ -37642,6 +37702,40 @@ export interface operations {
};
};
};
cost_optimization_usage_logs_cost_optimization_usage_logs_get: {
parameters: {
query?: {
page?: number;
page_size?: number;
start_date?: string | null;
end_date?: string | null;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successful Response */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["OptimizedRequestLogsResponse"];
};
};
/** @description Validation Error */
422: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
get_credentials_credentials_get: {
parameters: {
query?: never;