mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(ui): remove Top models by task card from Model Leaderboard (#44502)
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: Ishaan Jaffer <155045088+ishaan-berri@users.noreply.github.com>
This commit is contained in:
parent
9cc15e9320
commit
adb59a7af8
3 changed files with 8 additions and 177 deletions
|
|
@ -20,7 +20,6 @@ vi.mock("recharts", () => ({
|
|||
</div>
|
||||
),
|
||||
CartesianGrid: () => null,
|
||||
Treemap: () => null,
|
||||
XAxis: () => null,
|
||||
YAxis: () => null,
|
||||
}));
|
||||
|
|
@ -45,48 +44,26 @@ const response = {
|
|||
daily_totals: [{ date: "2026-09-28", spend: 2.5, prompt_tokens: 1000, completion_tokens: 2000, requests: 12 }],
|
||||
};
|
||||
|
||||
const taskResponse = {
|
||||
start_date: "2025-09-29",
|
||||
end_date: "2026-09-28",
|
||||
tasks: [
|
||||
{
|
||||
task_type: "code_generation",
|
||||
label: "Code Generation",
|
||||
category: "Code",
|
||||
value: 2.5,
|
||||
share: 100,
|
||||
leader: "fast-chat",
|
||||
provider: "openai",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const mockApi = (tasks: unknown = taskResponse) =>
|
||||
vi
|
||||
.mocked(apiClient.get)
|
||||
.mockImplementation((path: string) =>
|
||||
path === "/model-insights/tasks" ? (tasks as Promise<unknown>) : Promise.resolve(response),
|
||||
);
|
||||
|
||||
describe("ModelInsightsView", () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(apiClient.get).mockReset();
|
||||
mockApi(Promise.resolve(taskResponse));
|
||||
vi.mocked(apiClient.get).mockResolvedValue(response);
|
||||
});
|
||||
|
||||
it("shows the ranking with share and the task legend from the API response", async () => {
|
||||
it("shows the ranking with share from the API response", async () => {
|
||||
render(<ModelInsightsView accessToken="token" />);
|
||||
|
||||
expect(await screen.findByText("fast-chat")).toBeInTheDocument();
|
||||
expect(screen.getByText("by openai")).toBeInTheDocument();
|
||||
expect(await screen.findByText("Code")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("100.0%")).toHaveLength(2);
|
||||
expect(screen.getAllByText("100.0%")).toHaveLength(1);
|
||||
expect(screen.queryByText("Top models by task")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("tab", { name: "tokens" })).toHaveAttribute("aria-selected", "true");
|
||||
expect(screen.getByRole("tab", { name: "log" })).toBeInTheDocument();
|
||||
expect(apiClient.get).toHaveBeenCalledWith("/model-insights", {
|
||||
accessToken: "token",
|
||||
query: { metric: "tokens" },
|
||||
});
|
||||
expect(apiClient.get).not.toHaveBeenCalledWith("/model-insights/tasks", expect.anything());
|
||||
});
|
||||
|
||||
it("refetches with the selected metric so top models are ranked by it", async () => {
|
||||
|
|
@ -103,24 +80,6 @@ describe("ModelInsightsView", () => {
|
|||
);
|
||||
});
|
||||
|
||||
it("does not refetch the task breakdown when the chart metric changes", async () => {
|
||||
render(<ModelInsightsView accessToken="token" />);
|
||||
await screen.findByText("Code");
|
||||
const taskCalls = () =>
|
||||
vi.mocked(apiClient.get).mock.calls.filter(([path]) => path === "/model-insights/tasks").length;
|
||||
const before = taskCalls();
|
||||
|
||||
await userEvent.click(screen.getByRole("tab", { name: "requests" }));
|
||||
await waitFor(() =>
|
||||
expect(apiClient.get).toHaveBeenCalledWith("/model-insights", {
|
||||
accessToken: "token",
|
||||
query: { metric: "requests" },
|
||||
}),
|
||||
);
|
||||
|
||||
expect(taskCalls()).toBe(before);
|
||||
});
|
||||
|
||||
it("shows the API error instead of loading forever", async () => {
|
||||
vi.mocked(apiClient.get).mockRejectedValue(new Error("Only proxy admins can view deployment-wide model insights"));
|
||||
render(<ModelInsightsView accessToken="token" />);
|
||||
|
|
@ -133,11 +92,7 @@ describe("ModelInsightsView", () => {
|
|||
render(<ModelInsightsView accessToken="token" />);
|
||||
await screen.findByText("fast-chat");
|
||||
let resolve: (value: typeof response) => void = () => {};
|
||||
vi.mocked(apiClient.get).mockImplementation((path: string) =>
|
||||
path === "/model-insights/tasks"
|
||||
? Promise.resolve(taskResponse)
|
||||
: new Promise((done) => (resolve = done as typeof resolve)),
|
||||
);
|
||||
vi.mocked(apiClient.get).mockImplementation(() => new Promise((done) => (resolve = done as typeof resolve)));
|
||||
|
||||
await userEvent.click(screen.getByRole("tab", { name: "spend" }));
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
import { Page } from "@/components/shared/Page";
|
||||
import React from "react";
|
||||
import { Bar, BarChart, CartesianGrid, Treemap, XAxis, YAxis } from "recharts";
|
||||
import { ArrowDownRight, ArrowUpRight, BarChart3, Layers, Minus } from "lucide-react";
|
||||
import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts";
|
||||
import { ArrowDownRight, ArrowUpRight, BarChart3, Minus } from "lucide-react";
|
||||
|
||||
import { apiClient } from "@/components/networking";
|
||||
import { extractErrorMessage } from "@/utils/errorUtils";
|
||||
|
|
@ -12,7 +12,6 @@ import { PageHeader, PageHeaderDescription, PageHeaderTitle } from "@/components
|
|||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { ChartConfig, ChartContainer, ChartTooltip, ChartTooltipContent } from "@/components/ui/chart";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import {
|
||||
|
|
@ -22,8 +21,6 @@ import {
|
|||
Granularity,
|
||||
Metric,
|
||||
ModelInsightsResponse,
|
||||
ModelInsightTasksResponse,
|
||||
TaskSummary,
|
||||
modelOrder,
|
||||
rankModels,
|
||||
RankedModel,
|
||||
|
|
@ -41,13 +38,6 @@ const PALETTE = [
|
|||
"#6366f1",
|
||||
"#f97316",
|
||||
];
|
||||
const FALLBACK_COLOR = "#64748b";
|
||||
const CATEGORY_COLORS: Record<string, string> = {
|
||||
General: "#ee8650",
|
||||
Agent: "#7666e4",
|
||||
Code: "#5fb074",
|
||||
Data: "#3b82f6",
|
||||
};
|
||||
const SCALES = ["linear", "log"] as const;
|
||||
const GRANULARITIES = ["day", "week"] as const;
|
||||
const GRANULARITY_LABELS: Record<Granularity, string> = { day: "Daily", week: "Weekly" };
|
||||
|
|
@ -90,37 +80,11 @@ const RankingRow = ({ model, rank }: { model: RankedModel; rank: number }) => (
|
|||
</li>
|
||||
);
|
||||
|
||||
type TileProps = TaskSummary & { x: number; y: number; width: number; height: number; index: number };
|
||||
|
||||
const TaskTileContent = ({ x, y, width, height, category, label, leader }: TileProps) => {
|
||||
if (width <= 0 || height <= 0) return null;
|
||||
const color = CATEGORY_COLORS[category] ?? FALLBACK_COLOR;
|
||||
const fits = width > 90 && height > 44;
|
||||
return (
|
||||
<g>
|
||||
<rect x={x} y={y} width={width} height={height} fill={color} stroke="#fff" strokeWidth={2} />
|
||||
{fits && (
|
||||
<>
|
||||
<text x={x + 12} y={y + 26} fill="#fff" fontSize={16} fontWeight={500}>
|
||||
{label}
|
||||
</text>
|
||||
<text x={x + 12} y={y + 46} fill="#ffffffcc" fontSize={12}>
|
||||
{leader}
|
||||
</text>
|
||||
</>
|
||||
)}
|
||||
</g>
|
||||
);
|
||||
};
|
||||
|
||||
export default function ModelInsightsView({ accessToken }: { accessToken: string | null }) {
|
||||
const [loaded, setLoaded] = React.useState<{ metric: Metric; response: ModelInsightsResponse } | null>(null);
|
||||
const [metric, setMetric] = React.useState<Metric>("tokens");
|
||||
const [scale, setScale] = React.useState<Scale>("linear");
|
||||
const [granularity, setGranularity] = React.useState<Granularity>("day");
|
||||
const [taskMetric, setTaskMetric] = React.useState<Metric>("spend");
|
||||
const [taskData, setTaskData] = React.useState<ModelInsightTasksResponse | null>(null);
|
||||
const [taskError, setTaskError] = React.useState<string | null>(null);
|
||||
const [error, setError] = React.useState<string | null>(null);
|
||||
|
||||
React.useEffect(() => {
|
||||
|
|
@ -141,24 +105,6 @@ export default function ModelInsightsView({ accessToken }: { accessToken: string
|
|||
};
|
||||
}, [accessToken, metric]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!accessToken) return;
|
||||
let cancelled = false;
|
||||
apiClient
|
||||
.get<ModelInsightTasksResponse>("/model-insights/tasks", { accessToken, query: { metric: taskMetric } })
|
||||
.then((response) => {
|
||||
if (cancelled) return;
|
||||
setTaskError(null);
|
||||
setTaskData(response);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!cancelled) setTaskError(extractErrorMessage(err));
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [accessToken, taskMetric]);
|
||||
|
||||
const data = loaded?.response ?? null;
|
||||
const shown = loaded?.metric ?? metric;
|
||||
const isStale = loaded !== null && loaded.metric !== metric;
|
||||
|
|
@ -176,15 +122,6 @@ export default function ModelInsightsView({ accessToken }: { accessToken: string
|
|||
() => (data ? rankModels(data.top_models, data.daily, shown, range) : []),
|
||||
[data, shown, range],
|
||||
);
|
||||
const tiles = React.useMemo(() => taskData?.tasks ?? [], [taskData]);
|
||||
const categoryShares = React.useMemo(
|
||||
() =>
|
||||
[...new Set(tiles.map((tile) => tile.category))].map((category) => ({
|
||||
category,
|
||||
share: tiles.filter((tile) => tile.category === category).reduce((sum, tile) => sum + tile.share, 0),
|
||||
})),
|
||||
[tiles],
|
||||
);
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
|
|
@ -317,57 +254,6 @@ export default function ModelInsightsView({ accessToken }: { accessToken: string
|
|||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader className="flex-row items-start justify-between space-y-0">
|
||||
<div>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Layers className="size-5" /> Top models by task
|
||||
</CardTitle>
|
||||
<CardDescription>
|
||||
Each task's share of {METRIC_LABELS[taskMetric]}, labelled with its leading model
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Select value={taskMetric} onValueChange={(value) => setTaskMetric(value as Metric)}>
|
||||
<SelectTrigger className="w-44" aria-label="Task metric">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="spend">Share of spend</SelectItem>
|
||||
<SelectItem value="requests">Share of requests</SelectItem>
|
||||
<SelectItem value="tokens">Share of tokens</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
{taskError && (
|
||||
<Alert variant="destructive">
|
||||
<AlertTitle>Could not load tasks</AlertTitle>
|
||||
<AlertDescription>{taskError}</AlertDescription>
|
||||
</Alert>
|
||||
)}
|
||||
<ChartContainer config={{}} className="h-[360px] w-full aspect-auto">
|
||||
<Treemap
|
||||
data={tiles.map((tile) => ({ ...tile, name: tile.task_type }))}
|
||||
dataKey="value"
|
||||
isAnimationActive={false}
|
||||
content={<TaskTileContent {...({} as TileProps)} />}
|
||||
/>
|
||||
</ChartContainer>
|
||||
<ul className="flex flex-wrap gap-x-6 gap-y-2">
|
||||
{categoryShares.map(({ category, share }) => (
|
||||
<li key={category} className="flex items-center gap-2 text-sm">
|
||||
<span
|
||||
className="size-3 rounded-full"
|
||||
style={{ backgroundColor: CATEGORY_COLORS[category] ?? FALLBACK_COLOR }}
|
||||
/>
|
||||
<span className="text-muted-foreground">{category}</span>
|
||||
<span className="font-medium tabular-nums">{share.toFixed(1)}%</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Cost per session</CardTitle>
|
||||
|
|
|
|||
|
|
@ -21,16 +21,6 @@ export type ModelInsightsResponse = {
|
|||
daily_totals: DailyTotal[];
|
||||
top_models: ModelMetric[];
|
||||
};
|
||||
export type TaskSummary = {
|
||||
task_type: string;
|
||||
label: string;
|
||||
category: string;
|
||||
value: number;
|
||||
share: number;
|
||||
leader: string;
|
||||
provider: string;
|
||||
};
|
||||
export type ModelInsightTasksResponse = { start_date: string; end_date: string; tasks: TaskSummary[] };
|
||||
|
||||
export type RankedModel = { model_group: string; provider: string; share: number; delta: number };
|
||||
export type Granularity = "day" | "week";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue