mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-07 02:59:05 +00:00
feat: enable running ai monitor on individual guardrails
This commit is contained in:
parent
9ea62fab04
commit
9947963c7d
5 changed files with 239 additions and 35 deletions
|
|
@ -549,7 +549,7 @@ function CreateKeyPageContent() {
|
|||
) : page == "vector-stores" ? (
|
||||
<VectorStoreManagement accessToken={accessToken} userRole={userRole} userID={userID} />
|
||||
) : page == "guardrails-monitor" ? (
|
||||
<GuardrailsMonitorView />
|
||||
<GuardrailsMonitorView accessToken={accessToken} />
|
||||
) : page == "new_usage" ? (
|
||||
<NewUsagePage
|
||||
teams={(teams as Team[]) ?? []}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,157 @@
|
|||
import { CloseOutlined, PlayCircleOutlined } from "@ant-design/icons";
|
||||
import { Button, Modal, Select, Input } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { fetchAvailableModels, type ModelGroup } from "@/components/playground/llm_calls/fetch_models";
|
||||
|
||||
const DEFAULT_PROMPT = `Evaluate whether this guardrail's decision was correct.
|
||||
Analyze the user input, the guardrail action taken, and determine if it was appropriate.
|
||||
|
||||
Consider:
|
||||
— Was the user's intent genuinely harmful or policy-violating?
|
||||
— Was the guardrail's action (block / flag / pass) appropriate?
|
||||
— Could this be a false positive or false negative?
|
||||
|
||||
Return a structured verdict with confidence and justification.`;
|
||||
|
||||
const DEFAULT_SCHEMA = `{
|
||||
"verdict": "correct" | "false_positive" | "false_negative",
|
||||
"confidence": 0.0,
|
||||
"justification": "string",
|
||||
"risk_category": "string",
|
||||
"suggested_action": "keep" | "adjust threshold" | "add allowlist"
|
||||
}
|
||||
`;
|
||||
|
||||
export interface EvaluationSettingsModalProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
guardrailName?: string;
|
||||
accessToken: string | null;
|
||||
onRunEvaluation?: (settings: { prompt: string; schema: string; model: string }) => void;
|
||||
}
|
||||
|
||||
export function EvaluationSettingsModal({
|
||||
open,
|
||||
onClose,
|
||||
guardrailName,
|
||||
accessToken,
|
||||
onRunEvaluation,
|
||||
}: EvaluationSettingsModalProps) {
|
||||
const [prompt, setPrompt] = useState(DEFAULT_PROMPT);
|
||||
const [schema, setSchema] = useState(DEFAULT_SCHEMA);
|
||||
const [model, setModel] = useState<string | null>(null);
|
||||
const [modelOptions, setModelOptions] = useState<ModelGroup[]>([]);
|
||||
const [loadingModels, setLoadingModels] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !accessToken) {
|
||||
setModelOptions([]);
|
||||
return;
|
||||
}
|
||||
let cancelled = false;
|
||||
setLoadingModels(true);
|
||||
fetchAvailableModels(accessToken)
|
||||
.then((list) => {
|
||||
if (!cancelled) setModelOptions(list);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setModelOptions([]);
|
||||
})
|
||||
.finally(() => {
|
||||
if (!cancelled) setLoadingModels(false);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [open, accessToken]);
|
||||
|
||||
const handleResetPrompt = () => setPrompt(DEFAULT_PROMPT);
|
||||
const handleRun = () => {
|
||||
if (model) {
|
||||
onRunEvaluation?.({ prompt, schema, model });
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const modelSelectOptions = modelOptions.map((m) => ({
|
||||
value: m.model_group,
|
||||
label: m.model_group,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Evaluation Settings"
|
||||
open={open}
|
||||
onCancel={onClose}
|
||||
width={640}
|
||||
footer={null}
|
||||
closeIcon={<CloseOutlined />}
|
||||
destroyOnClose
|
||||
>
|
||||
<p className="text-sm text-gray-500 mb-4">
|
||||
{guardrailName
|
||||
? `Configure AI evaluation for ${guardrailName}`
|
||||
: "Configure AI evaluation for re-running on logs"}
|
||||
</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<label className="text-sm font-medium text-gray-700">Evaluation Prompt</label>
|
||||
<button
|
||||
type="button"
|
||||
onClick={handleResetPrompt}
|
||||
className="text-xs text-indigo-600 hover:text-indigo-700"
|
||||
>
|
||||
Reset to default
|
||||
</button>
|
||||
</div>
|
||||
<Input.TextArea
|
||||
value={prompt}
|
||||
onChange={(e) => setPrompt(e.target.value)}
|
||||
rows={6}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
<p className="text-xs text-gray-400 mt-1">
|
||||
System prompt sent to the evaluation model. Output is structured via response_format.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1.5">
|
||||
Response Schema
|
||||
</label>
|
||||
<p className="text-xs text-gray-400 mb-1">response_format: json_schema</p>
|
||||
<Input.TextArea
|
||||
value={schema}
|
||||
onChange={(e) => setSchema(e.target.value)}
|
||||
rows={6}
|
||||
className="font-mono text-sm"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1.5">Model</label>
|
||||
<Select
|
||||
placeholder={loadingModels ? "Loading models…" : "Select a model"}
|
||||
value={model ?? undefined}
|
||||
onChange={setModel}
|
||||
options={modelSelectOptions}
|
||||
style={{ width: "100%" }}
|
||||
showSearch
|
||||
optionFilterProp="label"
|
||||
loading={loadingModels}
|
||||
notFoundContent={!accessToken ? "Sign in to see models" : "No models available"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center justify-end gap-2 mt-6 pt-4 border-t border-gray-100">
|
||||
<Button onClick={onClose}>Cancel</Button>
|
||||
<Button type="primary" icon={<PlayCircleOutlined />} onClick={handleRun} disabled={!model}>
|
||||
Run Evaluation
|
||||
</Button>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -3,19 +3,23 @@ import {
|
|||
BellOutlined,
|
||||
CheckOutlined,
|
||||
CloseOutlined,
|
||||
PlayCircleOutlined,
|
||||
SafetyOutlined,
|
||||
SettingOutlined,
|
||||
WarningOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Grid, Title } from "@tremor/react";
|
||||
import { Button, Input, Tabs } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { getGuardrailDetailOrDefault } from "./mockData";
|
||||
import { EvaluationSettingsModal } from "./EvaluationSettingsModal";
|
||||
import { LogViewer } from "./LogViewer";
|
||||
import { MetricCard } from "./MetricCard";
|
||||
|
||||
interface GuardrailDetailProps {
|
||||
guardrailId: string;
|
||||
onBack: () => void;
|
||||
accessToken?: string | null;
|
||||
}
|
||||
|
||||
const statusColors: Record<
|
||||
|
|
@ -27,10 +31,15 @@ const statusColors: Record<
|
|||
critical: { bg: "bg-red-50", text: "text-red-700", dot: "bg-red-500" },
|
||||
};
|
||||
|
||||
export function GuardrailDetail({ guardrailId, onBack }: GuardrailDetailProps) {
|
||||
export function GuardrailDetail({
|
||||
guardrailId,
|
||||
onBack,
|
||||
accessToken = null,
|
||||
}: GuardrailDetailProps) {
|
||||
const [activeTab, setActiveTab] = useState("overview");
|
||||
const [showNotifyPanel, setShowNotifyPanel] = useState(false);
|
||||
const [notifySaved, setNotifySaved] = useState(false);
|
||||
const [evaluationModalOpen, setEvaluationModalOpen] = useState(false);
|
||||
const [notifyConfig, setNotifyConfig] = useState({
|
||||
failRateThreshold: "",
|
||||
apiErrorThreshold: "",
|
||||
|
|
@ -77,6 +86,15 @@ export function GuardrailDetail({ guardrailId, onBack }: GuardrailDetailProps) {
|
|||
<span className="inline-flex items-center px-2.5 py-1 text-xs font-medium rounded-md bg-indigo-50 text-indigo-700 border border-indigo-200">
|
||||
{data.provider}
|
||||
</span>
|
||||
<Button type="default" icon={<PlayCircleOutlined />}>
|
||||
Re-run AI
|
||||
</Button>
|
||||
<Button
|
||||
type="default"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => setEvaluationModalOpen(true)}
|
||||
title="Evaluation settings"
|
||||
/>
|
||||
<div className="relative">
|
||||
<Button
|
||||
type={showNotifyPanel ? "primary" : "default"}
|
||||
|
|
@ -194,7 +212,7 @@ export function GuardrailDetail({ guardrailId, onBack }: GuardrailDetailProps) {
|
|||
|
||||
{activeTab === "overview" && (
|
||||
<div className="space-y-6 mt-4">
|
||||
<Grid numItems={2} numItemsMd={6} className="gap-4">
|
||||
<Grid numItems={2} numItemsMd={5} className="gap-4">
|
||||
<Col>
|
||||
<MetricCard label="Requests Evaluated" value={data.requestsEvaluated.toLocaleString()} />
|
||||
</Col>
|
||||
|
|
@ -205,6 +223,7 @@ export function GuardrailDetail({ guardrailId, onBack }: GuardrailDetailProps) {
|
|||
valueColor={
|
||||
data.failRate > 15 ? "text-red-600" : data.failRate > 5 ? "text-amber-600" : "text-green-600"
|
||||
}
|
||||
subtitle={`${Math.round((data.requestsEvaluated * data.failRate) / 100).toLocaleString()} blocked`}
|
||||
icon={data.failRate > 15 ? <WarningOutlined className="text-red-400" /> : undefined}
|
||||
/>
|
||||
</Col>
|
||||
|
|
@ -260,14 +279,6 @@ export function GuardrailDetail({ guardrailId, onBack }: GuardrailDetailProps) {
|
|||
subtitle={`p95: ${data.p95Latency}ms`}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<MetricCard
|
||||
label="Blocked Today"
|
||||
value="47"
|
||||
valueColor="text-red-600"
|
||||
subtitle="↑ 12% from yesterday"
|
||||
/>
|
||||
</Col>
|
||||
</Grid>
|
||||
|
||||
<Card className="bg-white border border-gray-200 rounded-lg p-6">
|
||||
|
|
@ -325,6 +336,13 @@ export function GuardrailDetail({ guardrailId, onBack }: GuardrailDetailProps) {
|
|||
<LogViewer guardrailName={data.name} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<EvaluationSettingsModal
|
||||
open={evaluationModalOpen}
|
||||
onClose={() => setEvaluationModalOpen(false)}
|
||||
guardrailName={data.name}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,11 @@ type View =
|
|||
| { type: "overview" }
|
||||
| { type: "detail"; guardrailId: string };
|
||||
|
||||
export default function GuardrailsMonitorView() {
|
||||
interface GuardrailsMonitorViewProps {
|
||||
accessToken?: string | null;
|
||||
}
|
||||
|
||||
export default function GuardrailsMonitorView({ accessToken = null }: GuardrailsMonitorViewProps) {
|
||||
const [view, setView] = useState<View>({ type: "overview" });
|
||||
|
||||
const handleSelectGuardrail = (id: string) => {
|
||||
|
|
@ -20,9 +24,13 @@ export default function GuardrailsMonitorView() {
|
|||
return (
|
||||
<div className="p-6 w-full min-w-0 flex-1">
|
||||
{view.type === "overview" ? (
|
||||
<GuardrailsOverview onSelectGuardrail={handleSelectGuardrail} />
|
||||
<GuardrailsOverview accessToken={accessToken} onSelectGuardrail={handleSelectGuardrail} />
|
||||
) : (
|
||||
<GuardrailDetail guardrailId={view.guardrailId} onBack={handleBack} />
|
||||
<GuardrailDetail
|
||||
guardrailId={view.guardrailId}
|
||||
onBack={handleBack}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
PlayCircleOutlined,
|
||||
RiseOutlined,
|
||||
SafetyOutlined,
|
||||
SettingOutlined,
|
||||
WarningOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Card, Col, Grid, Title } from "@tremor/react";
|
||||
|
|
@ -16,10 +17,12 @@ import {
|
|||
policiesTable,
|
||||
type PerformanceRow,
|
||||
} from "./mockData";
|
||||
import { EvaluationSettingsModal } from "./EvaluationSettingsModal";
|
||||
import { MetricCard } from "./MetricCard";
|
||||
import { ScoreChart } from "./ScoreChart";
|
||||
|
||||
interface GuardrailsOverviewProps {
|
||||
accessToken?: string | null;
|
||||
onSelectGuardrail: (id: string) => void;
|
||||
}
|
||||
|
||||
|
|
@ -59,11 +62,15 @@ function computeMetrics(data: PerformanceRow[]) {
|
|||
|
||||
type RerunState = "idle" | "running" | "done";
|
||||
|
||||
export function GuardrailsOverview({ onSelectGuardrail }: GuardrailsOverviewProps) {
|
||||
export function GuardrailsOverview({
|
||||
accessToken = null,
|
||||
onSelectGuardrail,
|
||||
}: GuardrailsOverviewProps) {
|
||||
const [viewMode, setViewMode] = useState<ViewMode>("guardrails");
|
||||
const [sortBy, setSortBy] = useState<SortKey>("failRate");
|
||||
const [sortDir, setSortDir] = useState<"asc" | "desc">("desc");
|
||||
const [rerunState, setRerunState] = useState<RerunState>("idle");
|
||||
const [evaluationModalOpen, setEvaluationModalOpen] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (rerunState !== "done") return;
|
||||
|
|
@ -357,26 +364,34 @@ export function GuardrailsOverview({ onSelectGuardrail }: GuardrailsOverviewProp
|
|||
: "Click a policy to view details, logs, and configuration"}
|
||||
</p>
|
||||
</div>
|
||||
<Button
|
||||
type="default"
|
||||
icon={
|
||||
rerunState === "idle" ? (
|
||||
<PlayCircleOutlined />
|
||||
) : rerunState === "done" ? (
|
||||
<CheckCircleOutlined className="text-green-600" />
|
||||
) : (
|
||||
<Spin size="small" />
|
||||
)
|
||||
}
|
||||
disabled={rerunState === "running"}
|
||||
onClick={handleRerun}
|
||||
>
|
||||
{rerunState === "idle"
|
||||
? "Re-run AI on last 100 logs"
|
||||
: rerunState === "running"
|
||||
? "Re-running on 100 logs…"
|
||||
: "Re-run complete"}
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
type="default"
|
||||
icon={<SettingOutlined />}
|
||||
onClick={() => setEvaluationModalOpen(true)}
|
||||
title="Evaluation settings"
|
||||
/>
|
||||
<Button
|
||||
type="default"
|
||||
icon={
|
||||
rerunState === "idle" ? (
|
||||
<PlayCircleOutlined />
|
||||
) : rerunState === "done" ? (
|
||||
<CheckCircleOutlined className="text-green-600" />
|
||||
) : (
|
||||
<Spin size="small" />
|
||||
)
|
||||
}
|
||||
disabled={rerunState === "running"}
|
||||
onClick={handleRerun}
|
||||
>
|
||||
{rerunState === "idle"
|
||||
? "Re-run AI on last 100 logs"
|
||||
: rerunState === "running"
|
||||
? "Re-running on 100 logs…"
|
||||
: "Re-run complete"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Table
|
||||
columns={columns}
|
||||
|
|
@ -390,6 +405,12 @@ export function GuardrailsOverview({ onSelectGuardrail }: GuardrailsOverviewProp
|
|||
})}
|
||||
/>
|
||||
</Card>
|
||||
|
||||
<EvaluationSettingsModal
|
||||
open={evaluationModalOpen}
|
||||
onClose={() => setEvaluationModalOpen(false)}
|
||||
accessToken={accessToken}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue