From d648e0fa4ae246e6d6e3e293f1fda7c46bb10817 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Mon, 16 Feb 2026 15:45:04 -0800 Subject: [PATCH] feat: add guardrail tracing UI - policy badges, match details, timeline --- .../GuardrailViewer/GuardrailViewer.tsx | 222 +++++++++++++++++- 1 file changed, 220 insertions(+), 2 deletions(-) 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 ed2198ba859..8f9869e4dd5 100644 --- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx +++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx @@ -24,6 +24,15 @@ interface MaskedEntityCount { [key: string]: number; } +interface MatchDetail { + type: string; + detection_method?: string; + action_taken?: string; + snippet?: string; + category?: string; + position?: number; +} + interface GuardrailInformation { duration: number; end_time: number; @@ -33,7 +42,15 @@ interface GuardrailInformation { guardrail_status: string; guardrail_response: GuardrailEntity[] | BedrockGuardrailResponse | any; masked_entity_count: MaskedEntityCount; - guardrail_provider?: string; // "presidio" | "bedrock" | "litellm_content_filter" | other providers + guardrail_provider?: string; + guardrail_id?: string; + policy_template?: string; + detection_method?: string; + confidence_score?: number; + classification?: Record; + match_details?: MatchDetail[]; + patterns_checked?: number; + alert_recipients?: string[]; } interface GuardrailViewerProps { @@ -87,6 +104,179 @@ const GenericGuardrailResponse = ({ response }: { response: any }) => { ); }; +const PolicyDetectionRow = ({ entry }: { entry: GuardrailInformation }) => { + const hasData = entry.policy_template || entry.detection_method || entry.confidence_score != null || entry.patterns_checked != null; + if (!hasData) return null; + + return ( +
+
+ {entry.policy_template && ( +
+ Policy: + + {entry.policy_template} + +
+ )} + {entry.detection_method && ( +
+ Detection: + {entry.detection_method.split(",").map((method) => ( + + {method.trim()} + + ))} +
+ )} + {entry.confidence_score != null && ( +
+ Confidence: + = 0.8 ? "bg-red-100 text-red-800" : + entry.confidence_score >= 0.5 ? "bg-amber-100 text-amber-800" : + "bg-green-100 text-green-800" + }`}> + {(entry.confidence_score * 100).toFixed(0)}% + +
+ )} + {entry.patterns_checked != null && ( +
+ Patterns checked: + {entry.patterns_checked} +
+ )} +
+
+ ); +}; + +const MatchDetailsTable = ({ matchDetails }: { matchDetails: MatchDetail[] }) => { + if (!matchDetails || matchDetails.length === 0) return null; + + return ( +
+
Match Details ({matchDetails.length})
+
+ + + + + + + + + + + {matchDetails.map((match, idx) => ( + + + + + + + ))} + +
TypeMethodActionDetail
{match.type} + + {match.detection_method ?? "-"} + + + + {match.action_taken ?? "-"} + + + {match.category ? `[${match.category}] ` : ""}{match.snippet ?? "-"} +
+
+
+ ); +}; + +const ClassificationDetails = ({ classification }: { classification: Record }) => { + if (!classification) return null; + + return ( +
+
Classification
+
+ {classification.category && ( +
+ Category: + {classification.category} +
+ )} + {classification.article_reference && ( +
+ Reference: + {classification.article_reference} +
+ )} + {classification.confidence != null && ( +
+ Confidence: + {(classification.confidence * 100).toFixed(0)}% +
+ )} + {classification.reason && ( +
+ Reason: + {classification.reason} +
+ )} +
+
+ ); +}; + +const ExecutionTimeline = ({ entries }: { entries: GuardrailInformation[] }) => { + if (entries.length <= 1) return null; + + const sorted = [...entries].sort((a, b) => (a.start_time ?? 0) - (b.start_time ?? 0)); + + return ( +
+
Execution Timeline
+
+ {sorted.map((e, idx) => { + const isSuccess = (e.guardrail_status ?? "").toLowerCase() === "success"; + return ( +
+
+
+ + {e.duration?.toFixed(3)}s + + {e.guardrail_name} + + {e.guardrail_mode} + + + {e.guardrail_status} + + {e.policy_template && ( + + {e.policy_template} + + )} +
+
+ ); + })} +
+
+ ); +}; + const GuardrailDetails = ({ entry, index, total }: GuardrailDetailsProps) => { const guardrailProvider = entry.guardrail_provider ?? "presidio"; const statusLabel = entry.guardrail_status ?? "unknown"; @@ -127,6 +317,12 @@ const GuardrailDetails = ({ entry, index, total }: GuardrailDetailsProps) => { Guardrail Name: {entry.guardrail_name}
+ {entry.guardrail_id && entry.guardrail_id !== entry.guardrail_name && ( +
+ Guardrail ID: + {entry.guardrail_id} +
+ )}
Mode: {entry.guardrail_mode} @@ -161,6 +357,17 @@ const GuardrailDetails = ({ entry, index, total }: GuardrailDetailsProps) => {
+ {/* Policy, detection method, confidence, patterns checked */} + + + {/* Classification details (LLM-judge) */} + {entry.classification && } + + {/* Match details table */} + {entry.match_details && entry.match_details.length > 0 && ( + + )} + {totalMaskedEntities > 0 && (
Masked Entity Summary
@@ -222,6 +429,10 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => { ); }, 0); + const policyTemplates = Array.from( + new Set(guardrailEntries.map((e) => e.policy_template).filter(Boolean)) + ); + const tooltipTitle = allSucceeded ? null : "Guardrail failed to run."; if (guardrailEntries.length === 0) { @@ -237,7 +448,7 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => { { key: "1", label: ( -
+

Guardrail Information

@@ -257,10 +468,17 @@ const GuardrailViewer = ({ data }: GuardrailViewerProps) => { {totalMaskedEntities} masked {totalMaskedEntities === 1 ? "entity" : "entities"} )} + + {policyTemplates.map((pt) => ( + + {pt} + + ))}
), children: (
+ {guardrailEntries.map((entry, index) => (