diff --git a/ui/litellm-dashboard/src/components/ToolPolicies.tsx b/ui/litellm-dashboard/src/components/ToolPolicies.tsx index b02840a49ed..a9e3432bc0d 100644 --- a/ui/litellm-dashboard/src/components/ToolPolicies.tsx +++ b/ui/litellm-dashboard/src/components/ToolPolicies.tsx @@ -37,7 +37,7 @@ function getTrendSubtitle(newToday: number, newYesterday: number): string | unde return `${diff} since yesterday`; } -type SortField = "tool_name" | "call_policy" | "team_id" | "key_alias" | "created_at" | "call_count"; +type SortField = "tool_name" | "call_policy" | "team_id" | "key_alias" | "created_at" | "call_count" | "agent_id"; interface FilterValues { [key: string]: string; @@ -135,6 +135,10 @@ export const ToolPolicies: React.FC = ({ accessToken, onSelec label: v as string, value: v as string, })); + const agentIdOptions = Array.from(new Set(tools.map((t) => t.agent_id).filter(Boolean))).map((v) => ({ + label: v as string, + value: v as string, + })); const filterOptions: FilterOption[] = [ { @@ -152,6 +156,11 @@ export const ToolPolicies: React.FC = ({ accessToken, onSelec label: "Key Name", options: keyAliasOptions, }, + { + name: "Agent ID", + label: "Agent ID", + options: agentIdOptions, + }, ]; // Derived counts for summary cards and "Needs Review" (UTC today/yesterday) @@ -207,12 +216,14 @@ export const ToolPolicies: React.FC = ({ accessToken, onSelec (t.team_id ?? "").toLowerCase().includes(q) || (t.key_alias ?? "").toLowerCase().includes(q) || (t.key_hash ?? "").toLowerCase().includes(q) || + (t.agent_id ?? "").toLowerCase().includes(q) || t.call_policy.toLowerCase().includes(q); if (!matchesSearch) return false; } if (activeFilters["Policy"] && t.call_policy !== activeFilters["Policy"]) return false; if (activeFilters["Team Name"] && t.team_id !== activeFilters["Team Name"]) return false; if (activeFilters["Key Name"] && t.key_alias !== activeFilters["Key Name"]) return false; + if (activeFilters["Agent ID"] && t.agent_id !== activeFilters["Agent ID"]) return false; return true; }); @@ -423,6 +434,9 @@ export const ToolPolicies: React.FC = ({ accessToken, onSelec + + + @@ -436,13 +450,13 @@ export const ToolPolicies: React.FC = ({ accessToken, onSelec {loading ? ( - + Loading tools… ) : paginated.length === 0 ? ( - + No tools discovered yet. Make a chat completion that returns tool_calls to start auto-discovery. @@ -476,6 +490,13 @@ export const ToolPolicies: React.FC = ({ accessToken, onSelec {(tool.call_count ?? 0).toLocaleString()} + + + + {tool.agent_id || "current weather agent"} + + + {tool.team_id ?? "-"} diff --git a/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/EntityUsage.tsx b/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/EntityUsage.tsx index a106910cff7..a0536c5e518 100644 --- a/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/EntityUsage.tsx +++ b/ui/litellm-dashboard/src/components/UsagePage/components/EntityUsage/EntityUsage.tsx @@ -27,7 +27,6 @@ import { ActivityMetrics, processActivityData } from "../../../activity_metrics" import { UsageExportHeader } from "../../../EntityUsageExport"; import type { EntityType } from "../../../EntityUsageExport/types"; import { - agentDailyActivityCall, customerDailyActivityCall, organizationDailyActivityCall, tagDailyActivityCall, @@ -71,6 +70,220 @@ interface EntitySpendData { }; } +/** Stub data for agent usage until /agent/daily/activity is implemented. */ +const AGENT_USAGE_STUB: EntitySpendData = (() => { + const emptyMetrics = { + spend: 0, + prompt_tokens: 0, + completion_tokens: 0, + total_tokens: 0, + api_requests: 0, + successful_requests: 0, + failed_requests: 0, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }; + const makeMetrics = (overrides: Partial) => ({ ...emptyMetrics, ...overrides }); + const days = 7; + const results: ExtendedDailyData[] = []; + let totalSpend = 0; + let totalRequests = 0; + let totalSuccessful = 0; + let totalFailed = 0; + let totalTokens = 0; + + for (let d = days - 1; d >= 0; d--) { + const date = new Date(); + date.setDate(date.getDate() - d); + const dateStr = date.toISOString().slice(0, 10); + const daySpend = 12 + d * 8 + (d % 3) * 5; + const dayRequests = 800 + d * 120 + (d % 2) * 200; + const daySuccessful = Math.floor(dayRequests * 0.94); + const dayFailed = dayRequests - daySuccessful; + const dayTokens = 45000 + d * 8000 + (d % 4) * 5000; + + totalSpend += daySpend; + totalRequests += dayRequests; + totalSuccessful += daySuccessful; + totalFailed += dayFailed; + totalTokens += dayTokens; + + const entitySpend1 = Math.round(daySpend * 0.4); + const entitySpend2 = Math.round(daySpend * 0.35); + const entitySpend3 = Math.round(daySpend * 0.25); + + results.push({ + date: dateStr, + metrics: makeMetrics({ + spend: daySpend, + api_requests: dayRequests, + successful_requests: daySuccessful, + failed_requests: dayFailed, + total_tokens: dayTokens, + prompt_tokens: Math.floor(dayTokens * 0.6), + completion_tokens: Math.floor(dayTokens * 0.4), + }), + breakdown: { + model_groups: {}, + mcp_servers: {}, + entities: { + "agent-support-1": { + metrics: makeMetrics({ + spend: entitySpend1, + api_requests: Math.floor(dayRequests * 0.45), + successful_requests: Math.floor(daySuccessful * 0.45), + failed_requests: Math.floor(dayFailed * 0.5), + total_tokens: Math.floor(dayTokens * 0.42), + prompt_tokens: Math.floor(dayTokens * 0.25), + completion_tokens: Math.floor(dayTokens * 0.17), + }), + metadata: { alias: "Support Agent" }, + api_key_breakdown: {}, + }, + "agent-sales-1": { + metrics: makeMetrics({ + spend: entitySpend2, + api_requests: Math.floor(dayRequests * 0.35), + successful_requests: Math.floor(daySuccessful * 0.35), + failed_requests: Math.floor(dayFailed * 0.3), + total_tokens: Math.floor(dayTokens * 0.35), + prompt_tokens: Math.floor(dayTokens * 0.21), + completion_tokens: Math.floor(dayTokens * 0.14), + }), + metadata: { alias: "Sales Bot" }, + api_key_breakdown: {}, + }, + "agent-code-1": { + metrics: makeMetrics({ + spend: entitySpend3, + api_requests: Math.floor(dayRequests * 0.2), + successful_requests: Math.floor(daySuccessful * 0.2), + failed_requests: Math.floor(dayFailed * 0.2), + total_tokens: Math.floor(dayTokens * 0.23), + prompt_tokens: Math.floor(dayTokens * 0.14), + completion_tokens: Math.floor(dayTokens * 0.09), + }), + metadata: { alias: "Code Reviewer" }, + api_key_breakdown: {}, + }, + }, + models: { + "gpt-4o": { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.55), + api_requests: Math.floor(dayRequests * 0.5), + successful_requests: Math.floor(daySuccessful * 0.5), + failed_requests: Math.floor(dayFailed * 0.5), + total_tokens: Math.floor(dayTokens * 0.5), + prompt_tokens: Math.floor(dayTokens * 0.3), + completion_tokens: Math.floor(dayTokens * 0.2), + }), + metadata: {}, + api_key_breakdown: {}, + }, + "claude-3-5-sonnet-20241022": { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.35), + api_requests: Math.floor(dayRequests * 0.35), + successful_requests: Math.floor(daySuccessful * 0.35), + failed_requests: Math.floor(dayFailed * 0.35), + total_tokens: Math.floor(dayTokens * 0.35), + prompt_tokens: Math.floor(dayTokens * 0.21), + completion_tokens: Math.floor(dayTokens * 0.14), + }), + metadata: {}, + api_key_breakdown: {}, + }, + "gpt-4o-mini": { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.1), + api_requests: Math.floor(dayRequests * 0.15), + successful_requests: Math.floor(daySuccessful * 0.15), + failed_requests: Math.floor(dayFailed * 0.15), + total_tokens: Math.floor(dayTokens * 0.15), + prompt_tokens: Math.floor(dayTokens * 0.09), + completion_tokens: Math.floor(dayTokens * 0.06), + }), + metadata: {}, + api_key_breakdown: {}, + }, + }, + providers: { + openai: { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.65), + api_requests: Math.floor(dayRequests * 0.65), + successful_requests: Math.floor(daySuccessful * 0.65), + failed_requests: Math.floor(dayFailed * 0.65), + total_tokens: Math.floor(dayTokens * 0.65), + prompt_tokens: Math.floor(dayTokens * 0.39), + completion_tokens: Math.floor(dayTokens * 0.26), + }), + metadata: {}, + api_key_breakdown: {}, + }, + anthropic: { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.35), + api_requests: Math.floor(dayRequests * 0.35), + successful_requests: Math.floor(daySuccessful * 0.35), + failed_requests: Math.floor(dayFailed * 0.35), + total_tokens: Math.floor(dayTokens * 0.35), + prompt_tokens: Math.floor(dayTokens * 0.21), + completion_tokens: Math.floor(dayTokens * 0.14), + }), + metadata: {}, + api_key_breakdown: {}, + }, + }, + api_keys: { + "sk-agent-dashboard-1": { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.5), + api_requests: Math.floor(dayRequests * 0.5), + successful_requests: Math.floor(daySuccessful * 0.5), + failed_requests: Math.floor(dayFailed * 0.5), + total_tokens: Math.floor(dayTokens * 0.5), + }), + metadata: { key_alias: "Dashboard Key", team_id: null }, + }, + "sk-agent-integration-2": { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.3), + api_requests: Math.floor(dayRequests * 0.3), + successful_requests: Math.floor(daySuccessful * 0.3), + failed_requests: Math.floor(dayFailed * 0.3), + total_tokens: Math.floor(dayTokens * 0.3), + }), + metadata: { key_alias: "Integration Key", team_id: null }, + }, + "sk-agent-dev-3": { + metrics: makeMetrics({ + spend: Math.round(daySpend * 0.2), + api_requests: Math.floor(dayRequests * 0.2), + successful_requests: Math.floor(daySuccessful * 0.2), + failed_requests: Math.floor(dayFailed * 0.2), + total_tokens: Math.floor(dayTokens * 0.2), + }), + metadata: { key_alias: "Dev Key", team_id: null }, + }, + }, + }, + }); + } + + return { + results, + metadata: { + total_spend: totalSpend, + total_api_requests: totalRequests, + total_successful_requests: totalSuccessful, + total_failed_requests: totalFailed, + total_tokens: totalTokens, + }, + }; +})(); + export interface EntityList { label: string; value: string; @@ -149,14 +362,8 @@ const EntityUsage: React.FC = ({ accessToken, entityType, enti ); setSpendData(data); } else if (entityType === "agent") { - const data = await agentDailyActivityCall( - accessToken, - startTime, - endTime, - 1, - selectedTags.length > 0 ? selectedTags : null, - ); - setSpendData(data); + setSpendData(AGENT_USAGE_STUB); + return; } else if (entityType === "user") { const data = await userDailyActivityCall( accessToken, diff --git a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx index 10fbfa4615a..5d1ee64558b 100644 --- a/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx +++ b/ui/litellm-dashboard/src/components/agents/add_agent_form.tsx @@ -1,5 +1,5 @@ import React, { useState, useEffect } from "react"; -import { Modal, Form, message, Select, Input, Steps, Radio, Tag, Divider } from "antd"; +import { Modal, Form, message, Select, Input, InputNumber, Steps, Radio, Tag, Divider, Switch } from "antd"; import { Button } from "@tremor/react"; import { CheckCircleFilled, KeyOutlined, RobotOutlined, AppstoreOutlined, InfoCircleOutlined } from "@ant-design/icons"; import CreatedKeyDisplay from "../shared/CreatedKeyDisplay"; @@ -218,6 +218,14 @@ const AddAgentForm: React.FC = ({ } } + agentData.require_trace_id_on_outgoing = values.require_trace_id_on_outgoing ?? false; + agentData.require_trace_id_on_incoming = values.require_trace_id_on_incoming ?? false; + + if (values.require_trace_id_on_outgoing && values.max_iteration_cap != null && values.max_iteration_cap > 0) { + agentData.litellm_params = agentData.litellm_params ?? {}; + agentData.litellm_params.max_iterations = Number(values.max_iteration_cap); + } + const agentResponse = await createAgentCall(accessToken, agentData); const agentId: string = agentResponse.agent_id; const agentName: string = agentResponse.agent_name || values.agent_name || agentId; @@ -315,6 +323,55 @@ const AddAgentForm: React.FC = ({ )} + + + +
+

Agent tracing

+

+ Require x-litellm-trace-id for observability. When enabled, requests without the header will be rejected. +

+ + + + + + + prev.require_trace_id_on_outgoing !== curr.require_trace_id_on_outgoing} + > + {() => { + const tracingOutgoing = form.getFieldValue("require_trace_id_on_outgoing"); + return ( + + + + ); + }} + +
); @@ -653,8 +710,21 @@ const AddAgentForm: React.FC = ({ layout="vertical" initialValues={ agentType === "a2a" - ? { ...getDefaultFormValues(), allowed_mcp_servers_and_groups: { servers: [], accessGroups: [] }, mcp_tool_permissions: {} } - : { allowed_mcp_servers_and_groups: { servers: [], accessGroups: [] }, mcp_tool_permissions: {} } + ? { + ...getDefaultFormValues(), + allowed_mcp_servers_and_groups: { servers: [], accessGroups: [] }, + mcp_tool_permissions: {}, + require_trace_id_on_outgoing: false, + require_trace_id_on_incoming: false, + max_iteration_cap: undefined, + } + : { + allowed_mcp_servers_and_groups: { servers: [], accessGroups: [] }, + mcp_tool_permissions: {}, + require_trace_id_on_outgoing: false, + require_trace_id_on_incoming: false, + max_iteration_cap: undefined, + } } className="space-y-4" > diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 9ac0b1573e6..298d2a76d78 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -9996,6 +9996,7 @@ export interface ToolRow { key_hash?: string; team_id?: string; key_alias?: string; + agent_id?: string; created_at?: string; updated_at?: string; created_by?: string;