Agent Usage small issues

This commit is contained in:
yuneng-jiang 2025-12-12 12:52:51 -08:00
parent 92d71a3ed4
commit 19504adb9d
5 changed files with 209 additions and 51 deletions

View file

@ -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();
});
});

View file

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

View 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();
});
});

View file

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

View file

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