mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-05 02:41:56 +00:00
fix(ui): anchor guardrail lifecycle on timed entries and show not_run skip reason
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
4be84a859d
commit
e41faf54a8
3 changed files with 62 additions and 14 deletions
|
|
@ -49,17 +49,55 @@ describe("GuardrailViewer", () => {
|
|||
expect(screen.queryByText("FAILED")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders not_run as NOT RUN (muted) and keeps it out of the evaluated and passed counts", () => {
|
||||
const data = makeGuardrailInformation({ guardrail_status: "not_run", guardrail_mode: "pre_call" });
|
||||
it("renders not_run as NOT RUN (muted) and keeps it out of the evaluated and passed counts", async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeGuardrailInformation({
|
||||
guardrail_status: "not_run",
|
||||
guardrail_mode: "pre_call",
|
||||
guardrail_response: "no scannable content after message scoping",
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
duration: null,
|
||||
});
|
||||
renderWithProviders(<GuardrailViewer data={data} />);
|
||||
|
||||
expect(screen.getByText(/0 guardrails evaluated/)).toBeInTheDocument();
|
||||
expect(screen.getByText(/0 Passed/)).toHaveClass("text-muted-foreground");
|
||||
expect(screen.getByText(/1 Not run/)).toBeInTheDocument();
|
||||
const badges = screen.getAllByText("NOT RUN");
|
||||
expect(badges).toHaveLength(2);
|
||||
expect(badges[0]).toHaveClass("text-muted-foreground");
|
||||
const badge = screen.getByText("NOT RUN");
|
||||
expect(badge).toHaveClass("text-muted-foreground");
|
||||
expect(screen.queryByText("FAILED")).not.toBeInTheDocument();
|
||||
expect(screen.queryByText(/^T\+/)).not.toBeInTheDocument();
|
||||
|
||||
await user.click(screen.getByText("pii-rail"));
|
||||
expect(screen.getByText("no scannable content after message scoping")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("anchors the lifecycle timeline on timed entries when an untimed not_run entry sorts first", () => {
|
||||
const skipped = makeGuardrailInformation({
|
||||
guardrail_name: "skipped-rail",
|
||||
guardrail_status: "not_run",
|
||||
guardrail_mode: "pre_call",
|
||||
start_time: null,
|
||||
end_time: null,
|
||||
duration: null,
|
||||
});
|
||||
const ran = makeGuardrailInformation({
|
||||
guardrail_name: "ran-rail",
|
||||
guardrail_status: "success",
|
||||
guardrail_mode: "post_call",
|
||||
start_time: 1_700_000_000,
|
||||
end_time: 1_700_000_000.25,
|
||||
duration: 0.25,
|
||||
});
|
||||
renderWithProviders(<GuardrailViewer data={[skipped, ran]} />);
|
||||
|
||||
expect(screen.getByText(/1 guardrail evaluated/)).toBeInTheDocument();
|
||||
expect(screen.getByText("Request received").parentElement).toHaveTextContent("T+0ms");
|
||||
expect(screen.getByText(/Post-call guardrail: ran-rail/).parentElement).toHaveTextContent("T+250ms");
|
||||
expect(screen.getByText("Response returned").parentElement).toHaveTextContent("T+251ms");
|
||||
expect(screen.queryByText(/Pre-call guardrail: skipped-rail/)).not.toBeInTheDocument();
|
||||
expect(screen.getByText("—")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("calculates and displays masked entity totals", async () => {
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ interface MatchDetail {
|
|||
}
|
||||
|
||||
interface GuardrailInformation {
|
||||
duration: number;
|
||||
end_time: number;
|
||||
start_time: number;
|
||||
duration: number | null;
|
||||
end_time: number | null;
|
||||
start_time: number | null;
|
||||
guardrail_mode: string | string[] | Record<string, unknown> | null;
|
||||
guardrail_name: string;
|
||||
guardrail_status: string;
|
||||
|
|
@ -121,7 +121,8 @@ const formatMode = (mode: GuardrailInformation["guardrail_mode"]): string => {
|
|||
return s.replace(/_/g, "-").toUpperCase();
|
||||
};
|
||||
|
||||
const formatDurationMs = (seconds: number): string => {
|
||||
const formatDurationMs = (seconds: number | null): string => {
|
||||
if (seconds == null) return "—";
|
||||
const ms = Math.round(seconds * 1000);
|
||||
return `${ms}ms`;
|
||||
};
|
||||
|
|
@ -364,8 +365,13 @@ interface TimelineEntry {
|
|||
outcome?: EntryOutcome;
|
||||
}
|
||||
|
||||
type TimedGuardrailInformation = GuardrailInformation & { start_time: number; end_time: number };
|
||||
|
||||
const isTimed = (e: GuardrailInformation): e is TimedGuardrailInformation =>
|
||||
typeof e.start_time === "number" && typeof e.end_time === "number";
|
||||
|
||||
const RequestLifecycle = ({ entries }: { entries: GuardrailInformation[] }) => {
|
||||
const sorted = useMemo(() => [...entries].sort((a, b) => (a.start_time ?? 0) - (b.start_time ?? 0)), [entries]);
|
||||
const sorted = useMemo(() => entries.filter(isTimed).sort((a, b) => a.start_time - b.start_time), [entries]);
|
||||
|
||||
const timeline = useMemo(() => {
|
||||
if (sorted.length === 0) return [];
|
||||
|
|
@ -669,6 +675,10 @@ const EvaluationCard = ({ entry }: { entry: GuardrailInformation }) => {
|
|||
</div>
|
||||
)}
|
||||
|
||||
{outcome === "not_run" && typeof guardrailResponse === "string" && (
|
||||
<p className="text-sm text-muted-foreground">{guardrailResponse}</p>
|
||||
)}
|
||||
|
||||
{/* Provider-specific details */}
|
||||
{guardrailProvider === "presidio" && presidioEntities.length > 0 && (
|
||||
<div className="mt-3">
|
||||
|
|
|
|||
|
|
@ -20,13 +20,13 @@ export interface GuardrailEntity {
|
|||
}
|
||||
|
||||
export interface GuardrailInformation {
|
||||
duration: number;
|
||||
end_time: number;
|
||||
start_time: number;
|
||||
duration: number | null;
|
||||
end_time: number | null;
|
||||
start_time: number | null;
|
||||
guardrail_mode: string | string[] | Record<string, unknown> | null;
|
||||
guardrail_name: string;
|
||||
guardrail_status: string;
|
||||
guardrail_response: GuardrailEntity[] | BedrockGuardrailResponse;
|
||||
guardrail_response: GuardrailEntity[] | BedrockGuardrailResponse | string;
|
||||
masked_entity_count: Record<string, number>;
|
||||
guardrail_usage?: Record<string, number>;
|
||||
guardrail_cost?: number;
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue