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 28991ebfa03..b5e04c72440 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
@@ -151,6 +151,39 @@ describe("GuardrailViewer", () => {
expect(screen.queryByText(/Raw Bedrock Guardrail Response/)).not.toBeInTheDocument();
});
+ it("renders without crashing when guardrail_mode is null", () => {
+ const data = makeGuardrailInformation({ guardrail_mode: null });
+ renderWithProviders();
+
+ expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
+ // Null mode should display as dash
+ expect(screen.getByText("—")).toBeInTheDocument();
+ });
+
+ it("renders without crashing when guardrail_mode is an object", () => {
+ const data = makeGuardrailInformation({
+ guardrail_mode: { default: "pre_call", tags: {} },
+ });
+ renderWithProviders();
+
+ expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
+ expect(screen.getByText("PRE-CALL")).toBeInTheDocument();
+ });
+
+ it("renders without crashing when guardrail_mode is an array and shows in both timeline buckets", () => {
+ const data = makeGuardrailInformation({
+ guardrail_mode: ["pre_call", "post_call"],
+ });
+ renderWithProviders();
+
+ expect(screen.getByText("Guardrails & Policy Compliance")).toBeInTheDocument();
+ // Mode badge shows first element formatted
+ expect(screen.getByText("PRE-CALL")).toBeInTheDocument();
+ // Entry should appear in both pre-call and post-call timeline sections
+ expect(screen.getByText(/Pre-call guardrail:/)).toBeInTheDocument();
+ expect(screen.getByText(/Post-call guardrail:/)).toBeInTheDocument();
+ });
+
it("integration: renders with real Bedrock details without mocks", 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 1ab4744b893..5608e3677fa 100644
--- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx
+++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/GuardrailViewer.tsx
@@ -40,7 +40,7 @@ interface GuardrailInformation {
duration: number;
end_time: number;
start_time: number;
- guardrail_mode: string;
+ guardrail_mode: string | string[] | Record | null;
guardrail_name: string;
guardrail_status: string;
guardrail_response: GuardrailEntity[] | BedrockGuardrailResponse | any;
@@ -77,9 +77,50 @@ const PROVIDERS_WITH_CUSTOM_RENDERERS = new Set([
"litellm_content_filter",
]);
-const formatMode = (mode: unknown): string => {
- if (mode == null || mode === "") return "—";
- const s = typeof mode === "string" ? mode : String(mode);
+/**
+ * Extracts a plain string from guardrail_mode for display purposes.
+ * Returns the first mode when multiple are present.
+ */
+const resolveMode = (mode: GuardrailInformation["guardrail_mode"]): string | null => {
+ if (mode == null) return null;
+ if (typeof mode === "string") return mode;
+ if (Array.isArray(mode)) {
+ const first = mode[0];
+ return typeof first === "string" ? first : null;
+ }
+ if (typeof mode === "object" && "default" in mode) {
+ const def = mode.default;
+ if (typeof def === "string") return def;
+ if (Array.isArray(def)) {
+ const first = def[0];
+ return typeof first === "string" ? first : null;
+ }
+ }
+ return null;
+};
+
+/**
+ * Checks whether guardrail_mode includes the given target stage.
+ * Handles arrays (multi-stage guardrails) by checking all elements.
+ */
+const modeMatches = (
+ mode: GuardrailInformation["guardrail_mode"],
+ target: string,
+): boolean => {
+ if (mode == null) return false;
+ if (typeof mode === "string") return mode === target;
+ if (Array.isArray(mode)) return mode.includes(target);
+ if (typeof mode === "object" && "default" in mode) {
+ const def = mode.default;
+ if (typeof def === "string") return def === target;
+ if (Array.isArray(def)) return def.some((x) => typeof x === "string" && x === target);
+ }
+ return false;
+};
+
+const formatMode = (mode: GuardrailInformation["guardrail_mode"]): string => {
+ const s = resolveMode(mode);
+ if (s == null || s === "") return "—";
return s.replace(/_/g, "-").toUpperCase();
};
@@ -301,10 +342,13 @@ const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => {
// Request received
items.push({ type: "request", label: "Request received", offsetMs: 0 });
- // Pre-call guardrails
- const preCalls = sorted.filter((e) => e.guardrail_mode === "pre_call");
- const postCalls = sorted.filter((e) => e.guardrail_mode === "post_call" || e.guardrail_mode === "logging_only");
- const duringCalls = sorted.filter((e) => e.guardrail_mode === "during_call");
+ // Pre-call guardrails — use modeMatches so array modes (e.g. ["pre_call", "post_call"])
+ // place the entry in every matching bucket.
+ const preCalls = sorted.filter((e) => modeMatches(e.guardrail_mode, "pre_call"));
+ const postCalls = sorted.filter(
+ (e) => modeMatches(e.guardrail_mode, "post_call") || modeMatches(e.guardrail_mode, "logging_only"),
+ );
+ const duringCalls = sorted.filter((e) => modeMatches(e.guardrail_mode, "during_call"));
for (const e of preCalls) {
const offsetMs = Math.round((e.end_time - baseTime) * 1000);
diff --git a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/__tests__/fixtures.ts b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/__tests__/fixtures.ts
index 3c78aacf850..fc487b04d7e 100644
--- a/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/__tests__/fixtures.ts
+++ b/ui/litellm-dashboard/src/components/view_logs/GuardrailViewer/__tests__/fixtures.ts
@@ -23,7 +23,7 @@ export interface GuardrailInformation {
duration: number;
end_time: number;
start_time: number;
- guardrail_mode: string;
+ guardrail_mode: string | string[] | Record | null;
guardrail_name: string;
guardrail_status: string;
guardrail_response: GuardrailEntity[] | BedrockGuardrailResponse;