feat: enable running ai monitor on individual guardrails

This commit is contained in:
Krrish Dholakia 2026-02-19 17:10:57 -08:00
parent 9ea62fab04
commit 9947963c7d
5 changed files with 239 additions and 35 deletions

View file

@ -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[]) ?? []}

View file

@ -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>
);
}

View file

@ -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>
);
}

View file

@ -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>
);

View file

@ -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>
);
}