mirror of
https://github.com/tiennm99/litellm.git
synced 2026-08-03 14:23:28 +00:00
[Feat] Edit Auto Router Settings on UI (#12966)
* EditAutoRouterTabProps * Revert "EditAutoRouterTabProps" This reverts commit 2835d3a3743e6411b9914a0b01381050e2273ad7. * add EditAutoRouterTab * delete edit * fixes for edit auto-router * fix accessing model edit * working edit auto router * fix - edit remove custom model name * fixes for edit auto router settings * qa for adding a model router * test fix
This commit is contained in:
@@ -1146,6 +1146,7 @@ async def clear_cache():
|
||||
|
||||
try:
|
||||
llm_router.model_list.clear()
|
||||
llm_router.auto_routers.clear()
|
||||
|
||||
await proxy_config.add_deployment(
|
||||
prisma_client=prisma_client, proxy_logging_obj=proxy_logging_obj
|
||||
|
||||
@@ -7,17 +7,6 @@ model_list:
|
||||
model: openai/*
|
||||
|
||||
|
||||
guardrails:
|
||||
- guardrail_name: "bedrock-post-guard"
|
||||
litellm_params:
|
||||
guardrail: bedrock # supported values: "aporia", "bedrock", "lakera"
|
||||
mode: "post_call"
|
||||
guardrailIdentifier: ff6ujrregl1q
|
||||
guardrailVersion: "DRAFT"
|
||||
default_on: true
|
||||
|
||||
|
||||
|
||||
litellm_settings:
|
||||
callbacks: ["datadog_llm_observability"]
|
||||
cache: true
|
||||
|
||||
@@ -1292,7 +1292,8 @@ def test_init_auto_router_deployment_success(mock_auto_router, model_list):
|
||||
# Verify AutoRouter was called with correct parameters
|
||||
mock_auto_router.assert_called_once_with(
|
||||
model_name="test-auto-router",
|
||||
router_config_path="/path/to/config",
|
||||
auto_router_config_path="/path/to/config",
|
||||
auto_router_config=None,
|
||||
default_model="gpt-3.5-turbo",
|
||||
embedding_model="text-embedding-ada-002",
|
||||
litellm_router_instance=router,
|
||||
|
||||
@@ -211,21 +211,6 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
||||
showSearch={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
{showCustomDefaultModel && (
|
||||
<Form.Item
|
||||
label="Custom Default Model"
|
||||
name="custom_default_model"
|
||||
labelCol={{ span: 10 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<TextInput
|
||||
placeholder="Enter custom model name"
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('auto_router_default_model', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{/* Auto Router Embedding Model */}
|
||||
<Form.Item
|
||||
@@ -255,22 +240,6 @@ const AddAutoRouterTab: React.FC<AddAutoRouterTabProps> = ({
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
{showCustomEmbeddingModel && (
|
||||
<Form.Item
|
||||
label="Custom Embedding Model"
|
||||
name="custom_embedding_model"
|
||||
labelCol={{ span: 10 }}
|
||||
labelAlign="left"
|
||||
>
|
||||
<TextInput
|
||||
placeholder="Enter custom embedding model name"
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('auto_router_embedding_model', e.target.value);
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<div className="flex items-center my-4">
|
||||
<div className="flex-grow border-t border-gray-200"></div>
|
||||
<span className="px-4 text-gray-500 text-sm">Additional Settings</span>
|
||||
|
||||
@@ -101,7 +101,6 @@ const AddModelTab: React.FC<AddModelTabProps> = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title level={2}>Add new model</Title>
|
||||
<TabGroup className="w-full">
|
||||
<TabList className="mb-4">
|
||||
<Tab>Add Model</Tab>
|
||||
@@ -109,6 +108,7 @@ const AddModelTab: React.FC<AddModelTabProps> = ({
|
||||
</TabList>
|
||||
<TabPanels>
|
||||
<TabPanel>
|
||||
<Title level={2}>Add Model</Title>
|
||||
<Card>
|
||||
<Form
|
||||
form={form}
|
||||
|
||||
@@ -49,11 +49,6 @@ export const handleAddAutoRouterSubmit = async (
|
||||
console.log("Calling modelCreateCall with:", { accessToken: accessToken ? "Present" : "Missing", config: autoRouterConfig });
|
||||
const response: any = await modelCreateCall(accessToken, autoRouterConfig as Model);
|
||||
console.log(`response for auto router create call:`, response);
|
||||
|
||||
message.success("Auto router added successfully!");
|
||||
|
||||
// Call the callback function if provided (usually to refresh the model list)
|
||||
callback && callback();
|
||||
|
||||
// Reset the form
|
||||
form.resetFields();
|
||||
|
||||
@@ -15,9 +15,22 @@ interface Route {
|
||||
score_threshold: number;
|
||||
}
|
||||
|
||||
interface SavedRoute {
|
||||
id?: string;
|
||||
name?: string;
|
||||
model?: string;
|
||||
utterances?: string[];
|
||||
description?: string;
|
||||
score_threshold?: number;
|
||||
}
|
||||
|
||||
interface RouterConfig {
|
||||
routes?: SavedRoute[];
|
||||
}
|
||||
|
||||
interface RouterConfigBuilderProps {
|
||||
modelInfo: ModelGroup[];
|
||||
value?: any;
|
||||
value?: RouterConfig;
|
||||
onChange?: (config: any) => void;
|
||||
}
|
||||
|
||||
@@ -26,17 +39,30 @@ const RouterConfigBuilder: React.FC<RouterConfigBuilderProps> = ({
|
||||
value,
|
||||
onChange,
|
||||
}) => {
|
||||
const [routes, setRoutes] = useState<Route[]>(value?.routes || []);
|
||||
const [routes, setRoutes] = useState<Route[]>([]);
|
||||
const [showJsonPreview, setShowJsonPreview] = useState<boolean>(false);
|
||||
const [expandedRoutes, setExpandedRoutes] = useState<string[]>([]);
|
||||
|
||||
// Initialize expanded routes for existing routes on mount
|
||||
// Initialize routes from value prop
|
||||
useEffect(() => {
|
||||
if (value?.routes && value.routes.length > 0 && expandedRoutes.length === 0) {
|
||||
const existingRouteIds = value.routes.map((route: any) => route.id || `route-${Math.random()}`);
|
||||
setExpandedRoutes(existingRouteIds);
|
||||
if (value?.routes) {
|
||||
const initializedRoutes = value.routes.map((route: SavedRoute, index: number) => ({
|
||||
id: route.id || `route-${index}-${Date.now()}`,
|
||||
model: route.name || route.model || "", // handle both 'name' and 'model' fields
|
||||
utterances: route.utterances || [],
|
||||
description: route.description || "",
|
||||
score_threshold: route.score_threshold || 0.5,
|
||||
}));
|
||||
setRoutes(initializedRoutes);
|
||||
|
||||
// Set expanded routes for existing routes
|
||||
const routeIds = initializedRoutes.map(route => route.id);
|
||||
setExpandedRoutes(routeIds);
|
||||
} else {
|
||||
setRoutes([]);
|
||||
setExpandedRoutes([]);
|
||||
}
|
||||
}, [value?.routes, expandedRoutes.length]);
|
||||
}, [value]);
|
||||
|
||||
// Handle adding a new route
|
||||
const addRoute = () => {
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { Modal, Form, Button, Select as AntdSelect, message } from "antd";
|
||||
import { Text, TextInput } from "@tremor/react";
|
||||
import { modelAvailableCall, modelPatchUpdateCall } from "../networking";
|
||||
import { fetchAvailableModels, ModelGroup } from "../chat_ui/llm_calls/fetch_models";
|
||||
import RouterConfigBuilder from "../add_model/router_config_builder";
|
||||
|
||||
interface EditAutoRouterModalProps {
|
||||
isVisible: boolean;
|
||||
onCancel: () => void;
|
||||
onSuccess: (updatedModel: any) => void;
|
||||
modelData: any;
|
||||
accessToken: string;
|
||||
userRole: string;
|
||||
}
|
||||
|
||||
const EditAutoRouterModal: React.FC<EditAutoRouterModalProps> = ({
|
||||
isVisible,
|
||||
onCancel,
|
||||
onSuccess,
|
||||
modelData,
|
||||
accessToken,
|
||||
userRole,
|
||||
}) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [modelAccessGroups, setModelAccessGroups] = useState<string[]>([]);
|
||||
const [modelInfo, setModelInfo] = useState<ModelGroup[]>([]);
|
||||
const [showCustomDefaultModel, setShowCustomDefaultModel] = useState<boolean>(false);
|
||||
const [showCustomEmbeddingModel, setShowCustomEmbeddingModel] = useState<boolean>(false);
|
||||
const [routerConfig, setRouterConfig] = useState<any>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isVisible && modelData) {
|
||||
initializeForm();
|
||||
}
|
||||
}, [isVisible, modelData]);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchModelAccessGroups = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const response = await modelAvailableCall(accessToken, "", "", false, null, true, true);
|
||||
setModelAccessGroups(response["data"].map((model: any) => model["id"]));
|
||||
} catch (error) {
|
||||
console.error("Error fetching model access groups:", error);
|
||||
}
|
||||
};
|
||||
|
||||
const loadModels = async () => {
|
||||
if (!accessToken) return;
|
||||
try {
|
||||
const uniqueModels = await fetchAvailableModels(accessToken);
|
||||
setModelInfo(uniqueModels);
|
||||
} catch (error) {
|
||||
console.error("Error fetching model info:", error);
|
||||
}
|
||||
};
|
||||
|
||||
if (isVisible) {
|
||||
fetchModelAccessGroups();
|
||||
loadModels();
|
||||
}
|
||||
}, [isVisible, accessToken]);
|
||||
|
||||
const initializeForm = () => {
|
||||
try {
|
||||
// Parse the auto_router_config if it exists and is a string
|
||||
let parsedConfig = null;
|
||||
if (modelData.litellm_params?.auto_router_config) {
|
||||
if (typeof modelData.litellm_params.auto_router_config === 'string') {
|
||||
parsedConfig = JSON.parse(modelData.litellm_params.auto_router_config);
|
||||
} else {
|
||||
parsedConfig = modelData.litellm_params.auto_router_config;
|
||||
}
|
||||
}
|
||||
|
||||
setRouterConfig(parsedConfig);
|
||||
|
||||
// Set form values
|
||||
form.setFieldsValue({
|
||||
auto_router_name: modelData.model_name,
|
||||
auto_router_default_model: modelData.litellm_params?.auto_router_default_model || '',
|
||||
auto_router_embedding_model: modelData.litellm_params?.auto_router_embedding_model || '',
|
||||
model_access_group: modelData.model_info?.access_groups || [],
|
||||
});
|
||||
|
||||
// Check if using custom models
|
||||
const allModelGroups = new Set(modelInfo.map(model => model.model_group));
|
||||
setShowCustomDefaultModel(!allModelGroups.has(modelData.litellm_params?.auto_router_default_model));
|
||||
setShowCustomEmbeddingModel(!allModelGroups.has(modelData.litellm_params?.auto_router_embedding_model));
|
||||
|
||||
} catch (error) {
|
||||
console.error("Error parsing auto router config:", error);
|
||||
message.error("Error loading auto router configuration");
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const values = await form.validateFields();
|
||||
|
||||
// Prepare the updated litellm_params
|
||||
const updatedLitellmParams = {
|
||||
...modelData.litellm_params,
|
||||
auto_router_config: JSON.stringify(routerConfig),
|
||||
auto_router_default_model: values.auto_router_default_model,
|
||||
auto_router_embedding_model: values.auto_router_embedding_model || undefined,
|
||||
};
|
||||
|
||||
// Prepare updated model_info
|
||||
const updatedModelInfo = {
|
||||
...modelData.model_info,
|
||||
access_groups: values.model_access_group || [],
|
||||
};
|
||||
|
||||
const updateData = {
|
||||
model_name: values.auto_router_name,
|
||||
litellm_params: updatedLitellmParams,
|
||||
model_info: updatedModelInfo,
|
||||
};
|
||||
|
||||
await modelPatchUpdateCall(accessToken, updateData, modelData.model_info.id);
|
||||
|
||||
const updatedModelData = {
|
||||
...modelData,
|
||||
model_name: values.auto_router_name,
|
||||
litellm_params: updatedLitellmParams,
|
||||
model_info: updatedModelInfo,
|
||||
};
|
||||
|
||||
message.success("Auto router configuration updated successfully");
|
||||
onSuccess(updatedModelData);
|
||||
onCancel();
|
||||
} catch (error) {
|
||||
console.error("Error updating auto router:", error);
|
||||
message.error("Failed to update auto router configuration");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const modelOptions = modelInfo.map(model => ({
|
||||
value: model.model_group,
|
||||
label: model.model_group,
|
||||
}));
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="Edit Auto Router Configuration"
|
||||
open={isVisible}
|
||||
onCancel={onCancel}
|
||||
footer={[
|
||||
<Button key="cancel" onClick={onCancel}>
|
||||
Cancel
|
||||
</Button>,
|
||||
<Button key="submit" loading={loading} onClick={handleSubmit}>
|
||||
Save Changes
|
||||
</Button>,
|
||||
]}
|
||||
width={1000}
|
||||
destroyOnClose
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<Text className="text-gray-600">
|
||||
Edit the auto router configuration including routing logic, default models, and access settings.
|
||||
</Text>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
className="space-y-4"
|
||||
>
|
||||
{/* Auto Router Name */}
|
||||
<Form.Item
|
||||
label="Auto Router Name"
|
||||
name="auto_router_name"
|
||||
rules={[{ required: true, message: "Auto router name is required" }]}
|
||||
>
|
||||
<TextInput placeholder="e.g., auto_router_1, smart_routing" />
|
||||
</Form.Item>
|
||||
|
||||
{/* Router Configuration Builder */}
|
||||
<div className="w-full">
|
||||
<RouterConfigBuilder
|
||||
modelInfo={modelInfo}
|
||||
value={routerConfig}
|
||||
onChange={(config) => {
|
||||
setRouterConfig(config);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Default Model */}
|
||||
<Form.Item
|
||||
label="Default Model"
|
||||
name="auto_router_default_model"
|
||||
rules={[{ required: true, message: "Default model is required" }]}
|
||||
>
|
||||
<AntdSelect
|
||||
placeholder="Select a default model"
|
||||
onChange={(value) => {
|
||||
setShowCustomDefaultModel(value === 'custom');
|
||||
}}
|
||||
options={[
|
||||
...modelOptions,
|
||||
{ value: 'custom', label: 'Enter custom model name' }
|
||||
]}
|
||||
showSearch={true}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
||||
{/* Embedding Model */}
|
||||
<Form.Item
|
||||
label="Embedding Model"
|
||||
name="auto_router_embedding_model"
|
||||
>
|
||||
<AntdSelect
|
||||
placeholder="Select an embedding model (optional)"
|
||||
onChange={(value) => {
|
||||
setShowCustomEmbeddingModel(value === 'custom');
|
||||
}}
|
||||
options={[
|
||||
...modelOptions,
|
||||
{ value: 'custom', label: 'Enter custom model name' }
|
||||
]}
|
||||
showSearch={true}
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
{/* Model Access Groups - Admin only */}
|
||||
{userRole === "Admin" && (
|
||||
<Form.Item
|
||||
label="Model Access Groups"
|
||||
name="model_access_group"
|
||||
tooltip="Control who can access this auto router"
|
||||
>
|
||||
<AntdSelect
|
||||
mode="tags"
|
||||
showSearch
|
||||
placeholder="Select existing groups or type to create new ones"
|
||||
optionFilterProp="children"
|
||||
tokenSeparators={[',']}
|
||||
options={modelAccessGroups.map((group) => ({
|
||||
value: group,
|
||||
label: group
|
||||
}))}
|
||||
maxTagCount="responsive"
|
||||
allowClear
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditAutoRouterModal;
|
||||
@@ -35,6 +35,7 @@ import ReuseCredentialsModal from "./model_add/reuse_credentials";
|
||||
import CacheControlSettings from "./add_model/cache_control_settings";
|
||||
import { CheckIcon, CopyIcon } from "lucide-react";
|
||||
import { copyToClipboard as utilCopyToClipboard } from "../utils/dataUtils";
|
||||
import EditAutoRouterModal from "./edit_auto_router/edit_auto_router_modal";
|
||||
|
||||
interface ModelInfoViewProps {
|
||||
modelId: string;
|
||||
@@ -74,18 +75,20 @@ export default function ModelInfoView({
|
||||
useState<CredentialItem | null>(null);
|
||||
const [showCacheControl, setShowCacheControl] = useState(false);
|
||||
const [copiedStates, setCopiedStates] = useState<Record<string, boolean>>({});
|
||||
const [isAutoRouterModalOpen, setIsAutoRouterModalOpen] = useState(false);
|
||||
|
||||
const canEditModel =
|
||||
userRole === "Admin" || modelData.model_info.created_by === userID;
|
||||
userRole === "Admin" || modelData?.model_info?.created_by === userID;
|
||||
const isAdmin = userRole === "Admin";
|
||||
const isAutoRouter = modelData?.litellm_params?.auto_router_config != null;
|
||||
|
||||
const usingExistingCredential =
|
||||
modelData.litellm_params?.litellm_credential_name != null &&
|
||||
modelData.litellm_params?.litellm_credential_name != undefined;
|
||||
modelData?.litellm_params?.litellm_credential_name != null &&
|
||||
modelData?.litellm_params?.litellm_credential_name != undefined;
|
||||
console.log("usingExistingCredential, ", usingExistingCredential);
|
||||
console.log(
|
||||
"modelData.litellm_params.litellm_credential_name, ",
|
||||
modelData.litellm_params.litellm_credential_name
|
||||
modelData?.litellm_params?.litellm_credential_name
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -271,6 +274,13 @@ export default function ModelInfoView({
|
||||
}
|
||||
};
|
||||
|
||||
const handleAutoRouterUpdate = (updatedModel: any) => {
|
||||
setLocalModelData(updatedModel);
|
||||
if (onModelUpdate) {
|
||||
onModelUpdate(updatedModel);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-center mb-6">
|
||||
@@ -430,15 +440,26 @@ export default function ModelInfoView({
|
||||
<Card>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<Title>Model Settings</Title>
|
||||
{canEditModel && !isEditing && (
|
||||
<TremorButton
|
||||
variant="secondary"
|
||||
onClick={() => setIsEditing(true)}
|
||||
className="flex items-center"
|
||||
>
|
||||
Edit Model
|
||||
</TremorButton>
|
||||
)}
|
||||
<div className="flex gap-2">
|
||||
{isAutoRouter && canEditModel && !isEditing && (
|
||||
<TremorButton
|
||||
variant="primary"
|
||||
onClick={() => setIsAutoRouterModalOpen(true)}
|
||||
className="flex items-center"
|
||||
>
|
||||
Edit Auto Router
|
||||
</TremorButton>
|
||||
)}
|
||||
{canEditModel && !isEditing && (
|
||||
<TremorButton
|
||||
variant="secondary"
|
||||
onClick={() => setIsEditing(true)}
|
||||
className="flex items-center"
|
||||
>
|
||||
Edit Model
|
||||
</TremorButton>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{localModelData ? (
|
||||
<Form
|
||||
@@ -916,6 +937,16 @@ export default function ModelInfoView({
|
||||
<Text>{modelData.litellm_params.litellm_credential_name}</Text>
|
||||
</Modal>
|
||||
)}
|
||||
|
||||
{/* Edit Auto Router Modal */}
|
||||
<EditAutoRouterModal
|
||||
isVisible={isAutoRouterModalOpen}
|
||||
onCancel={() => setIsAutoRouterModalOpen(false)}
|
||||
onSuccess={handleAutoRouterUpdate}
|
||||
modelData={localModelData || modelData}
|
||||
accessToken={accessToken || ""}
|
||||
userRole={userRole || ""}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user