+
{checkHistory &&
}
@@ -186,17 +191,31 @@ export function AgentTracesSection({
)}
+ 0} live={live} onSetup={() => setShowSetup(true)} />
+
{timeControls && (
- changeRange(hours, timeControls.onRangeHoursChange)}
- live={isLiveTail}
- showLive={live}
- onLiveChange={timeControls.onLiveChange}
- />
+
+ changeRange(hours, timeControls.onRangeHoursChange)}
+ live={isLiveTail}
+ showLive={live}
+ onLiveChange={timeControls.onLiveChange}
+ />
+
)}
+
setShowSetup(true)}
/>
+
);
@@ -222,3 +242,33 @@ function TracesReceived({ received }: { received: boolean }) {
);
}
+
+function TraceCounts({ runs }: { runs: readonly TraceSummary[] }) {
+ const failed = runs.filter((run) => run.error_count > 0).length;
+ return (
+
+
+ {runs.length} {runs.length === 1 ? "run" : "runs"} from {new Set(runs.flatMap(traceAgentNames)).size} agents
+
+ {failed > 0 && {failed} with errors}
+
+ );
+}
+
+function TraceFooter({ runs, hasMore }: { runs: readonly TraceSummary[]; hasMore: boolean }) {
+ return (
+
+ );
+}
+
+function TracingSetupAction({ available, live, onSetup }: { available: boolean; live: boolean; onSetup: () => void }) {
+ if (!available) return null;
+ return (
+
+ );
+}
diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.test.tsx b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.test.tsx
index f4da4b49cdc..0b5b915ce69 100644
--- a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.test.tsx
+++ b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.test.tsx
@@ -12,7 +12,7 @@ describe("RunSearch", () => {
it("offers the run fields, then the agents seen in the loaded runs", async () => {
const user = userEvent.setup();
render(
);
- expect(screen.getByText("Search runs, or filter like agent:researcher status:error")).toBeVisible();
+ expect(screen.getByText("Search input or trace ID")).toBeVisible();
await user.click(box());
expect(
within(listbox())
diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.tsx b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.tsx
index 03a9e20e285..bf07ad61a78 100644
--- a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.tsx
+++ b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunSearch.tsx
@@ -31,11 +31,11 @@ export function RunSearch({ value, onChange, runs, range }: RunSearchProps) {
value={value}
onValueChange={onChange}
label="Search runs"
- className="h-11 min-w-0"
+ className="min-w-40 basis-full sm:basis-72 sm:max-w-96"
>
diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunsToolbar.tsx b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunsToolbar.tsx
index 01fabdff0e4..67357e70a30 100644
--- a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunsToolbar.tsx
+++ b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/RunsToolbar.tsx
@@ -3,6 +3,9 @@
import type { TraceSummary } from "../../types";
import type { TimeWindow } from "@/components/shared/timeline/Timeline";
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
+import { useRunFilterRouting } from "../../routing";
+import { traceAgentNames } from "../../utils";
import { RunSearch } from "./RunSearch";
interface RunsToolbarProps {
@@ -16,16 +19,50 @@ interface RunsToolbarProps {
}
export function RunsToolbar({ query, onQueryChange, runs, range, children }: RunsToolbarProps) {
+ const { agent, status, setAgent, setStatus } = useRunFilterRouting();
+ const agents = [
+ { value: "", label: "All agents" },
+ ...[...new Set([...runs.flatMap(traceAgentNames), ...(agent ? [agent] : [])])]
+ .sort()
+ .map((name) => ({ value: name, label: name })),
+ ];
+ const statuses = [
+ { value: "all", label: "All status" },
+ { value: "ok", label: "No errors" },
+ { value: "error", label: "With errors" },
+ ];
return (
-
-
-
- {children && (
-
- {children}
-
- )}
-
+
+
+
+
+ {children &&
{children}
}
);
}
diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.test.ts b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.test.ts
index d30b8afe382..40fd4afd4db 100644
--- a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.test.ts
+++ b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.test.ts
@@ -45,3 +45,10 @@ describe("RUN_INDEX values", () => {
expect(fieldValues(RUN_INDEX, [run({ models: [] })], "model")).toEqual([]);
});
});
+
+it("combines quick filters with search and treats agent names literally", () => {
+ const selected = filterRuns(runs, "vector", { agent: "researcher", status: "error" });
+ expect(selected.map((run) => run.trace_id)).toEqual(["bbb222"]);
+ expect(filterRuns(runs, "vector", { agent: "triage", status: "all" })).toEqual([]);
+ expect(filterRuns(runs, "", { agent: "research*", status: "all" })).toEqual([]);
+});
diff --git a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.ts b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.ts
index 3a69345797c..cd235b669e5 100644
--- a/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.ts
+++ b/ui/litellm-dashboard/src/components/lens/traces/list/runSearch/runQuery.ts
@@ -32,5 +32,13 @@ export const RUN_INDEX: ClientIndex
= {
freeText: (run) => [run.trace_id, previewText(run.input_preview), run.name],
};
-export const filterRuns = (runs: TraceSummary[], query: string): TraceSummary[] =>
- filterItems(RUN_QUERY, RUN_INDEX, runs, query);
+export function filterRuns(
+ runs: TraceSummary[],
+ query: string,
+ filters: { agent: string; status: "all" | "ok" | "error" } = { agent: "", status: "all" },
+): TraceSummary[] {
+ const matchesAgent = (run: TraceSummary) => !filters.agent || traceAgentNames(run).includes(filters.agent);
+ const matchesStatus = (run: TraceSummary) =>
+ filters.status === "all" || (run.error_count > 0 ? "error" : "ok") === filters.status;
+ return filterItems(RUN_QUERY, RUN_INDEX, runs, query).filter((run) => matchesAgent(run) && matchesStatus(run));
+}
diff --git a/ui/litellm-dashboard/src/components/lens/traces/routing.ts b/ui/litellm-dashboard/src/components/lens/traces/routing.ts
index 1e4ecccd218..3fd88699af6 100644
--- a/ui/litellm-dashboard/src/components/lens/traces/routing.ts
+++ b/ui/litellm-dashboard/src/components/lens/traces/routing.ts
@@ -59,6 +59,8 @@ export const OPEN_TRACE_PARSERS = {
export const RUN_FILTER_PARSERS = {
q: parseAsString.withDefault(""),
+ agent: parseAsString.withDefault(""),
+ status: parseAsStringLiteral(["all", "ok", "error"]).withDefault("all"),
hours: parseAsNumberLiteral(RANGE_HOURS).withDefault(DEFAULT_RANGE_HOURS),
from: parseAsInteger,
to: parseAsInteger,
@@ -139,9 +141,16 @@ export function useLocalRunSelection(initialSpanId: string | null): RunSelection
return { spanId, view, spanTab, stepQuery, errorsOnly, selectSpan, setView, setSpanTab, setStepQuery, setErrorsOnly };
}
-export function useRunFilterRouting(): { query: string; setQuery: (query: string) => void } {
- const [query, setQuery] = useQueryState("q", RUN_FILTER_PARSERS.q);
- return { query, setQuery: useCallback((q: string) => void setQuery(q), [setQuery]) };
+export function useRunFilterRouting() {
+ const [{ q, agent, status }, setParams] = useQueryStates(RUN_FILTER_PARSERS);
+ return {
+ query: q,
+ agent,
+ status,
+ setQuery: (query: string) => void setParams({ q: query }),
+ setAgent: (agent: string) => void setParams({ agent }),
+ setStatus: (status: "all" | "ok" | "error") => void setParams({ status }),
+ };
}
export function useRangeHoursRouting(): [number, (hours: number) => void] {
diff --git a/ui/litellm-dashboard/src/components/lens/ui/frame.ts b/ui/litellm-dashboard/src/components/lens/ui/frame.ts
deleted file mode 100644
index d42b4894ec3..00000000000
--- a/ui/litellm-dashboard/src/components/lens/ui/frame.ts
+++ /dev/null
@@ -1,38 +0,0 @@
-import { cva } from "@/lib/cva.config";
-
-export const frameCard = cva(
- "flex min-h-0 flex-1 flex-col overflow-hidden rounded-b-2xl bg-card @min-[24rem]/lens-frame:rounded-t-2xl",
- {
- variants: {
- session: {
- live: "border border-t-0 border-foreground/15 @min-[24rem]/lens-frame:border-t",
- demo: "border-2 border-t-0 border-info @min-[24rem]/lens-frame:border-t-2",
- },
- },
- defaultVariants: { session: "live" },
- },
-);
-
-export const frameTab = cva("relative z-raised rounded-t-2xl bg-card", {
- variants: {
- session: {
- live: "border-x border-t border-foreground/15 @min-[24rem]/lens-frame:-mb-px",
- demo: "border-x-2 border-t-2 border-info @min-[24rem]/lens-frame:-mb-0.5",
- },
- },
- defaultVariants: { session: "live" },
-});
-
-export const frameCorner = cva("block size-full shadow-[0_0_0_12px_var(--card)]", {
- variants: {
- session: { live: "border-foreground/15", demo: "border-info" },
- side: { left: "rounded-br-xl", right: "rounded-bl-xl" },
- },
- compoundVariants: [
- { session: "live", side: "left", className: "border-r border-b" },
- { session: "live", side: "right", className: "border-l border-b" },
- { session: "demo", side: "left", className: "border-r-2 border-b-2" },
- { session: "demo", side: "right", className: "border-l-2 border-b-2" },
- ],
- defaultVariants: { session: "live" },
-});
diff --git a/ui/litellm-dashboard/src/lib/http/schema.d.ts b/ui/litellm-dashboard/src/lib/http/schema.d.ts
index bc407c30c70..401439f6e63 100644
--- a/ui/litellm-dashboard/src/lib/http/schema.d.ts
+++ b/ui/litellm-dashboard/src/lib/http/schema.d.ts
@@ -9283,6 +9283,23 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/lens/{lens_id}/runs/{job_id}/reviews": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Read Reviews */
+ get: operations["read_reviews_lens__lens_id__runs__job_id__reviews_get"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/liteadmin/slack/connect/{token}": {
parameters: {
query?: never;
@@ -33006,6 +33023,20 @@ export interface components {
} & {
[key: string]: unknown;
};
+ /** InFlight */
+ InFlight: {
+ /** Agent */
+ agent: string;
+ /** Execution Id */
+ execution_id: string;
+ /**
+ * Started At
+ * Format: date-time
+ */
+ started_at: string;
+ /** Trace Id */
+ trace_id: string;
+ };
/**
* IncompleteDetails
* @description Details about why the response is incomplete.
@@ -33314,6 +33345,21 @@ export interface components {
id: string;
/** Lease Until */
lease_until?: string | null;
+ /**
+ * Reading
+ * @default []
+ */
+ reading: components["schemas"]["InFlight"][];
+ /**
+ * Reviewed
+ * @default 0
+ */
+ reviewed: number;
+ /**
+ * Reviews
+ * @default []
+ */
+ reviews: components["schemas"]["Review"][];
/** Revision */
revision: number;
sample?: components["schemas"]["Sample"] | null;
@@ -40908,6 +40954,9 @@ export interface components {
* }
*/
coverage: components["schemas"]["Coverage"];
+ /** Reading */
+ reading?: components["schemas"]["InFlight"][] | null;
+ review?: components["schemas"]["Review"] | null;
/** Stage */
stage: string;
};
@@ -43671,6 +43720,81 @@ export interface components {
/** Timeouterrorretries */
TimeoutErrorRetries?: number | null;
};
+ /** Review */
+ Review: {
+ /** Agent */
+ agent: string;
+ /**
+ * At
+ * Format: date-time
+ */
+ at: string;
+ /**
+ * Cannot Assess
+ * @default false
+ */
+ cannot_assess: boolean;
+ /** Duration Ms */
+ duration_ms: number;
+ /** Execution Id */
+ execution_id: string;
+ /** Model */
+ model: string;
+ /** Name */
+ name: string;
+ /**
+ * Reasoning
+ * @default
+ */
+ reasoning: string;
+ /**
+ * Spans
+ * @default []
+ */
+ spans: components["schemas"]["ReviewSpan"][];
+ /** Trace Id */
+ trace_id: string;
+ /**
+ * Verdicts
+ * @default []
+ */
+ verdicts: components["schemas"]["ReviewVerdict"][];
+ };
+ /** ReviewPage */
+ ReviewPage: {
+ /** Reviewed */
+ reviewed: number;
+ /** Reviews */
+ reviews: components["schemas"]["Review"][];
+ };
+ /** ReviewSpan */
+ ReviewSpan: {
+ /**
+ * Cited
+ * @default false
+ */
+ cited: boolean;
+ /** Kind */
+ kind: string;
+ /** Name */
+ name: string;
+ /** Preview */
+ preview: string;
+ /** Span Id */
+ span_id: string;
+ };
+ /** ReviewVerdict */
+ ReviewVerdict: {
+ /** Check Id */
+ check_id: string;
+ /**
+ * Kind
+ * @enum {string}
+ */
+ kind: "issue" | "pattern";
+ /** Summary */
+ summary: string;
+ };
/**
* RoleMappings
* @description Configuration for mapping SSO groups to LiteLLM roles.
@@ -62676,6 +62800,40 @@ export interface operations {
};
};
};
+ read_reviews_lens__lens_id__runs__job_id__reviews_get: {
+ parameters: {
+ query?: {
+ after?: number;
+ };
+ header?: never;
+ path: {
+ lens_id: string;
+ job_id: string;
+ };
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Successful Response */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["ReviewPage"];
+ };
+ };
+ /** @description Validation Error */
+ 422: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ "application/json": components["schemas"]["HTTPValidationError"];
+ };
+ };
+ };
+ };
connect_page_liteadmin_slack_connect__token__get: {
parameters: {
query?: never;