mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(lens-ui): open traces on the top-level step and stop showing model names as input (#45254)
* fix(lens-ui): open a trace on its top-level step instead of the first failed one Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(lens-ui): pin that failed runs open on the root step Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * refactor(lens-ui): drop the unused firstErrorSpan helper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(lens-ui): stop testing the removed firstErrorSpan helper Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(lens-ui): stop showing the model name as a run's input when none was recorded Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(lens-ui): cover the trace list row with no recorded input Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
4f49f88172
commit
e4d9a591dd
6 changed files with 50 additions and 85 deletions
|
|
@ -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> = { span_id: "mw", type: "framework", status: "error", start_offset_ms: 1 };
|
||||
const toolFields: Partial<Span> = { 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> = { 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> = { 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> = { span_id: "agent", type: "agent" };
|
||||
const failureFields: Partial<Span> = { 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));
|
||||
|
|
|
|||
|
|
@ -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<string>, id: string): Set<string> => {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
<AgentTracesTable
|
||||
traces={[run]}
|
||||
findings={new Map()}
|
||||
isLoading={false}
|
||||
error={null}
|
||||
hasMore={false}
|
||||
onLoadMore={vi.fn()}
|
||||
onSetUpTracing={vi.fn()}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(screen.getByText("No input recorded")).toBeInTheDocument();
|
||||
expect(screen.queryByText(run.name)).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe("AgentTracesTable column picker", () => {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<div className="flex min-w-0 items-center gap-2">
|
||||
<span className="truncate text-foreground">
|
||||
{singleLine(previewText(run.input_preview)) || traceDisplayName(run)}
|
||||
</span>
|
||||
{input ? (
|
||||
<span className="truncate text-foreground">{input}</span>
|
||||
) : (
|
||||
<span className="truncate text-muted-foreground">No input recorded</span>
|
||||
)}
|
||||
{run.resolution_limited && (
|
||||
<span
|
||||
className="shrink-0 text-xs text-muted-foreground"
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import {
|
|||
buildTreeRows,
|
||||
buildVisibleTree,
|
||||
errorSource,
|
||||
firstErrorSpan,
|
||||
findTraceSteps,
|
||||
fmtMs,
|
||||
GROUP_PAGE_SIZE,
|
||||
|
|
@ -317,7 +316,7 @@ describe("buildTreeRows", () => {
|
|||
|
||||
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();
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue