From 45ff658c6f020665bbe997ba6fc93ebf7c4eb2e3 Mon Sep 17 00:00:00 2001 From: Yucheng Zhu Date: Tue, 1 Sep 2026 01:09:01 -0700 Subject: [PATCH] fix(ui): render not_run guardrail evaluations as not run instead of failed --- ui/litellm-dashboard/eslint-suppressions.json | 2 +- .../GuardrailViewer/GuardrailViewer.test.tsx | 10 +++ .../GuardrailViewer/GuardrailViewer.tsx | 71 +++++++++++++------ 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/ui/litellm-dashboard/eslint-suppressions.json b/ui/litellm-dashboard/eslint-suppressions.json index 7de7373b20b..93966c8d0ee 100644 --- a/ui/litellm-dashboard/eslint-suppressions.json +++ b/ui/litellm-dashboard/eslint-suppressions.json @@ -2352,7 +2352,7 @@ }, "src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx": { "no-nested-ternary": { - "count": 4 + "count": 3 } }, "src/components/view_logs/LogDetailsDrawer/LogDetailContent.tsx": { diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx index b5e04c72440..0fb5504d231 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.test.tsx @@ -33,6 +33,16 @@ describe("GuardrailViewer", () => { expect(screen.getByText("1235ms")).toBeInTheDocument(); }); + it("renders not_run entries as not run instead of failed", () => { + const data = makeGuardrailInformation({ guardrail_status: "not_run", guardrail_mode: "pre_call" }); + renderWithProviders(); + + expect(screen.getByText(/1 Not run/)).toBeInTheDocument(); + // one NOT RUN badge in the timeline, one in the evaluation card + expect(screen.getAllByText("NOT RUN")).toHaveLength(2); + expect(screen.queryByText("FAILED")).not.toBeInTheDocument(); + }); + it("calculates and displays masked entity totals", async () => { const user = userEvent.setup(); const data = makeGuardrailInformation({ diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx index 863f4117510..08f9002b050 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx @@ -137,6 +137,36 @@ const isEntrySuccess = (entry: GuardrailInformation): boolean => { return (entry.guardrail_status ?? "").toLowerCase() === "success"; }; +const isEntryNotRun = (entry: GuardrailInformation): boolean => { + return (entry.guardrail_status ?? "").toLowerCase() === "not_run"; +}; + +type EntryStatusLabel = "PASSED" | "NOT RUN" | "FAILED"; + +const entryStatusLabel = (entry: GuardrailInformation): EntryStatusLabel => { + if (isEntrySuccess(entry)) return "PASSED"; + if (isEntryNotRun(entry)) return "NOT RUN"; + return "FAILED"; +}; + +const StatusIcon = ({ status }: { status: EntryStatusLabel }) => { + if (status === "PASSED") return ; + if (status === "NOT RUN") return ; + return ; +}; + +const timelineStatusClass = (status: EntryStatusLabel): string => { + if (status === "PASSED") return "bg-success/15 text-success"; + if (status === "NOT RUN") return "bg-muted text-muted-foreground"; + return "bg-destructive/15 text-destructive"; +}; + +const cardStatusClass = (status: EntryStatusLabel): string => { + if (status === "PASSED") return "bg-success/15 text-success border border-success/20"; + if (status === "NOT RUN") return "bg-muted text-muted-foreground border border-border"; + return "bg-destructive/15 text-destructive border border-destructive/20"; +}; + const getRiskColor = (score: number): string => { if (score <= 3) return "text-success bg-success/10 border-success/20"; if (score <= 6) return "text-warning bg-warning/10 border-warning/20"; @@ -318,8 +348,7 @@ interface TimelineEntry { type: "request" | "guardrail" | "llm" | "response"; label: string; offsetMs: number; - status?: string; - isSuccess?: boolean; + status?: EntryStatusLabel; } const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { @@ -348,8 +377,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { type: "guardrail", label: `Pre-call guardrail: ${getDisplayName(e)}`, offsetMs, - status: isEntrySuccess(e) ? "PASSED" : "FAILED", - isSuccess: isEntrySuccess(e), + status: entryStatusLabel(e), }); } @@ -372,8 +400,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { type: "guardrail", label: `During-call guardrail: ${getDisplayName(e)}`, offsetMs, - status: isEntrySuccess(e) ? "PASSED" : "FAILED", - isSuccess: isEntrySuccess(e), + status: entryStatusLabel(e), }); } @@ -384,8 +411,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { type: "guardrail", label: `Post-call guardrail: ${getDisplayName(e)}`, offsetMs, - status: isEntrySuccess(e) ? "PASSED" : "FAILED", - isSuccess: isEntrySuccess(e), + status: entryStatusLabel(e), }); } @@ -410,10 +436,8 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { ) : item.type === "llm" ? ( - ) : item.isSuccess ? ( - ) : ( - + )} {idx < timeline.length - 1 &&
} @@ -427,9 +451,7 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => { {item.status && ( {item.status} @@ -456,6 +478,7 @@ const formatGuardrailCost = (cost: number): string => { const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { const [expanded, setExpanded] = useState(false); const success = isEntrySuccess(entry); + const statusLabel = entryStatusLabel(entry); const totalMasked = getTotalMasked(entry); const displayName = getDisplayName(entry); const durationStr = formatDurationMs(entry.duration); @@ -490,7 +513,9 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { onClick={() => setExpanded(!expanded)} > {/* Status icon */} -
{success ? : }
+
+ +
{/* Name + badges */}
@@ -501,13 +526,9 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => { - {success ? "PASSED" : "FAILED"} + {statusLabel} {matchCountStr && ( @@ -673,7 +694,8 @@ const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps) }, [data]); const passedCount = guardrailEntries.filter(isEntrySuccess).length; - const allPassed = passedCount === guardrailEntries.length; + const notRunCount = guardrailEntries.filter(isEntryNotRun).length; + const allPassed = passedCount === guardrailEntries.length - notRunCount; const totalOverheadMs = useMemo(() => { return Math.round(guardrailEntries.reduce((sum, e) => sum + (e.duration ?? 0), 0) * 1000); @@ -728,6 +750,11 @@ const GuardrailViewer = ({ data, accessToken, logEntry }: GuardrailViewerProps) ) : null} {passedCount} Passed + {notRunCount > 0 && ( + + {notRunCount} Not run + + )}