mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
Agent Usage small issues
This commit is contained in:
parent
92d71a3ed4
commit
19504adb9d
5 changed files with 209 additions and 51 deletions
|
|
@ -1,7 +1,7 @@
|
|||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import EntityUsage from "./EntityUsage";
|
||||
import * as networking from "../../../networking";
|
||||
import EntityUsage from "./EntityUsage";
|
||||
|
||||
beforeAll(() => {
|
||||
if (typeof window !== "undefined" && !window.ResizeObserver) {
|
||||
|
|
@ -269,4 +269,45 @@ describe("EntityUsage", () => {
|
|||
expect(screen.getByText("Total Spend")).toBeInTheDocument();
|
||||
expect(screen.getAllByText("0")[0]).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display Model Activity tab for non-agent entity types", async () => {
|
||||
render(<EntityUsage {...defaultProps} entityType="tag" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockTagDailyActivityCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Model Activity")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display Request / Token Consumption tab for agent entity type", async () => {
|
||||
render(<EntityUsage {...defaultProps} entityType="agent" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAgentDailyActivityCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Request / Token Consumption")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display Top Models title for non-agent entity types", async () => {
|
||||
render(<EntityUsage {...defaultProps} entityType="tag" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockTagDailyActivityCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
const topModelsElements = screen.getAllByText("Top Models");
|
||||
expect(topModelsElements.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("should display Top Agents title for agent entity type", async () => {
|
||||
render(<EntityUsage {...defaultProps} entityType="agent" />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAgentDailyActivityCall).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
expect(screen.getByText("Top Agents")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import {
|
||||
BarChart,
|
||||
Card,
|
||||
Title,
|
||||
Text,
|
||||
Grid,
|
||||
Col,
|
||||
DateRangePickerValue,
|
||||
DonutChart,
|
||||
Grid,
|
||||
Subtitle,
|
||||
Tab,
|
||||
TabGroup,
|
||||
Table,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableHeaderCell,
|
||||
TableBody,
|
||||
TableCell,
|
||||
DonutChart,
|
||||
TabPanel,
|
||||
TabGroup,
|
||||
TableHead,
|
||||
TableHeaderCell,
|
||||
TableRow,
|
||||
TabList,
|
||||
Tab,
|
||||
TabPanel,
|
||||
TabPanels,
|
||||
Subtitle,
|
||||
Text,
|
||||
Title,
|
||||
} from "@tremor/react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { ActivityMetrics, processActivityData } from "../../../activity_metrics";
|
||||
import { DailyData, BreakdownMetrics, KeyMetricWithMetadata, EntityMetricWithMetadata, TagUsage } from "../../types";
|
||||
import { UsageExportHeader } from "../../../EntityUsageExport";
|
||||
import type { EntityType } from "../../../EntityUsageExport/types";
|
||||
import {
|
||||
agentDailyActivityCall,
|
||||
customerDailyActivityCall,
|
||||
organizationDailyActivityCall,
|
||||
tagDailyActivityCall,
|
||||
teamDailyActivityCall,
|
||||
customerDailyActivityCall,
|
||||
agentDailyActivityCall,
|
||||
} from "../../../networking";
|
||||
import TopKeyView from "./TopKeyView";
|
||||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { valueFormatterSpend } from "../../utils/value_formatters";
|
||||
import { getProviderLogoAndName } from "../../../provider_info_helpers";
|
||||
import { UsageExportHeader } from "../../../EntityUsageExport";
|
||||
import type { EntityType } from "../../../EntityUsageExport/types";
|
||||
import { BreakdownMetrics, DailyData, EntityMetricWithMetadata, KeyMetricWithMetadata, TagUsage } from "../../types";
|
||||
import { valueFormatterSpend } from "../../utils/value_formatters";
|
||||
import TopKeyView from "./TopKeyView";
|
||||
import TopModelView from "./TopModelView";
|
||||
|
||||
interface EntityMetrics {
|
||||
|
|
@ -385,7 +385,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
|
|||
<TabGroup>
|
||||
<TabList variant="solid" className="mt-1">
|
||||
<Tab>Cost</Tab>
|
||||
<Tab>Model Activity</Tab>
|
||||
<Tab>{entityType === "agent" ? "Request / Token Consumption" : "Model Activity"}</Tab>
|
||||
<Tab>Key Activity</Tab>
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
|
|
@ -583,7 +583,7 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
|
|||
{/* Top Models */}
|
||||
<Col numColSpan={1}>
|
||||
<Card>
|
||||
<Title>Top Models</Title>
|
||||
<Title>{entityType === "agent" ? "Top Agents" : "Top Models"}</Title>
|
||||
<TopModelView topModels={getTopModels()} />
|
||||
</Card>
|
||||
</Col>
|
||||
|
|
@ -661,10 +661,10 @@ const EntityUsage: React.FC<EntityUsageProps> = ({
|
|||
</Grid>
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ActivityMetrics modelMetrics={modelMetrics} />
|
||||
<ActivityMetrics modelMetrics={modelMetrics} hidePromptCachingMetrics={entityType === "agent"} />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<ActivityMetrics modelMetrics={keyMetrics} />
|
||||
<ActivityMetrics modelMetrics={keyMetrics} hidePromptCachingMetrics={entityType === "agent"} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
|
|
|
|||
101
ui/litellm-dashboard/src/components/activity_metrics.test.tsx
Normal file
101
ui/litellm-dashboard/src/components/activity_metrics.test.tsx
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
import { render, screen } from "@testing-library/react";
|
||||
import React from "react";
|
||||
import { beforeAll, describe, expect, it, vi } from "vitest";
|
||||
import { ActivityMetrics } from "./activity_metrics";
|
||||
import { ModelActivityData } from "./UsagePage/types";
|
||||
|
||||
beforeAll(() => {
|
||||
if (typeof window !== "undefined" && !window.ResizeObserver) {
|
||||
window.ResizeObserver = class ResizeObserver {
|
||||
observe() {}
|
||||
unobserve() {}
|
||||
disconnect() {}
|
||||
} as any;
|
||||
}
|
||||
});
|
||||
|
||||
vi.mock("@tremor/react", () => ({
|
||||
Card: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
Grid: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
|
||||
Text: ({ children }: { children: React.ReactNode }) => <span>{children}</span>,
|
||||
Title: ({ children }: { children: React.ReactNode }) => <h2>{children}</h2>,
|
||||
AreaChart: () => <div>AreaChart</div>,
|
||||
BarChart: () => <div>BarChart</div>,
|
||||
}));
|
||||
|
||||
vi.mock("antd", () => {
|
||||
const CollapseComponent = ({ children }: { children: React.ReactNode }) => <div>{children}</div>;
|
||||
CollapseComponent.Panel = ({ children, header }: { children: React.ReactNode; header: React.ReactNode }) => (
|
||||
<div>
|
||||
<div>{header}</div>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
);
|
||||
return {
|
||||
Collapse: CollapseComponent,
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("./UsagePage/utils/value_formatters", () => ({
|
||||
valueFormatter: (value: number) => value.toString(),
|
||||
}));
|
||||
|
||||
vi.mock("./common_components/chartUtils", () => ({
|
||||
CustomTooltip: () => null,
|
||||
CustomLegend: () => <div>Legend</div>,
|
||||
}));
|
||||
|
||||
vi.mock("@/utils/dataUtils", () => ({
|
||||
formatNumberWithCommas: (value: number, decimals?: number) => {
|
||||
return value.toFixed(decimals || 0);
|
||||
},
|
||||
}));
|
||||
|
||||
describe("ActivityMetrics", () => {
|
||||
const mockModelMetrics: Record<string, ModelActivityData> = {
|
||||
"gpt-4": {
|
||||
label: "GPT-4",
|
||||
total_requests: 100,
|
||||
total_successful_requests: 95,
|
||||
total_failed_requests: 5,
|
||||
total_tokens: 50000,
|
||||
prompt_tokens: 30000,
|
||||
completion_tokens: 20000,
|
||||
total_spend: 100.5,
|
||||
total_cache_read_input_tokens: 1000,
|
||||
total_cache_creation_input_tokens: 500,
|
||||
top_api_keys: [],
|
||||
daily_data: [
|
||||
{
|
||||
date: "2025-01-01",
|
||||
metrics: {
|
||||
prompt_tokens: 30000,
|
||||
completion_tokens: 20000,
|
||||
total_tokens: 50000,
|
||||
api_requests: 100,
|
||||
spend: 100.5,
|
||||
successful_requests: 95,
|
||||
failed_requests: 5,
|
||||
cache_read_input_tokens: 1000,
|
||||
cache_creation_input_tokens: 500,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
it("should render", () => {
|
||||
render(<ActivityMetrics modelMetrics={mockModelMetrics} />);
|
||||
expect(screen.getByText("Overall Usage")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should display prompt caching metrics when hidePromptCachingMetrics is false", () => {
|
||||
render(<ActivityMetrics modelMetrics={mockModelMetrics} hidePromptCachingMetrics={false} />);
|
||||
expect(screen.getByText("Prompt Caching Metrics")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("should hide prompt caching metrics when hidePromptCachingMetrics is true", () => {
|
||||
render(<ActivityMetrics modelMetrics={mockModelMetrics} hidePromptCachingMetrics={true} />);
|
||||
expect(screen.queryByText("Prompt Caching Metrics")).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
|
@ -6,12 +6,22 @@ import { Collapse } from "antd";
|
|||
import { formatNumberWithCommas } from "@/utils/dataUtils";
|
||||
import { valueFormatter } from "./UsagePage/utils/value_formatters";
|
||||
import { CustomTooltip, CustomLegend } from "./common_components/chartUtils";
|
||||
import { EntityType } from "./EntityUsageExport/types";
|
||||
|
||||
interface ActivityMetricsProps {
|
||||
modelMetrics: Record<string, ModelActivityData>;
|
||||
hidePromptCachingMetrics?: boolean;
|
||||
}
|
||||
|
||||
const ModelSection = ({ modelName, metrics }: { modelName: string; metrics: ModelActivityData }) => {
|
||||
const ModelSection = ({
|
||||
modelName,
|
||||
metrics,
|
||||
hidePromptCachingMetrics = false,
|
||||
}: {
|
||||
modelName: string;
|
||||
metrics: ModelActivityData;
|
||||
hidePromptCachingMetrics?: boolean;
|
||||
}) => {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
{/* Summary Cards */}
|
||||
|
|
@ -138,35 +148,37 @@ const ModelSection = ({ modelName, metrics }: { modelName: string; metrics: Mode
|
|||
/>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title>Prompt Caching Metrics</Title>
|
||||
<CustomLegend
|
||||
{!hidePromptCachingMetrics && (
|
||||
<Card>
|
||||
<div className="flex justify-between items-center">
|
||||
<Title>Prompt Caching Metrics</Title>
|
||||
<CustomLegend
|
||||
categories={["metrics.cache_read_input_tokens", "metrics.cache_creation_input_tokens"]}
|
||||
colors={["cyan", "purple"]}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Text>Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens</Text>
|
||||
<Text>Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens</Text>
|
||||
</div>
|
||||
<AreaChart
|
||||
className="mt-4"
|
||||
data={metrics.daily_data}
|
||||
index="date"
|
||||
categories={["metrics.cache_read_input_tokens", "metrics.cache_creation_input_tokens"]}
|
||||
colors={["cyan", "purple"]}
|
||||
valueFormatter={valueFormatter}
|
||||
customTooltip={CustomTooltip}
|
||||
showLegend={false}
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<Text>Cache Read: {metrics.total_cache_read_input_tokens?.toLocaleString() || 0} tokens</Text>
|
||||
<Text>Cache Creation: {metrics.total_cache_creation_input_tokens?.toLocaleString() || 0} tokens</Text>
|
||||
</div>
|
||||
<AreaChart
|
||||
className="mt-4"
|
||||
data={metrics.daily_data}
|
||||
index="date"
|
||||
categories={["metrics.cache_read_input_tokens", "metrics.cache_creation_input_tokens"]}
|
||||
colors={["cyan", "purple"]}
|
||||
valueFormatter={valueFormatter}
|
||||
customTooltip={CustomTooltip}
|
||||
showLegend={false}
|
||||
/>
|
||||
</Card>
|
||||
</Card>
|
||||
)}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics }) => {
|
||||
export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics, hidePromptCachingMetrics = false }) => {
|
||||
const modelNames = Object.keys(modelMetrics).sort((a, b) => {
|
||||
if (a === "") return 1;
|
||||
if (b === "") return -1;
|
||||
|
|
@ -314,7 +326,11 @@ export const ActivityMetrics: React.FC<ActivityMetricsProps> = ({ modelMetrics }
|
|||
</div>
|
||||
}
|
||||
>
|
||||
<ModelSection modelName={modelName || "Unknown Model"} metrics={modelMetrics[modelName]} />
|
||||
<ModelSection
|
||||
modelName={modelName || "Unknown Model"}
|
||||
metrics={modelMetrics[modelName]}
|
||||
hidePromptCachingMetrics={hidePromptCachingMetrics}
|
||||
/>
|
||||
</Collapse.Panel>
|
||||
))}
|
||||
</Collapse>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React, { useCallback, useState, useRef, useEffect } from "react";
|
||||
import { DateRangePickerValue, Text, Button } from "@tremor/react";
|
||||
import { CalendarOutlined, ClockCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, DateRangePickerValue, Text } from "@tremor/react";
|
||||
import moment from "moment";
|
||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
interface AdvancedDatePickerProps {
|
||||
value: DateRangePickerValue;
|
||||
|
|
@ -299,7 +299,7 @@ const AdvancedDatePicker: React.FC<AdvancedDatePickerProps> = ({
|
|||
|
||||
{/* Dropdown panel */}
|
||||
{isOpen && (
|
||||
<div className="absolute top-full left-0 z-[9999] min-w-[600px] mt-1 bg-white border border-gray-200 rounded-lg shadow-xl">
|
||||
<div className="absolute top-full right-0 z-[9999] min-w-[600px] mt-1 bg-white border border-gray-200 rounded-lg shadow-xl">
|
||||
<div className="flex">
|
||||
{/* Left side - Relative time options */}
|
||||
<div className="w-1/2 border-r border-gray-200">
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue