From 69c1eab9621938c08f487973b3a91bc885aa2623 Mon Sep 17 00:00:00 2001 From: Ishaan Jaffer Date: Wed, 22 Apr 2026 16:51:17 -0700 Subject: [PATCH] add Evals [Beta] catalog page with create/delete UI --- .../src/components/evals/EvalsCatalog.tsx | 305 ++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 ui/litellm-dashboard/src/components/evals/EvalsCatalog.tsx diff --git a/ui/litellm-dashboard/src/components/evals/EvalsCatalog.tsx b/ui/litellm-dashboard/src/components/evals/EvalsCatalog.tsx new file mode 100644 index 00000000000..a1f29590010 --- /dev/null +++ b/ui/litellm-dashboard/src/components/evals/EvalsCatalog.tsx @@ -0,0 +1,305 @@ +"use client"; +import React, { useEffect, useState } from "react"; +import { + Button, + Table, + Tag, + Typography, + Space, + Drawer, + Form, + Input, + InputNumber, + Select, + Popconfirm, + message, +} from "antd"; +import { PlusOutlined, DeleteOutlined } from "@ant-design/icons"; +import { createLiteLLMEval, listLiteLLMEvals, deleteLiteLLMEval } from "../networking"; + +const { Title, Text } = Typography; + +interface EvalCriterion { + name: string; + weight: number; + description: string; + threshold?: number; +} + +interface LiteLLMEval { + eval_id: string; + eval_name: string; + version: number; + criteria: EvalCriterion[]; + judge_model: string; + description?: string; + overall_threshold?: number; + max_iterations: number; + created_at: string; +} + +interface Props { + accessToken: string | null; + userRole?: string | null; + availableModels?: string[]; +} + +export default function EvalsCatalog({ accessToken, userRole, availableModels = [] }: Props) { + const [evals, setEvals] = useState([]); + const [loading, setLoading] = useState(false); + const [drawerOpen, setDrawerOpen] = useState(false); + const [saving, setSaving] = useState(false); + const [form] = Form.useForm(); + + const fetchEvals = async () => { + if (!accessToken) return; + setLoading(true); + try { + const data = await listLiteLLMEvals(accessToken); + setEvals(data); + } catch (e: any) { + message.error(`Failed to load evals: ${e.message}`); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + fetchEvals(); + }, [accessToken]); + + const handleCreate = async (values: any) => { + if (!accessToken) return; + setSaving(true); + try { + await createLiteLLMEval(accessToken, { + eval_name: values.eval_name, + criteria: values.criteria || [], + judge_model: values.judge_model, + description: values.description, + overall_threshold: values.overall_threshold ?? 80, + max_iterations: values.max_iterations ?? 1, + }); + message.success("Eval created"); + setDrawerOpen(false); + form.resetFields(); + fetchEvals(); + } catch (e: any) { + message.error(`Failed to create eval: ${e.message}`); + } finally { + setSaving(false); + } + }; + + const handleDelete = async (evalId: string) => { + if (!accessToken) return; + try { + await deleteLiteLLMEval(accessToken, evalId); + message.success("Eval deleted"); + fetchEvals(); + } catch (e: any) { + message.error(`Failed to delete eval: ${e.message}`); + } + }; + + const columns = [ + { + title: "Name", + dataIndex: "eval_name", + key: "eval_name", + render: (v: string) => {v}, + }, + { + title: "Judge Model", + dataIndex: "judge_model", + key: "judge_model", + }, + { + title: "Minimum Score to Pass", + dataIndex: "overall_threshold", + key: "overall_threshold", + render: (v?: number) => + v != null ? ≥ {v} / 100 : —, + }, + { + title: "Criteria", + dataIndex: "criteria", + key: "criteria", + render: (v: EvalCriterion[]) => {v?.length ?? 0} criteria, + }, + { + title: "Version", + dataIndex: "version", + key: "version", + render: (v: number) => v{v}, + }, + { + title: "Actions", + key: "actions", + render: (_: any, record: LiteLLMEval) => ( + handleDelete(record.eval_id)} + okText="Delete" + okButtonProps={{ danger: true }} + > + + + + + + + Create Eval + Beta + + } + open={drawerOpen} + onClose={() => { + setDrawerOpen(false); + form.resetFields(); + }} + width={560} + footer={ +
+ + +
+ } + > +
+ + + + + + + + + + + + + + + + + + + + + ))} + + + )} + + + + + + + +
+ + ); +}