mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-24 00:52:24 +00:00
add Evals [Beta] tab to agent detail page
This commit is contained in:
parent
897f65cd4f
commit
ae144728d8
1 changed files with 178 additions and 2 deletions
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useState, useEffect } from "react";
|
||||
import { Card, Title, Text, Button as TremorButton, Tab, TabGroup, TabList, TabPanel, TabPanels} from "@tremor/react";
|
||||
import { Form, Input, InputNumber, Button as AntButton, Spin, Descriptions, Divider } from "antd";
|
||||
import { Form, Input, InputNumber, Button as AntButton, Spin, Descriptions, Divider, Modal, Tag, Space, Table, Popconfirm, message, Typography, Select as AntSelect } from "antd";
|
||||
import MessageManager from "@/components/molecules/message_manager";
|
||||
import { ArrowLeftIcon } from "@heroicons/react/outline";
|
||||
import { getAgentInfo, patchAgentCall, getAgentCreateMetadata, AgentCreateInfo } from "../networking";
|
||||
import { getAgentInfo, patchAgentCall, getAgentCreateMetadata, AgentCreateInfo, getAgentEvals, listLiteLLMEvals, attachEvalToAgent, detachEvalFromAgent } from "../networking";
|
||||
import { Agent } from "./types";
|
||||
import AgentFormFields from "./agent_form_fields";
|
||||
import DynamicAgentFormFields, { buildDynamicAgentData } from "./dynamic_agent_form_fields";
|
||||
|
|
@ -11,6 +11,177 @@ import { buildAgentDataFromForm, parseAgentForForm } from "./agent_config";
|
|||
import AgentCostView from "./agent_cost_view";
|
||||
import { detectAgentType, parseDynamicAgentForForm } from "./agent_type_utils";
|
||||
|
||||
function AgentEvalsTab({ agentId, accessToken }: { agentId: string; accessToken: string | null }) {
|
||||
const [attachedEvals, setAttachedEvals] = React.useState<any[]>([]);
|
||||
const [allEvals, setAllEvals] = React.useState<any[]>([]);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [modalOpen, setModalOpen] = React.useState(false);
|
||||
const [selectedEvalId, setSelectedEvalId] = React.useState<string | undefined>();
|
||||
const [onFailure, setOnFailure] = React.useState<"block" | "log">("block");
|
||||
const [attaching, setAttaching] = React.useState(false);
|
||||
|
||||
const fetchData = React.useCallback(async () => {
|
||||
if (!accessToken || !agentId) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const [attached, catalog] = await Promise.all([
|
||||
getAgentEvals(accessToken, agentId),
|
||||
listLiteLLMEvals(accessToken),
|
||||
]);
|
||||
setAttachedEvals(attached);
|
||||
setAllEvals(catalog);
|
||||
} catch (e: any) {
|
||||
message.error(`Failed to load evals: ${e.message}`);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [accessToken, agentId]);
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchData();
|
||||
}, [fetchData]);
|
||||
|
||||
const handleAttach = async () => {
|
||||
if (!accessToken || !selectedEvalId) return;
|
||||
setAttaching(true);
|
||||
try {
|
||||
await attachEvalToAgent(accessToken, agentId, { eval_id: selectedEvalId, on_failure: onFailure });
|
||||
message.success("Eval attached");
|
||||
setModalOpen(false);
|
||||
setSelectedEvalId(undefined);
|
||||
fetchData();
|
||||
} catch (e: any) {
|
||||
message.error(`Failed to attach eval: ${e.message}`);
|
||||
} finally {
|
||||
setAttaching(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDetach = async (evalId: string) => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
await detachEvalFromAgent(accessToken, agentId, evalId);
|
||||
message.success("Eval detached");
|
||||
fetchData();
|
||||
} catch (e: any) {
|
||||
message.error(`Failed to detach eval: ${e.message}`);
|
||||
}
|
||||
};
|
||||
|
||||
const attachedEvalIds = new Set(attachedEvals.map((e: any) => e.eval_id));
|
||||
const availableToAttach = allEvals.filter((e: any) => !attachedEvalIds.has(e.eval_id));
|
||||
|
||||
const evalColumns = [
|
||||
{
|
||||
title: "Eval Name",
|
||||
dataIndex: "eval_name",
|
||||
key: "eval_name",
|
||||
render: (v: string) => <Typography.Text strong>{v}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: "On Failure",
|
||||
dataIndex: "on_failure",
|
||||
key: "on_failure",
|
||||
render: (v: string) => (
|
||||
<Tag color={v === "block" ? "red" : "orange"}>{v === "block" ? "Block" : "Log only"}</Tag>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: "Threshold",
|
||||
key: "threshold",
|
||||
render: (_: any, r: any) => {
|
||||
const t = r.overall_threshold_override ?? r.eval?.overall_threshold;
|
||||
return t != null ? <Tag color="blue">≥ {t}</Tag> : <Typography.Text type="secondary">default</Typography.Text>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "Judge Model",
|
||||
key: "judge_model",
|
||||
render: (_: any, r: any) => r.eval?.judge_model || "—",
|
||||
},
|
||||
{
|
||||
title: "Actions",
|
||||
key: "actions",
|
||||
render: (_: any, r: any) => (
|
||||
<Popconfirm
|
||||
title="Remove this eval?"
|
||||
onConfirm={() => handleDetach(r.eval_id)}
|
||||
okText="Remove"
|
||||
okButtonProps={{ danger: true }}
|
||||
>
|
||||
<AntButton danger size="small">Remove</AntButton>
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
marginBottom: 16,
|
||||
}}
|
||||
>
|
||||
<Typography.Text type="secondary">
|
||||
Evals gate this agent's responses. Hard-gate (Block) returns 422 on failure; Soft-observer (Log) records the result without blocking.
|
||||
</Typography.Text>
|
||||
<AntButton
|
||||
type="primary"
|
||||
onClick={() => setModalOpen(true)}
|
||||
disabled={availableToAttach.length === 0}
|
||||
>
|
||||
Attach Eval
|
||||
</AntButton>
|
||||
</div>
|
||||
<Table
|
||||
dataSource={attachedEvals}
|
||||
columns={evalColumns}
|
||||
rowKey="eval_id"
|
||||
loading={loading}
|
||||
locale={{ emptyText: "No evals attached yet." }}
|
||||
/>
|
||||
<Modal
|
||||
title={
|
||||
<Space>
|
||||
Attach Eval <Tag color="blue">Beta</Tag>
|
||||
</Space>
|
||||
}
|
||||
open={modalOpen}
|
||||
onCancel={() => { setModalOpen(false); setSelectedEvalId(undefined); }}
|
||||
onOk={handleAttach}
|
||||
confirmLoading={attaching}
|
||||
okText="Attach"
|
||||
>
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="Select Eval" required>
|
||||
<AntSelect
|
||||
placeholder="Choose an eval"
|
||||
value={selectedEvalId}
|
||||
onChange={(val: string) => setSelectedEvalId(val)}
|
||||
style={{ width: "100%" }}
|
||||
options={availableToAttach.map((e: any) => ({ label: e.eval_name, value: e.eval_id }))}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="On Failure">
|
||||
<AntSelect
|
||||
value={onFailure}
|
||||
onChange={(val: "block" | "log") => setOnFailure(val)}
|
||||
style={{ width: "100%" }}
|
||||
options={[
|
||||
{ label: "Block (return 422)", value: "block" },
|
||||
{ label: "Log only (observe)", value: "log" },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface AgentInfoViewProps {
|
||||
agentId: string;
|
||||
onClose: () => void;
|
||||
|
|
@ -165,6 +336,7 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({
|
|||
<TabList className="mb-4">
|
||||
<Tab key="overview">Overview</Tab>
|
||||
{isAdmin ? <Tab key="settings">Settings</Tab> : <></>}
|
||||
<Tab key="evals"><Space>Evals <Tag color="blue">Beta</Tag></Space></Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
|
|
@ -337,6 +509,10 @@ const AgentInfoView: React.FC<AgentInfoViewProps> = ({
|
|||
</Card>
|
||||
</TabPanel>
|
||||
)}
|
||||
{/* Evals Panel */}
|
||||
<TabPanel>
|
||||
<AgentEvalsTab agentId={agent.agent_id} accessToken={accessToken} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</TabGroup>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue