mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
fix(ui): logs guardrail mode type crash on non-string values
Cherry-pick of PR #24035. Fixes crash when guardrail_mode is null, array, or object by adding a resolveMode() helper that handles all shapes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e738405906
commit
8e3ef6ba11
3 changed files with 86 additions and 9 deletions
|
|
@ -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(<GuardrailViewer data={data} />);
|
||||
|
||||
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(<GuardrailViewer data={data} />);
|
||||
|
||||
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(<GuardrailViewer data={data} />);
|
||||
|
||||
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({
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ interface GuardrailInformation {
|
|||
duration: number;
|
||||
end_time: number;
|
||||
start_time: number;
|
||||
guardrail_mode: string;
|
||||
guardrail_mode: string | string[] | Record<string, unknown> | 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);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export interface GuardrailInformation {
|
|||
duration: number;
|
||||
end_time: number;
|
||||
start_time: number;
|
||||
guardrail_mode: string;
|
||||
guardrail_mode: string | string[] | Record<string, unknown> | null;
|
||||
guardrail_name: string;
|
||||
guardrail_status: string;
|
||||
guardrail_response: GuardrailEntity[] | BedrockGuardrailResponse;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue