From e78845afcf10fe48b56f4c352aafd2f624c48919 Mon Sep 17 00:00:00 2001 From: ishaan-berri <155045088+ishaan-berri@users.noreply.github.com> Date: Wed, 30 Sep 2026 12:10:27 -0700 Subject: [PATCH] feat(ui): agent traces tab on logs with timeline and otel setup guide (#43891) * feat(ui): add agent trace api calls * feat(ui): add agent trace types * feat(ui): add span tree row types * feat(ui): build span tree rows, grouped per agent * test(ui): cover span tree rows, grouping and previews * test(ui): add research trace fixture * test(ui): add swarm trace fixture * test(ui): add deep agent trace fixture * test(ui): add trace list fixture * feat(ui): fetch agent traces with a rolling live window * feat(ui): look up a span's request log at its own time * test(ui): cover rolling trace window and span log lookup * feat(ui): add status mark for spans * feat(ui): add duration bar for span timeline * feat(ui): add copy button * feat(ui): render span content as messages * feat(ui): open a span's request log in place * feat(ui): show raw span attributes * feat(ui): add span detail pane * test(ui): cover span detail pane * feat(ui): span tree with timeline and keyboard nav * feat(ui): run view with back out of load errors * test(ui): cover the run view and initial selection * feat(ui): add runs table toolbar * feat(ui): add runs table * feat(ui): add runs timeline with drag to zoom * test(ui): cover runs timeline bucketing and drag * feat(ui): add time range and live controls * feat(ui): agent traces section with timeline and range * test(ui): cover agent traces section * feat(ui): add agent traces page * feat(ui): otel setup guide for agent traces * test(ui): cover agent traces setup guide * feat(ui): add agent traces preview image * feat(ui): add langgraph logo * feat(ui): add langchain logo * feat(ui): add openai agents logo * feat(ui): add crewai logo * feat(ui): add pydantic ai logo * feat(ui): add llamaindex logo * feat(ui): add opentelemetry logo * feat(ui): add agent traces tab to logs * test(ui): cover agent traces tab on logs * feat(ui): collapse sidebar on logs for a full screen view * test(ui): cover sidebar collapse on logs --- .../public/assets/agent-traces-preview.png | Bin 0 -> 113074 bytes .../public/assets/logos/crewai-color.svg | 1 + .../public/assets/logos/langchain.svg | 1 + .../public/assets/logos/langgraph-color.svg | 1 + .../public/assets/logos/llamaindex-color.svg | 1 + .../public/assets/logos/openai-agents.svg | 1 + .../public/assets/logos/opentelemetry.svg | 1 + .../public/assets/logos/pydantic-ai-color.svg | 1 + .../src/app/(dashboard)/layout.test.tsx | 25 +- .../src/app/(dashboard)/layout.tsx | 13 +- .../src/components/networking.tsx | 28 + .../view_logs/TraceView/AgentTracesPage.tsx | 42 + .../TraceView/AgentTracesSection.test.tsx | 265 ++ .../TraceView/AgentTracesSection.tsx | 185 + .../view_logs/TraceView/AgentTracesTable.tsx | 148 + .../view_logs/TraceView/AttributesDetail.tsx | 33 + .../view_logs/TraceView/CopyButton.tsx | 63 + .../view_logs/TraceView/DetailContent.tsx | 152 + .../view_logs/TraceView/DetailPane.test.tsx | 191 + .../view_logs/TraceView/DetailPane.tsx | 188 + .../view_logs/TraceView/DurationBar.tsx | 27 + .../view_logs/TraceView/RequestDetail.tsx | 91 + .../view_logs/TraceView/RunsToolbar.tsx | 92 + .../view_logs/TraceView/SpanTree.tsx | 267 ++ .../view_logs/TraceView/StatusMark.tsx | 27 + .../view_logs/TraceView/TimeRangeControls.tsx | 101 + .../view_logs/TraceView/TraceDrawer.test.tsx | 146 + .../view_logs/TraceView/TraceDrawer.tsx | 269 ++ .../TraceView/TracesTimeline.test.ts | 83 + .../view_logs/TraceView/TracesTimeline.tsx | 365 ++ .../TraceView/TracingSetupCard.test.tsx | 57 + .../view_logs/TraceView/TracingSetupCard.tsx | 390 ++ .../__fixtures__/deep_agent_trace.json | 2056 ++++++++++ .../__fixtures__/research_trace.json | 3504 +++++++++++++++++ .../TraceView/__fixtures__/swarm_trace.json | 3368 ++++++++++++++++ .../TraceView/__fixtures__/trace_list.json | 59 + .../view_logs/TraceView/traceTree.ts | 48 + .../view_logs/TraceView/traceTypes.ts | 89 + .../view_logs/TraceView/traceUtils.test.ts | 269 ++ .../view_logs/TraceView/traceUtils.ts | 314 ++ .../TraceView/useAgentTraces.test.ts | 33 + .../view_logs/TraceView/useAgentTraces.ts | 91 + .../view_logs/TraceView/useSpanRequestLog.ts | 47 + .../src/components/view_logs/index.test.tsx | 8 +- .../src/components/view_logs/index.tsx | 29 +- 45 files changed, 13157 insertions(+), 13 deletions(-) create mode 100644 ui/litellm-dashboard/public/assets/agent-traces-preview.png create mode 100644 ui/litellm-dashboard/public/assets/logos/crewai-color.svg create mode 100644 ui/litellm-dashboard/public/assets/logos/langchain.svg create mode 100644 ui/litellm-dashboard/public/assets/logos/langgraph-color.svg create mode 100644 ui/litellm-dashboard/public/assets/logos/llamaindex-color.svg create mode 100644 ui/litellm-dashboard/public/assets/logos/openai-agents.svg create mode 100644 ui/litellm-dashboard/public/assets/logos/opentelemetry.svg create mode 100644 ui/litellm-dashboard/public/assets/logos/pydantic-ai-color.svg create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesPage.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesTable.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/AttributesDetail.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/CopyButton.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/DurationBar.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/RequestDetail.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/RunsToolbar.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/SpanTree.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/StatusMark.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TimeRangeControls.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TraceDrawer.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TraceDrawer.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TracesTimeline.test.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TracesTimeline.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TracingSetupCard.test.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/TracingSetupCard.tsx create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/__fixtures__/deep_agent_trace.json create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/__fixtures__/research_trace.json create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/__fixtures__/swarm_trace.json create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/__fixtures__/trace_list.json create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/traceTree.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/traceTypes.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/traceUtils.test.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/traceUtils.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/useAgentTraces.test.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/useAgentTraces.ts create mode 100644 ui/litellm-dashboard/src/components/view_logs/TraceView/useSpanRequestLog.ts diff --git a/ui/litellm-dashboard/public/assets/agent-traces-preview.png b/ui/litellm-dashboard/public/assets/agent-traces-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..34569e263315acaf94bd75a51ff44e3acd3d52cd GIT binary patch literal 113074 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 B#*Y91 literal 0 HcmV?d00001 diff --git a/ui/litellm-dashboard/public/assets/logos/crewai-color.svg b/ui/litellm-dashboard/public/assets/logos/crewai-color.svg new file mode 100644 index 00000000000..95cb17f9364 --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/crewai-color.svg @@ -0,0 +1 @@ +CrewAI \ No newline at end of file diff --git a/ui/litellm-dashboard/public/assets/logos/langchain.svg b/ui/litellm-dashboard/public/assets/logos/langchain.svg new file mode 100644 index 00000000000..939b79989a7 --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/langchain.svg @@ -0,0 +1 @@ +LangChain \ No newline at end of file diff --git a/ui/litellm-dashboard/public/assets/logos/langgraph-color.svg b/ui/litellm-dashboard/public/assets/logos/langgraph-color.svg new file mode 100644 index 00000000000..14f16e3cd1d --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/langgraph-color.svg @@ -0,0 +1 @@ +LangGraph \ No newline at end of file diff --git a/ui/litellm-dashboard/public/assets/logos/llamaindex-color.svg b/ui/litellm-dashboard/public/assets/logos/llamaindex-color.svg new file mode 100644 index 00000000000..99be517874e --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/llamaindex-color.svg @@ -0,0 +1 @@ +LlamaIndex \ No newline at end of file diff --git a/ui/litellm-dashboard/public/assets/logos/openai-agents.svg b/ui/litellm-dashboard/public/assets/logos/openai-agents.svg new file mode 100644 index 00000000000..78caf4fa20f --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/openai-agents.svg @@ -0,0 +1 @@ +OpenAI \ No newline at end of file diff --git a/ui/litellm-dashboard/public/assets/logos/opentelemetry.svg b/ui/litellm-dashboard/public/assets/logos/opentelemetry.svg new file mode 100644 index 00000000000..606165cf788 --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/opentelemetry.svg @@ -0,0 +1 @@ +OpenTelemetry \ No newline at end of file diff --git a/ui/litellm-dashboard/public/assets/logos/pydantic-ai-color.svg b/ui/litellm-dashboard/public/assets/logos/pydantic-ai-color.svg new file mode 100644 index 00000000000..85827432f0c --- /dev/null +++ b/ui/litellm-dashboard/public/assets/logos/pydantic-ai-color.svg @@ -0,0 +1 @@ +PydanticAI \ No newline at end of file diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.test.tsx index 3b52a2eac33..cc497677a1a 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.test.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.test.tsx @@ -23,7 +23,9 @@ vi.mock("@/components/DashboardHeader", () => ({ })); vi.mock("@/app/(dashboard)/components/SidebarProvider", () => ({ - default: () =>

, + default: ({ sidebarCollapsed }: { sidebarCollapsed: boolean }) => ( +
+ ), })); vi.mock("@/components/DebugWarningBanner", () => ({ @@ -112,6 +114,27 @@ describe("(dashboard) Layout", () => { }, ); + it("collapses the sidebar on Logs for a full-screen view and expands it again after leaving", async () => { + const dashboard = () => ( + + +
+ + + ); + const { rerender } = render(dashboard()); + pendingUiConfig.resolve(); + expect(await screen.findByTestId("sidebar")).toHaveAttribute("data-collapsed", "false"); + + vi.mocked(usePathname).mockReturnValue("/ui/logs"); + rerender(dashboard()); + expect(screen.getByTestId("sidebar")).toHaveAttribute("data-collapsed", "true"); + + vi.mocked(usePathname).mockReturnValue("/ui/api-keys"); + rerender(dashboard()); + expect(screen.getByTestId("sidebar")).toHaveAttribute("data-collapsed", "false"); + }); + it("does not mount route content until getUiConfig has resolved", async () => { render( diff --git a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx index 406a323fbfb..72f26919060 100644 --- a/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx +++ b/ui/litellm-dashboard/src/app/(dashboard)/layout.tsx @@ -99,11 +99,18 @@ export function AgentControlPlaneView() { ); } +const FULL_BLEED_SEGMENTS = new Set(["logs"]); + function DashboardShell({ children }: { children: React.ReactNode }) { const { accessToken } = useAuth(); - const [sidebarCollapsed, setSidebarCollapsed] = useState(false); const { mode } = usePluginMode(); - const isPlayground = routeSegmentForPathname(usePathname()) === "playground"; + const routeSegment = routeSegmentForPathname(usePathname()); + const isPlayground = routeSegment === "playground"; + const isFullBleed = FULL_BLEED_SEGMENTS.has(routeSegment); + // A manual toggle holds only for the route it was made on; full-bleed routes default to collapsed. + const [sidebarOverride, setSidebarOverride] = useState<{ segment: string; collapsed: boolean } | null>(null); + const sidebarCollapsed = sidebarOverride?.segment === routeSegment ? sidebarOverride.collapsed : isFullBleed; + const toggleSidebar = () => setSidebarOverride({ segment: routeSegment, collapsed: !sidebarCollapsed }); const isGateway = mode === "ai-gateway"; @@ -133,7 +140,7 @@ function DashboardShell({ children }: { children: React.ReactNode }) { // so the page can't be dragged past the end of the nav. return (
- setSidebarCollapsed((v) => !v)} /> +
diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 42dc5350b49..d0a5349364f 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -115,6 +115,7 @@ import type { ComplexityRouterConfigPayload } from "./add_model/build_complexity import type { AutoRouterPresetsResponse } from "@/lib/autorouter_presets"; import type { VectorStoreIndex } from "@/app/(dashboard)/vector-stores/_components/IndexesTab"; import type { RoutingDecision } from "./view_logs/LogDetailsDrawer/RoutingDecisionCard"; +import type { SpanDetail, Trace, TracePage } from "./view_logs/TraceView/traceTypes"; import { createApiClient, deriveErrorMessage, @@ -2101,6 +2102,33 @@ export const uiSpendLogsCall = async ({ } }; +/** + * Agent tracing. All three respond 501 `{detail}` when `general_settings.tracing` is not + * configured; callers can detect that through the thrown `ApiError`'s `status`. + */ +export const agentTraceListCall = async ({ + accessToken, + startMs, + endMs, + cursor, +}: { + accessToken: string; + startMs: number; + endMs: number; + cursor?: string | null; +}): Promise => { + const query = { start_ms: startMs, end_ms: endMs, cursor: cursor ?? undefined }; + return apiClient.get(`/v1/traces`, { accessToken, query }); +}; + +export const agentTraceCall = async (accessToken: string, traceId: string): Promise => + apiClient.get(`/v1/traces/${encodeURIComponent(traceId)}`, { accessToken }); + +export const agentTraceSpanCall = async (accessToken: string, traceId: string, spanId: string): Promise => + apiClient.get(`/v1/traces/${encodeURIComponent(traceId)}/spans/${encodeURIComponent(spanId)}`, { + accessToken, + }); + export const adminSpendLogsCall = async (accessToken: string) => { try { const data = await apiClient.get(`/global/spend/logs`, { accessToken }); diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesPage.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesPage.tsx new file mode 100644 index 00000000000..5b506cadbb0 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesPage.tsx @@ -0,0 +1,42 @@ +"use client"; + +import moment from "moment"; +import { useMemo, useState } from "react"; + +import { AgentTracesSection } from "./AgentTracesSection"; + +const DEFAULT_RANGE_HOURS = 24; +const TIME_FORMAT = "YYYY-MM-DDTHH:mm"; + +/** Agent Traces: agent runs (developer view). Lives as the "Agent Traces" tab of the Logs page. */ +export default function AgentTracesPage({ accessToken }: { accessToken: string }) { + const [rangeHours, setRangeHours] = useState(DEFAULT_RANGE_HOURS); + const [live, setLive] = useState(true); + const [anchor, setAnchor] = useState(() => moment()); + const { startTime, endTime } = useMemo( + () => ({ + startTime: anchor.clone().subtract(rangeHours, "hours").format(TIME_FORMAT), + endTime: anchor.format(TIME_FORMAT), + }), + [anchor, rangeHours], + ); + + const changeRange = (hours: number) => { + setRangeHours(hours); + setAnchor(moment()); + }; + + return ( +
+ +
+ ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx new file mode 100644 index 00000000000..6d28e8a53e9 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.test.tsx @@ -0,0 +1,265 @@ +import { fireEvent, screen, within } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; + +import { ApiError } from "@/lib/http/client"; + +import { renderWithProviders, testQueryClient } from "../../../../tests/test-utils"; +import traceList from "./__fixtures__/trace_list.json"; +import AgentTracesPage from "./AgentTracesPage"; +import { AgentTracesSection, filterRuns } from "./AgentTracesSection"; +import type { TracePage, TraceSummary } from "./traceTypes"; + +vi.mock("../../networking", () => ({ + agentTraceListCall: vi.fn(), + agentTraceCall: vi.fn(), + agentTraceSpanCall: vi.fn(), + getProxyBaseUrl: () => "http://localhost:4000", +})); + +vi.mock("./TraceDrawer", () => ({ + RunView: ({ traceId, onBack }: { traceId: string; onBack: () => void }) => ( +
+ run {traceId} + +
+ ), +})); + +import { agentTraceListCall } from "../../networking"; + +const runs = (traceList as TracePage).data as TraceSummary[]; + +const renderSection = () => + renderWithProviders( + , + ); + +// A UTC-pinned day around the fixture runs (2026-09-30 ~06:43 UTC), so they land in the same bucket in any timezone. +const renderWindowed = () => + renderWithProviders( + , + ); + +const bucketRunCounts = () => + screen.getAllByTestId("timeline-bucket").map((bucket) => Number(bucket.getAttribute("data-runs"))); + +describe("AgentTracesSection", () => { + afterEach(() => { + vi.restoreAllMocks(); + }); + + beforeEach(() => { + vi.spyOn(HTMLElement.prototype, "getBoundingClientRect").mockReturnValue({ + left: 0, + width: 600, + top: 0, + height: 56, + right: 600, + bottom: 56, + x: 0, + y: 0, + toJSON: () => ({}), + } as DOMRect); + testQueryClient.clear(); + vi.mocked(agentTraceListCall).mockReset(); + }); + + it("renders the setup snippet when the proxy answers 501", async () => { + vi.mocked(agentTraceListCall).mockRejectedValue( + new ApiError("Agent tracing is not enabled", 501, { detail: "Agent tracing is not enabled" }), + ); + renderSection(); + + const card = await screen.findByTestId("tracing-setup-card"); + expect(card).toHaveTextContent("Tracing is not enabled"); + expect(card).toHaveTextContent("store: clickhouse"); + expect(card).toHaveTextContent("OTEL_EXPORTER_OTLP_ENDPOINT="); + expect(card).not.toHaveTextContent(/langsmith/i); + expect(card).toHaveTextContent('OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $LITELLM_API_KEY"'); + expect(card).toHaveTextContent("Let Claude Code or Codex set it up"); + }); + + it("shows the waiting guide when tracing is on but no runs have arrived", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue({ ...(traceList as TracePage), data: [] }); + renderSection(); + + const card = await screen.findByTestId("tracing-setup-card"); + expect(card).toHaveTextContent("Waiting for traces"); + expect(card).toHaveTextContent("No traces detected yet"); + expect(card).not.toHaveTextContent("store: clickhouse"); + }); + + it("treats a proxy without the trace routes (404) like tracing being off", async () => { + vi.mocked(agentTraceListCall).mockRejectedValue(new ApiError("Not Found", 404, { detail: "Not Found" })); + renderSection(); + + const card = await screen.findByTestId("tracing-setup-card"); + expect(card).toHaveTextContent("Tracing is not enabled"); + expect(card).toHaveTextContent("CLICKHOUSE_READER_URL"); + }); + + it("lists every run with its input, counts and failed column", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderSection(); + + const rows = await screen.findAllByTestId("agent-trace-row"); + expect(rows).toHaveLength(runs.length); + const lead = rows.find((row) => row.textContent?.includes("Should we store OTEL agent spans")); + expect(lead).toBeDefined(); + const failed = rows.find((row) => row.textContent?.includes("acme-404")) as HTMLElement; + expect(within(failed).getByLabelText("2 errors")).toBeInTheDocument(); + expect(screen.getByText(`${runs.length} runs`)).toBeInTheDocument(); + expect(screen.queryByRole("columnheader", { name: "Cost" })).not.toBeInTheDocument(); + }); + + it("filters by input text and by trace id", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderSection(); + await screen.findAllByTestId("agent-trace-row"); + + const search = screen.getByLabelText("Search runs"); + fireEvent.change(search, { target: { value: "acme-404" } }); + expect(screen.getAllByTestId("agent-trace-row")).toHaveLength(1); + + const lead = runs.find((r) => r.name === "research_lead") as TraceSummary; + fireEvent.change(search, { target: { value: lead.trace_id.slice(0, 10) } }); + const rows = screen.getAllByTestId("agent-trace-row"); + expect(rows).toHaveLength(1); + expect(rows[0]).toHaveTextContent("Should we store OTEL agent spans"); + }); + + it("status filter 'Failed' keeps only runs with errors", () => { + const failed = filterRuns(runs, "", "all", "error"); + expect(failed.length).toBeGreaterThan(0); + expect(failed.every((r) => r.error_count > 0)).toBe(true); + const ok = filterRuns(runs, "", "all", "ok"); + expect(ok.every((r) => r.error_count === 0)).toBe(true); + expect(failed.length + ok.length).toBe(runs.length); + }); + + it("opens the run in place and goes back to the list", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderSection(); + const rows = await screen.findAllByTestId("agent-trace-row"); + + fireEvent.click(rows[0]); + expect(screen.getByTestId("run-view")).toHaveTextContent(`run ${runs[0].trace_id}`); + expect(screen.queryByTestId("runs-table")).not.toBeInTheDocument(); + + fireEvent.click(screen.getByText("back")); + expect(screen.getByTestId("runs-table")).toBeInTheDocument(); + }); + + it("plots every loaded run on the timeline", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWindowed(); + await screen.findAllByTestId("agent-trace-row"); + + expect(screen.getByTestId("traces-timeline")).toBeInTheDocument(); + const counts = bucketRunCounts(); + expect(counts).toHaveLength(60); + expect(counts.reduce((a, b) => a + b, 0)).toBe(runs.length); + }); + + it("zooms by dragging, resizes and pans the bracket, and clears with Esc", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWindowed(); + await screen.findAllByTestId("agent-trace-row"); + const area = screen.getByTestId("timeline-area"); + const x = (bucket: number) => bucket * 10 + 5; + const drag = (target: HTMLElement, from: number, to: number) => { + fireEvent.pointerDown(target, { clientX: x(from), pointerId: 1 }); + fireEvent.pointerMove(area, { clientX: x(to), pointerId: 1 }); + fireEvent.pointerUp(area, { clientX: x(to), pointerId: 1 }); + }; + const rowCount = () => screen.queryAllByTestId("agent-trace-row").length; + const withRuns = bucketRunCounts().flatMap((count, i) => (count > 0 ? [i] : [])); + const first = withRuns[0]; + // The pan below moves a [0, first] bracket to the far right; it must end up clear of every run. + expect(first).toBeGreaterThan(1); + expect(first).toBeLessThan(30); + + drag(area, 0, 1); + expect(screen.getByTestId("timeline-selection")).toBeInTheDocument(); + expect(rowCount()).toBe(0); + + drag(screen.getByTestId("timeline-handle-hi"), 1, first); + expect(rowCount()).toBeGreaterThan(0); + + drag(screen.getByTestId("timeline-selection"), 1, 1 - first); + expect(rowCount()).toBeGreaterThan(0); + drag(screen.getByTestId("timeline-selection"), 0, 59); + expect(rowCount()).toBe(0); + + fireEvent.keyDown(screen.getByTestId("traces-timeline"), { key: "Escape" }); + expect(screen.queryByTestId("timeline-selection")).not.toBeInTheDocument(); + expect(rowCount()).toBe(runs.length); + }); +}); + +describe("AgentTracesPage", () => { + beforeEach(() => { + testQueryClient.clear(); + vi.mocked(agentTraceListCall).mockReset(); + }); + + it("shows the actual range, switches presets from the popover, and toggles Live", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWithProviders(); + await screen.findByTestId("runs-table"); + + const trigger = screen.getByRole("button", { name: "Time range" }); + expect(trigger).toHaveTextContent(/ to /); + expect(screen.getByTestId("traces-timeline")).toHaveTextContent("Total 1d"); + + fireEvent.click(trigger); + fireEvent.click(await screen.findByRole("menuitemradio", { name: "Last 7 days" })); + expect(await screen.findByText("Total 7d")).toBeInTheDocument(); + const last = vi.mocked(agentTraceListCall).mock.calls.at(-1)?.[0]; + expect((last?.endMs ?? 0) - (last?.startMs ?? 0)).toBeGreaterThanOrEqual(7 * 24 * 3600 * 1000 - 60_000); + + const live = screen.getByRole("button", { name: "Live" }); + expect(live).toHaveAttribute("aria-pressed", "true"); + fireEvent.click(live); + expect(live).toHaveAttribute("aria-pressed", "false"); + expect(screen.getByRole("button", { name: "Reset zoom" })).toBeDisabled(); + }); + + it("keeps the time controls on an empty range the user picked, instead of showing onboarding", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWithProviders(); + await screen.findByTestId("runs-table"); + + vi.mocked(agentTraceListCall).mockResolvedValue({ ...(traceList as TracePage), data: [] }); + fireEvent.click(screen.getByRole("button", { name: "Time range" })); + fireEvent.click(await screen.findByRole("menuitemradio", { name: "Last hour" })); + + expect(await screen.findByText("No runs match these filters.")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: "Time range" })).toBeInTheDocument(); + expect(screen.queryByTestId("tracing-setup-card")).not.toBeInTheDocument(); + }); + + it("asks the proxy for the last 24 hours by default", async () => { + vi.mocked(agentTraceListCall).mockResolvedValue(traceList as TracePage); + renderWithProviders(); + await screen.findByTestId("runs-table"); + + const { startMs, endMs } = vi.mocked(agentTraceListCall).mock.calls[0][0]; + expect(endMs - startMs).toBeGreaterThanOrEqual(24 * 3600 * 1000 - 60_000); + expect(endMs - startMs).toBeLessThan(24 * 3600 * 1000 + 120_000); + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx new file mode 100644 index 00000000000..2bfa3c320a7 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesSection.tsx @@ -0,0 +1,185 @@ +"use client"; + +import moment from "moment"; +import { useMemo, useState } from "react"; + +import { AgentTracesTable } from "./AgentTracesTable"; +import { ALL_SERVICES, RunsToolbar, type RunStatusFilter } from "./RunsToolbar"; +import { RunView } from "./TraceDrawer"; +import type { TraceSummary } from "./traceTypes"; +import { previewText } from "./traceUtils"; +import { TimeRangeControls } from "./TimeRangeControls"; +import { TracesTimeline, type TimeWindow } from "./TracesTimeline"; +import { TracingSetupCard } from "./TracingSetupCard"; +import { traceWindowStartMs, useAgentTraces } from "./useAgentTraces"; + +/** Client-side search (input text or trace id) plus service / status filters over the loaded runs. */ +export function filterRuns( + runs: TraceSummary[], + query: string, + service: string, + status: RunStatusFilter, +): TraceSummary[] { + const q = query.trim().toLowerCase(); + return runs.filter((run) => { + const haystack = [run.trace_id, previewText(run.input_preview), run.name].map((s) => s.toLowerCase()); + const matchesQuery = !q || haystack.some((text) => text.includes(q)); + const matchesService = service === ALL_SERVICES || run.service === service; + const failed = run.error_count > 0; + const matchesStatus = status === "all" || (status === "error" ? failed : !failed); + return matchesQuery && matchesService && matchesStatus; + }); +} + +const filterByWindow = (runs: TraceSummary[], range: TimeWindow): TraceSummary[] => + runs.filter((run) => { + const t = moment(run.start_time).valueOf(); + return t >= range.startMs && t < range.endMs; + }); + +export interface TimeControls { + rangeHours: number; + onRangeHoursChange: (hours: number) => void; + onLiveChange: (live: boolean) => void; +} + +interface AgentTracesSectionProps { + accessToken: string; + isActive: boolean; + startTime: string; + endTime: string; + isCustomDate: boolean; + isLiveTail: boolean; + /** Page-owned time range + live state; when given, the toolbar shows the range / Live control group. */ + timeControls?: TimeControls; + /** Called when a run opens / closes, so the page can hide its own header while a run fills the view. */ + onRunOpenChange?: (open: boolean) => void; +} + +/** The Runs view: filters, the runs table and footer — or one run, in place, once a row is clicked. */ +export function AgentTracesSection({ + accessToken, + isActive, + startTime, + endTime, + isCustomDate, + isLiveTail, + timeControls, + onRunOpenChange, +}: AgentTracesSectionProps) { + const [openTraceId, setOpenTraceId] = useState(null); + const [query, setQuery] = useState(""); + const [service, setService] = useState(ALL_SERVICES); + const [status, setStatus] = useState("all"); + const [showSetup, setShowSetup] = useState(false); + const [zoom, setZoom] = useState(null); + const [rangeChanged, setRangeChanged] = useState(false); + const traceQuery = { accessToken, startTime, endTime, isCustomDate, isLiveTail, enabled: isActive }; + const traces = useAgentTraces(traceQuery); + + const services = useMemo(() => Array.from(new Set(traces.traces.map((t) => t.service))).sort(), [traces.traces]); + // Relative ranges end "now" (the list query uses Date.now() too); round to the minute so the histogram is stable. + const endMs = isCustomDate ? moment(endTime).valueOf() : moment().endOf("minute").valueOf(); + const range = useMemo( + () => ({ startMs: traceWindowStartMs(startTime, endTime, isCustomDate, endMs), endMs }), + [startTime, endTime, isCustomDate, endMs], + ); + const filtered = useMemo( + () => filterRuns(traces.traces, query, service, status), + [traces.traces, query, service, status], + ); + const runs = useMemo(() => (zoom ? filterByWindow(filtered, zoom) : filtered), [filtered, zoom]); + + const changeRange = (hours: number, apply: (hours: number) => void) => { + setZoom(null); + setRangeChanged(true); + apply(hours); + }; + + const openRun = (traceId: string | null) => { + setOpenTraceId(traceId); + onRunOpenChange?.(traceId !== null); + }; + + if (traces.notEnabledDetail !== null) return ; + // Onboarding only on the first, default view; an empty range the user picked keeps its controls. + const isEmpty = !traces.isLoading && !traces.error && traces.traces.length === 0; + if (isEmpty && !rangeChanged) return ; + if (showSetup) { + return ( +
+ + +
+ ); + } + + if (openTraceId !== null) { + return openRun(null)} />; + } + + return ( +
+ + + {timeControls && ( + changeRange(hours, timeControls.onRangeHoursChange)} + live={isLiveTail} + onLiveChange={timeControls.onLiveChange} + zoomed={zoom !== null} + onResetZoom={() => setZoom(null)} + /> + )} + + + +
+ {runs.length} {runs.length === 1 ? "run" : "runs"} + {zoom && ( + + )} + {traces.isFetching ? "Updating…" : "Updated just now"} +
+
+ ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesTable.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesTable.tsx new file mode 100644 index 00000000000..e4faff1621a --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AgentTracesTable.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { ArrowDown, ChevronRight } from "lucide-react"; + +import { Button } from "@/components/ui/button"; + +import { StatusMark } from "./StatusMark"; +import type { TraceSummary } from "./traceTypes"; +import { fmtMs, previewText, traceDisplayName } from "./traceUtils"; + +interface AgentTracesTableProps { + traces: TraceSummary[]; + isLoading: boolean; + error: Error | null; + hasMore: boolean; + onLoadMore: () => void; + onOpenTrace: (traceId: string) => void; +} + +/** Spend is only on summaries once the spend-enrichment PR lands; show Cost when it's there. */ +type SummaryWithSpend = TraceSummary & { spend?: number }; + +const SECOND_MS = 1000; +const MINUTE_S = 60; +const HOUR_M = 60; +const DAY_H = 24; + +export function relativeTime(iso: string, now: number = Date.now()): string { + const diffS = Math.round((now - new Date(iso).getTime()) / SECOND_MS); + if (diffS < 5) return "just now"; + if (diffS < MINUTE_S) return `${diffS}s ago`; + const diffM = Math.round(diffS / MINUTE_S); + if (diffM < HOUR_M) return `${diffM}m ago`; + const diffH = Math.round(diffM / HOUR_M); + if (diffH < DAY_H) return `${diffH}h ago`; + return `${Math.round(diffH / DAY_H)}d ago`; +} + +export const formatCost = (cost: number): string => { + if (cost === 0) return "$0.00"; + if (cost < 0.01) return `$${cost.toFixed(4)}`; + return `$${cost.toFixed(2)}`; +}; + +const firstLine = (text: string): string => text.split("\n")[0] ?? text; + +const TH = "px-3 font-medium"; +const TH_NUM = "px-3 text-right font-medium"; +const TD_NUM = "px-3 text-right font-mono tabular-nums text-muted-foreground"; + +/** Devtool-dense runs list: one row per agent run, newest first. */ +export function AgentTracesTable({ + traces, + isLoading, + error, + hasMore, + onLoadMore, + onOpenTrace, +}: AgentTracesTableProps) { + const showCost = traces.some((t) => typeof (t as SummaryWithSpend).spend === "number"); + const isEmpty = !isLoading && !error && traces.length === 0; + return ( +
+ + + + + + + + + + {showCost && } + + + + + {traces.map((run) => ( + onOpenTrace(run.trace_id)} + className="h-9 cursor-pointer border-b border-border/60 text-[12px] hover:bg-accent/50" + > + + + + + + + {showCost && ( + + )} + + + + ))} + +
+ + Time + + ServiceInputAgentsStepsDurationCostFailed +
+ {relativeTime(run.start_time)} + + {run.service} + +
+ 0 ? "error" : "ok"} subtle /> + + {firstLine(previewText(run.input_preview)) || traceDisplayName(run)} + + + {run.trace_id} + +
+
{run.agent_count.toLocaleString()}{run.span_count.toLocaleString()}{fmtMs(run.duration_ms)} + {formatCost((run as SummaryWithSpend).spend ?? 0)} + + {run.error_count > 0 ? ( + + ) : ( + 0 + )} + + +
+ {isLoading &&
Loading runs…
} + {error && ( +
Could not load runs: {error.message}
+ )} + {isEmpty && ( +
No runs match these filters.
+ )} + {hasMore && ( +
+ +
+ )} +
+ ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/AttributesDetail.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/AttributesDetail.tsx new file mode 100644 index 00000000000..9001b1470a2 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/AttributesDetail.tsx @@ -0,0 +1,33 @@ +"use client"; + +import type { Span } from "./traceTypes"; + +interface AttributesDetailProps { + traceId: string; + span: Span; + attributes: Record | undefined; + isLoading: boolean; +} + +/** Raw OTEL attributes as a key / value grid, ids first. */ +export function AttributesDetail({ traceId, span, attributes, isLoading }: AttributesDetailProps) { + const entries: [string, string][] = [ + ["trace_id", traceId], + ["span_id", span.span_id], + ["parent_span_id", span.parent_span_id ?? "—"], + ...Object.entries(attributes ?? {}).sort(([a], [b]) => a.localeCompare(b)), + ]; + return ( +
+
+ {entries.map(([key, value]) => ( +
+
{key}
+
{value}
+
+ ))} +
+ {isLoading &&
Loading attributes…
} +
+ ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/CopyButton.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/CopyButton.tsx new file mode 100644 index 00000000000..60dda4ce72d --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/CopyButton.tsx @@ -0,0 +1,63 @@ +"use client"; + +import { Check, Copy } from "lucide-react"; +import { useEffect, useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; +import { cn } from "@/lib/cva.config"; +import { copyToClipboard } from "@/utils/dataUtils"; + +const COPIED_RESET_MS = 1600; + +interface CopyButtonProps { + value: string; + label?: string; + copiedLabel?: string; + iconOnly?: boolean; + className?: string; +} + +/** Copy → check for a moment. `iconOnly` renders a bare icon button with a tooltip. */ +export function CopyButton({ + value, + label = "Copy", + copiedLabel = "Copied", + iconOnly = false, + className, +}: CopyButtonProps) { + const [copied, setCopied] = useState(false); + + useEffect(() => { + if (!copied) return; + const timeout = window.setTimeout(() => setCopied(false), COPIED_RESET_MS); + return () => window.clearTimeout(timeout); + }, [copied]); + + const button = ( + + ); + + if (!iconOnly) return button; + return ( + + + + {copied ? copiedLabel : label} + + + ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx new file mode 100644 index 00000000000..d60805e8f45 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailContent.tsx @@ -0,0 +1,152 @@ +"use client"; + +import { useQuery, type UseQueryOptions } from "@tanstack/react-query"; +import { AlertTriangle, Bot, CornerDownRight, Wrench } from "lucide-react"; + +import { agentTraceSpanCall } from "../../networking"; +import type { ErrorSource } from "./traceTree"; +import type { Span, SpanDetail, TraceMessage } from "./traceTypes"; +import { errorSource, parseMessages, prettyPayload } from "./traceUtils"; + +const ERROR_SOURCE_LABEL: Record = { tool: "Tool", model: "Model", litellm: "LiteLLM" }; +const TRACEBACK_MARKER = "Traceback (most recent call last):"; + +/** LangSmith records `repr(exc)` + traceback with no separator; keep the exception line. */ +export const errorHeadline = (error: string): string => + (error.split(TRACEBACK_MARKER, 1)[0].split("\n")[0] ?? "").trim() || error.trim(); + +/** `ValueError('x not found')` → "ValueError"; plain text → "error". */ +const errorReason = (headline: string): string => /^([A-Za-z_][\w.]*)\(/.exec(headline)?.[1] ?? "error"; + +/** Shared lazy fetch of one span's full input / output / attributes. */ +export function useSpanDetail(accessToken: string, traceId: string, spanId: string | null) { + const queryOptions: UseQueryOptions = { + queryKey: ["agentTraceSpan", traceId, spanId, accessToken], + queryFn: () => agentTraceSpanCall(accessToken, traceId, spanId as string), + enabled: spanId !== null, + staleTime: Infinity, + }; + return useQuery(queryOptions); +} + +export function SectionLabel({ children }: { children: React.ReactNode }) { + return ( +
+ {children} +
+ ); +} + +export function TextBlock({ label, value, mono = false }: { label: string; value: string; mono?: boolean }) { + return ( +
+ {label} +
+ {value} +
+
+ ); +} + +function RoleIcon({ role }: { role: string }) { + if (role === "assistant") return ; + if (role === "tool") return ; + return ; +} + +export function MessageBlock({ message }: { message: TraceMessage }) { + return ( +
+
+ + {message.role} + {message.name ? · {message.name} : null} +
+ {(message.tool_calls ?? []).map((call, i) => ( +
+ {call.name} + ( + {JSON.stringify(call.args)} + ) +
+ ))} + {message.content && ( +
+ {message.content} +
+ )} +
+ ); +} + +export function ErrorBlock({ span }: { span: Span }) { + const source = errorSource(span); + if (!source) return null; + const headline = errorHeadline(span.error ?? "") || "Span reported an error status."; + return ( +
+
+ + {ERROR_SOURCE_LABEL[source]} · {errorReason(headline)} +
+
+        {headline}
+      
+
+ ); +} + +function Payload({ label, value, mono }: { label: string; value: string; mono: boolean }) { + const messages = parseMessages(value); + if (messages) { + return ( + <> + {`${label}${messages.length > 1 ? ` · ${messages.length} messages` : ""}`} + {messages.map((message, i) => ( + + ))} + + ); + } + return ; +} + +interface DetailContentProps { + accessToken: string; + traceId: string; + span: Span; +} + +/** Content tab: the error first (if any), then what went in and what came out. */ +export function DetailContent({ accessToken, traceId, span }: DetailContentProps) { + const detailQuery = useSpanDetail(accessToken, traceId, span.span_id); + const detail = detailQuery.data; + const isTool = span.type === "tool"; + const empty = detail && !detail.input && !detail.output; + + return ( +
+ + {detailQuery.isLoading &&
Loading span…
} + {detailQuery.isError && ( +
+ Could not load span: {detailQuery.error.message} +
+ )} + {detail?.input ? : null} + {detail?.output ? : null} + {empty && span.status !== "error" && ( +
+ No content recorded for this span. +
+ )} +
+ ); +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.test.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.test.tsx new file mode 100644 index 00000000000..a15584e6d41 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.test.tsx @@ -0,0 +1,191 @@ +import { screen, waitFor } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { renderWithProviders, testQueryClient } from "../../../../tests/test-utils"; +import { DetailPane } from "./DetailPane"; +import type { GroupRowData, SpanRowData } from "./traceTree"; +import type { Span, SpanDetail, Trace } from "./traceTypes"; + +vi.mock("../../networking", () => ({ + agentTraceSpanCall: vi.fn(), + getProxyBaseUrl: () => "http://proxy.test/", +})); + +import { agentTraceSpanCall } from "../../networking"; + +const span = (overrides: Partial & Pick): Span => ({ + parent_span_id: "root", + name: overrides.span_id, + type: "chain", + agent: "support_triage_agent", + start_offset_ms: 0, + duration_ms: 1300, + status: "ok", + error: null, + input_preview: "", + model: null, + input_tokens: 0, + output_tokens: 0, + litellm_request_id: null, + ...overrides, +}); + +const rootFields: SpanFields = { span_id: "root", parent_span_id: null, name: "support_triage_agent", type: "agent" }; +const llmFields: SpanFields = { + span_id: "llm1", + name: "ChatOpenAI", + type: "llm", + model: "claude-sonnet-4-5", + input_tokens: 659, + output_tokens: 60, + litellm_request_id: "chatcmpl-abc", +}; +const failedToolFields: SpanFields = { + span_id: "tool1", + name: "get_customer_plan", + type: "tool", + status: "error", + error: + "ValueError('customer acme-404 not found in billing DB')Traceback (most recent call last):\n File \"x.py\", line 1", +}; +const root = span(rootFields); +const llm = span(llmFields); +const failedTool = span(failedToolFields); + +const trace: Trace = { + summary: { + trace_id: "t1", + name: "support_triage_agent", + service: "research-agent", + input_preview: '[{"role": "user", "content": "Customer acme-404 says billing is wrong."}]', + start_time: "2026-09-30T06:43:52.928000+00:00", + duration_ms: 1310, + status: "ok", + span_count: 3, + agent_count: 1, + agent_invocations: 1, + llm_calls: 1, + tool_calls: 1, + error_count: 1, + input_tokens: 659, + output_tokens: 60, + models: ["claude-sonnet-4-5"], + } as Trace["summary"], + agents: [], + spans: [root, llm, failedTool], +}; + +const details: Record = { + llm1: { + span_id: "llm1", + input: JSON.stringify([ + { role: "system", content: "You are a LiteLLM support agent." }, + { role: "user", content: "Customer acme-404 says billing is wrong." }, + ]), + output: JSON.stringify({ + role: "assistant", + content: "", + tool_calls: [{ name: "get_customer_plan", args: { customer_id: "acme-404" } }], + }), + attributes: { "gen_ai.request.model": "claude-sonnet-4-5" }, + }, + tool1: { span_id: "tool1", input: '{"customer_id":"acme-404"}', output: "", attributes: {} }, + root: { + span_id: "root", + input: JSON.stringify([{ role: "user", content: "Customer acme-404 says billing is wrong." }]), + output: JSON.stringify({ role: "assistant", content: "Customer acme-404 is on the Enterprise plan." }), + attributes: {}, + }, +}; + +const spanRow = (s: Span): SpanRowData => ({ + kind: "span", + id: s.span_id, + span: s, + depth: 1, + hasChildren: false, + collapsed: false, +}); + +const renderPane = (row: SpanRowData | GroupRowData) => + renderWithProviders(); + +describe("DetailPane", () => { + beforeEach(() => { + testQueryClient.clear(); + vi.mocked(agentTraceSpanCall).mockReset(); + vi.mocked(agentTraceSpanCall).mockImplementation(async (_token, _trace, spanId) => details[spanId]); + }); + + it("renders the span tabs and the fetched LLM conversation with its tool call", async () => { + renderPane(spanRow(llm)); + expect(screen.getByRole("tab", { name: "Content" })).toBeInTheDocument(); + expect(screen.getByRole("tab", { name: "Request" })).toBeInTheDocument(); + expect(screen.getByRole("tab", { name: "Attributes" })).toBeInTheDocument(); + expect(await screen.findByText("You are a LiteLLM support agent.")).toBeInTheDocument(); + expect(screen.getByText("get_customer_plan")).toBeInTheDocument(); + expect(vi.mocked(agentTraceSpanCall)).toHaveBeenCalledWith("sk-test", "t1", "llm1"); + }); + + it("shows a tool failure as 'Tool · ' with the exception line and no traceback", async () => { + renderPane(spanRow(failedTool)); + const error = screen.getByRole("region", { name: "Error" }); + expect(error).toHaveTextContent("Tool · ValueError"); + expect(error).toHaveTextContent("ValueError('customer acme-404 not found in billing DB')"); + expect(error).not.toHaveTextContent("Traceback"); + // tool args render as pretty JSON under "Input" + expect(await screen.findByText(/"customer_id": "acme-404"/)).toBeInTheDocument(); + }); + + it("shows the LiteLLM request facts on the Request tab", async () => { + const user = userEvent.setup(); + renderPane(spanRow(llm)); + await user.click(screen.getByRole("tab", { name: "Request" })); + expect(await screen.findByText("chatcmpl-abc")).toBeInTheDocument(); + expect(screen.getByText("659")).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Open request log/ })).toBeInTheDocument(); + }); + + it("summarizes a ×N group with its failure pattern", () => { + const members = Array.from({ length: 12 }, (_, i) => { + const timedOut: SpanFields = { + span_id: `f${i}`, + name: "lookup_benchmark", + type: "tool", + status: "error", + error: "TimeoutError('slow')", + }; + return span(timedOut); + }); + const groupRow: GroupRowData = { + kind: "group", + id: "grp", + depth: 1, + name: "lookup_benchmark", + type: "tool", + agent: "researcher", + members, + failedCount: 12, + p50Duration: 640, + isFailureGroup: true, + expanded: false, + }; + renderPane(groupRow); + const pane = screen.getByRole("complementary", { name: "Group details" }); + expect(pane).toHaveTextContent("lookup_benchmark ×12"); + expect(pane).toHaveTextContent("Invocations12"); + expect(pane).toHaveTextContent("Failed12"); + expect(pane).toHaveTextContent("TimeoutError('slow')"); + }); + + it("'Copy step' copies a curl for just this span as Markdown", async () => { + const user = userEvent.setup(); + const writeText = vi.fn().mockResolvedValue(undefined); + Object.defineProperty(navigator, "clipboard", { value: { writeText }, configurable: true }); + renderPane(spanRow(llm)); + await user.click(screen.getByRole("button", { name: "Copy step" })); + await waitFor(() => expect(writeText).toHaveBeenCalled()); + expect(writeText.mock.calls[0][0]).toContain("http://proxy.test/v1/traces/t1?format=md&span_id=llm1"); + }); +}); diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.tsx new file mode 100644 index 00000000000..0a4dd3c5029 --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/DetailPane.tsx @@ -0,0 +1,188 @@ +"use client"; + +import { PanelRightClose } from "lucide-react"; +import { useState } from "react"; + +import { Button } from "@/components/ui/button"; +import { cn } from "@/lib/cva.config"; + +import { AttributesDetail } from "./AttributesDetail"; +import { CopyButton } from "./CopyButton"; +import { DetailContent, errorHeadline, useSpanDetail } from "./DetailContent"; +import { RequestDetail } from "./RequestDetail"; +import { agentHandoffText } from "./TraceDrawer"; +import type { GroupRowData, TreeRow } from "./traceTree"; +import type { Span, Trace } from "./traceTypes"; +import { fmtMs, fmtTok } from "./traceUtils"; + +interface DetailPaneProps { + trace: Trace; + row: TreeRow | undefined; + accessToken: string; + onClose: () => void; +} + +type Tab = "content" | "request" | "attributes"; + +const TABS: { id: Tab; label: string }[] = [ + { id: "content", label: "Content" }, + { id: "request", label: "Request" }, + { id: "attributes", label: "Attributes" }, +]; + +function PaneHeader({ children, onClose }: { children: React.ReactNode; onClose: () => void }) { + return ( +
+ {children} + +
+ ); +} + +function PaneFooter({ children }: { children: React.ReactNode }) { + return
{children}
; +} + +function Meta({ label, value }: { label: string; value: string }) { + return ( + + {label}= + {value} + + ); +} + +function SpanPane({ + trace, + span, + accessToken, + onClose, +}: { + trace: Trace; + span: Span; + accessToken: string; + onClose: () => void; +}) { + const [tab, setTab] = useState("content"); + const traceId = trace.summary.trace_id; + const detailQuery = useSpanDetail(accessToken, traceId, tab === "attributes" ? span.span_id : null); + const tokens = span.input_tokens + span.output_tokens; + return ( + + ); +} + +function GroupMetric({ label, value }: { label: string; value: string }) { + return ( +
+
{label}
+
{value}
+
+ ); +} + +/** ×N group: rollup of every invocation plus the first failure's message. */ +function GroupPane({ trace, row, onClose }: { trace: Trace; row: GroupRowData; onClose: () => void }) { + const tokens = row.members.reduce((sum, m) => sum + m.input_tokens + m.output_tokens, 0); + const firstFailure = row.members.find((m) => m.status === "error" && m.error); + return ( + + ); +} + +/** Right pane of the run view: switches on the selected tree row. */ +export function DetailPane({ trace, row, accessToken, onClose }: DetailPaneProps) { + if (!row || row.kind === "load-more") { + return ( +
+ Select a span to inspect it. +
+ ); + } + if (row.kind === "group") return ; + return ; +} diff --git a/ui/litellm-dashboard/src/components/view_logs/TraceView/DurationBar.tsx b/ui/litellm-dashboard/src/components/view_logs/TraceView/DurationBar.tsx new file mode 100644 index 00000000000..205712f4eec --- /dev/null +++ b/ui/litellm-dashboard/src/components/view_logs/TraceView/DurationBar.tsx @@ -0,0 +1,27 @@ +import { cn } from "@/lib/cva.config"; + +interface DurationBarProps { + startMs: number; + durationMs: number; + totalMs: number; + error?: boolean; + className?: string; +} + +/** Waterfall bar: a hairline track with the span's slice of the run's timeline. */ +export function DurationBar({ startMs, durationMs, totalMs, error = false, className }: DurationBarProps) { + const left = totalMs > 0 ? Math.max(0, Math.min(98, (startMs / totalMs) * 100)) : 0; + const width = totalMs > 0 ? Math.max(1.5, Math.min(100 - left, (durationMs / totalMs) * 100)) : 1.5; + return ( +