[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:
Ishaan Jaff
2025-07-24 21:25:48 -07:00
committed by GitHub
parent 29cff4f4e6
commit 39b0fe0bf7
9 changed files with 343 additions and 69 deletions
@@ -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
-11
View File
@@ -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>
);
}