diff --git a/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.test.tsx b/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.test.tsx index f6bc52e1fe9..f25517e2909 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.test.tsx +++ b/ui/litellm-dashboard/src/components/lens/traces/detail/run/RunView.test.tsx @@ -113,19 +113,12 @@ describe("RunView", () => { expect(screen.getByRole("banner")).toHaveTextContent(`Step errors ${swarm.summary.error_count}`); }); - it("opens a failed run on its first failed span", async () => { - renderRun(swarm); - - const pane = await screen.findByTestId("detail-pane"); - const { selectedId } = initialRunSelection(swarm); - expect(selectedId).not.toBe(rootSpanId(swarm)); - expect(pane).toHaveAttribute("data-row-id", selectedId); - expect(swarm.spans.find((s) => s.span_id === selectedId)?.status).toBe("error"); - }); - - it("opens a healthy run on the root span", async () => { - renderRun(research); - expect(await screen.findByTestId("detail-pane")).toHaveAttribute("data-row-id", rootSpanId(research)); + it.each([ + ["failed", swarm], + ["healthy", research], + ])("opens a %s run on the root span", async (_label, trace) => { + renderRun(trace); + expect(await screen.findByTestId("detail-pane")).toHaveAttribute("data-row-id", rootSpanId(trace)); }); it("inside the drawer moves spans with the arrow keys and leaves J / K to switch runs", async () => { @@ -191,7 +184,7 @@ describe("RunView", () => { expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent(traceDisplayName(swarm.summary)), ); expect(screen.getByTestId("run-view")).toHaveAttribute("aria-busy", "false"); - expect(screen.getByTestId("detail-pane")).toHaveAttribute("data-row-id", initialRunSelection(swarm).selectedId); + expect(screen.getByTestId("detail-pane")).toHaveAttribute("data-row-id", rootSpanId(swarm)); }); it("moves the selection with J / K and closes the detail pane with Esc", async () => { @@ -712,36 +705,19 @@ describe("initialRunSelection", () => { return { ...base, ...defaults, ...over }; }; - it("lands on a visible failure, never on a framework span the tree hides", () => { - const hiddenFields: Partial = { span_id: "mw", type: "framework", status: "error", start_offset_ms: 1 }; - const toolFields: Partial = { span_id: "tool", type: "tool", status: "error", start_offset_ms: 5 }; - const hiddenFailure = child(hiddenFields); - const toolFailure = child(toolFields); - const trace = { ...research, spans: [base, hiddenFailure, toolFailure] }; - expect(initialRunSelection(trace).selectedId).toBe("tool"); - }); - - it("folds other agent branches while revealing the failed step", () => { - const first = child({ span_id: "first", type: "agent" }); - const second = child({ span_id: "second", type: "agent" }); - const failureFields: Partial = { span_id: "failed", parent_span_id: "second", type: "tool", status: "error" }; - const failure = child(failureFields); - const { selectedId, state } = initialRunSelection({ ...research, spans: [base, first, second, failure] }); - expect(selectedId).toBe("failed"); - expect(state.collapsedSpanIds.has("first")).toBe(true); - expect(state.collapsedSpanIds.has("second")).toBe(false); - }); - - it("falls back to the nearest visible ancestor when only a hidden span failed", () => { - const agent = child({ span_id: "agent", type: "agent", name: "researcher" }); - const hiddenFields: Partial = { span_id: "mw", parent_span_id: "agent", type: "framework", status: "error" }; - const hiddenFailure = child(hiddenFields); - const trace = { ...research, spans: [base, agent, hiddenFailure] }; - expect(initialRunSelection(trace).selectedId).toBe("agent"); + it("opens on the root and folds nested agent branches even when a step failed", () => { + const agentFields: Partial = { span_id: "agent", type: "agent" }; + const failureFields: Partial = { span_id: "failed", parent_span_id: "agent", type: "tool", status: "error" }; + const { selectedId, state } = initialRunSelection({ + ...research, + spans: [base, child(agentFields), child(failureFields)], + }); + expect(selectedId).toBe(base.span_id); + expect(state.collapsedSpanIds.has("agent")).toBe(true); }); }); -it("opens a cited span instead of the default failed span", () => { +it("opens a cited span instead of the root", () => { const cited = research.spans.find((span) => span.parent_span_id !== null)!; expect(initialRunSelection(research, cited.span_id).selectedId).toBe(cited.span_id); expect(initialRunSelection(research, "missing")).toEqual(initialRunSelection(research)); diff --git a/ui/litellm-dashboard/src/components/lens/traces/detail/run/useRunTree.ts b/ui/litellm-dashboard/src/components/lens/traces/detail/run/useRunTree.ts index d9eaf9a18de..2b956be765d 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/detail/run/useRunTree.ts +++ b/ui/litellm-dashboard/src/components/lens/traces/detail/run/useRunTree.ts @@ -3,15 +3,7 @@ import { useCallback, useMemo, useState } from "react"; import type { RunSelection } from "../../routing"; import type { SpanTreeState, TreeRow } from "../../tree"; import type { Trace } from "../../types"; -import { - buildTreeRows, - findTraceSteps, - firstErrorSpan, - GROUP_PAGE_SIZE, - isFrameworkSpan, - nearestVisibleSpanId, - revealSpanInState, -} from "../../utils"; +import { buildTreeRows, findTraceSteps, GROUP_PAGE_SIZE, nearestVisibleSpanId, revealSpanInState } from "../../utils"; const INITIAL_STATE: SpanTreeState = { hideFramework: true, @@ -20,7 +12,7 @@ const INITIAL_STATE: SpanTreeState = { groupRevealCounts: {}, }; -/** First failed span if the run has errors (with its tree path opened), otherwise the root agent. */ +/** The linked step when one is given, otherwise the root agent with nested agent branches folded. */ export function initialRunSelection( trace: Trace, initialSpanId?: string, @@ -30,22 +22,14 @@ export function initialRunSelection( const state = revealSpanInState(trace.spans, { ...INITIAL_STATE, hideFramework: false }, selectedId); return { selectedId, state }; } - const initialState = { + const root = trace.spans.find((span) => span.parent_span_id === null); + const state = { ...INITIAL_STATE, collapsedSpanIds: new Set( trace.spans.filter((span) => span.type === "agent" && span.parent_span_id !== null).map((span) => span.span_id), ), }; - const failed = firstErrorSpan(trace.spans); - if (!failed || failed.parent_span_id === null) { - const root = trace.spans.find((s) => s.parent_span_id === null); - return { selectedId: root?.span_id ?? "", state: initialState }; - } - const visibleFailure = trace.spans - .filter((s) => s.status === "error" && s.parent_span_id !== null && !isFrameworkSpan(s)) - .sort((a, b) => a.start_offset_ms - b.start_offset_ms)[0]; - const selectedId = visibleFailure?.span_id ?? nearestVisibleSpanId(trace.spans, failed.span_id, true); - return { selectedId, state: revealSpanInState(trace.spans, initialState, selectedId) }; + return { selectedId: root?.span_id ?? "", state }; } const toggle = (set: ReadonlySet, id: string): Set => { diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.test.tsx b/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.test.tsx index b2a8111656f..d6c75700f62 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.test.tsx +++ b/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.test.tsx @@ -183,6 +183,26 @@ describe("AgentTracesTable input cell", () => { ); expect(screen.getByText("CURRENT USER REQUEST: - add feedback to Lens")).toBeInTheDocument(); }); + + it("says no input was recorded instead of passing the run's model name off as its input", () => { + const template = (traceList as TracePage).data[0] as TraceSummary; + const run = { ...template, name: "chat openai/gpt-6-astra", input_preview: "" }; + renderWithProviders( + inList( + , + ), + ); + expect(screen.getByText("No input recorded")).toBeInTheDocument(); + expect(screen.queryByText(run.name)).not.toBeInTheDocument(); + }); }); describe("AgentTracesTable column picker", () => { diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.tsx b/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.tsx index fa4051a34a4..cb39eea63e0 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.tsx +++ b/ui/litellm-dashboard/src/components/lens/traces/list/AgentTracesTable.tsx @@ -20,7 +20,7 @@ import { SignalPills } from "../ui/SignalPills"; import { FrameworkLogo, traceFramework } from "../ui/TraceFramework"; import type { TraceSummary } from "../types"; import { traceRefOf } from "../routing"; -import { fmtMs, previewText, traceDisplayName, traceAgentNames } from "../utils"; +import { fmtMs, previewText, traceAgentNames } from "../utils"; interface AgentTracesTableProps { traces: TraceSummary[]; @@ -103,11 +103,14 @@ function AgentCell({ run }: { run: TraceSummary }) { } function InputCell({ run }: { run: TraceSummary }) { + const input = singleLine(previewText(run.input_preview)); return (
- - {singleLine(previewText(run.input_preview)) || traceDisplayName(run)} - + {input ? ( + {input} + ) : ( + No input recorded + )} {run.resolution_limited && ( { describe("revealSpanInState", () => { it("opens the path to a span nested in a folded group so the view can land on it", () => { - const failed = firstErrorSpan(swarm.spans) as Span; + const failed = swarm.spans.find((s) => s.status === "error" && s.parent_span_id !== null) as Span; const state = revealSpanInState(swarm.spans, STATE, failed.span_id); const rows = buildTreeRows(swarm.spans, state); expect(rows.some((r) => r.id === failed.span_id)).toBe(true); @@ -345,13 +344,6 @@ describe("errorSource", () => { }); describe("payload helpers", () => { - it("finds the earliest failing non-root span", () => { - const failed = firstErrorSpan(swarm.spans); - expect(failed?.status).toBe("error"); - expect(failed?.parent_span_id).not.toBeNull(); - expect(firstErrorSpan(deepAgent.spans)).toBeNull(); - }); - it("parses llm message payloads and rejects non-message JSON", () => { expect(parseMessages('[{"role":"user","content":"hi"}]')).toEqual([{ role: "user", content: "hi" }]); expect(parseMessages('{"file_path":"/tmp/x"}')).toBeNull(); diff --git a/ui/litellm-dashboard/src/components/lens/traces/utils.ts b/ui/litellm-dashboard/src/components/lens/traces/utils.ts index 63ae58486cc..d4e5abf1cfc 100644 --- a/ui/litellm-dashboard/src/components/lens/traces/utils.ts +++ b/ui/litellm-dashboard/src/components/lens/traces/utils.ts @@ -254,16 +254,6 @@ export function nearestVisibleSpanId(spans: readonly Span[], spanId: string, hid return current?.span_id ?? spanId; } -/* ------------------------------------------------------------------ */ -/* Trace-level rollups */ -/* ------------------------------------------------------------------ */ - -/** Earliest failing non-root span (the root just echoes its children), else the root. */ -export function firstErrorSpan(spans: readonly Span[]): Span | null { - const failed = spans.filter((s) => s.status === "error").sort(byStart); - return failed.find((s) => s.parent_span_id !== null) ?? failed[0] ?? null; -} - /* ------------------------------------------------------------------ */ /* Span detail payloads */ /* ------------------------------------------------------------------ */