diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index bdac032f315..c054494a97d 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -62,6 +62,8 @@ import { adminGlobalActivityExceptions, adminGlobalActivityExceptionsPerDeployment, allEndUsersCall, + getRouterSettings, + updateRouterSettings, } from "./networking"; import { BarChart, AreaChart } from "@tremor/react"; import { @@ -114,6 +116,7 @@ import { ModelDataTable } from "./model_dashboard/table"; import { columns } from "./model_dashboard/columns"; import { all_admin_roles } from "@/utils/roles"; import { Table as TableInstance } from '@tanstack/react-table'; +import ModelAliasManagement from "./model_dashboard/model_alias_management"; interface ModelDashboardProps { accessToken: string | null; @@ -670,6 +673,8 @@ const ModelDashboard: React.FC = ({ handleRefreshClick(); }, [accessToken, token, userRole, userID, modelMap, lastRefreshed, selectedTeam]); + + if (!modelData) { return
Loading...
; } @@ -1067,13 +1072,13 @@ const ModelDashboard: React.FC = ({ } {all_admin_roles.includes(userRole) && Model Analytics} {all_admin_roles.includes(userRole) && Model Retry Settings} - + {all_admin_roles.includes(userRole) && Model Group Aliases}
{lastRefreshed && Last Refreshed: {lastRefreshed}} = ({ Save + + {accessToken && ( + + )} + diff --git a/ui/litellm-dashboard/src/components/model_dashboard/model_alias_management.tsx b/ui/litellm-dashboard/src/components/model_dashboard/model_alias_management.tsx new file mode 100644 index 00000000000..e36bfb870e0 --- /dev/null +++ b/ui/litellm-dashboard/src/components/model_dashboard/model_alias_management.tsx @@ -0,0 +1,239 @@ +import React, { useState, useEffect } from "react"; +import { + Card, + Title, + Text, + Table, + TableHead, + TableRow, + TableHeaderCell, + TableCell, + TableBody, + Button +} from "@tremor/react"; +import { + Input, + Select, + message, + Button as AntdButton, +} from "antd"; +import { + PencilIcon, + TrashIcon, +} from "@heroicons/react/outline"; +import { getRouterSettings, updateRouterSettings } from "../networking"; + +interface ModelAliasManagementProps { + accessToken: string; + availableModels: string[]; + onRefresh: () => void; +} + +const ModelAliasManagement: React.FC = ({ + accessToken, + availableModels, + onRefresh, +}) => { + const [modelGroupAliases, setModelGroupAliases] = useState>({}); + const [newAliasName, setNewAliasName] = useState(""); + const [selectedModelForAlias, setSelectedModelForAlias] = useState(""); + const [editingAlias, setEditingAlias] = useState(null); + const [isLoading, setIsLoading] = useState(false); + + // Fetch router settings + useEffect(() => { + fetchRouterSettings(); + }, [accessToken]); + + const fetchRouterSettings = async () => { + if (!accessToken) return; + + setIsLoading(true); + try { + const settings = await getRouterSettings(accessToken); + if (settings?.router_settings?.model_group_alias) { + setModelGroupAliases(settings.router_settings.model_group_alias); + } else { + setModelGroupAliases({}); + } + } catch (error) { + console.error("Failed to fetch router settings:", error); + message.error("Failed to load model aliases"); + } finally { + setIsLoading(false); + } + }; + + // Handle adding or updating a model group alias + const handleSaveModelGroupAlias = async () => { + if (!accessToken || !newAliasName || !selectedModelForAlias) { + message.error("Alias name and model selection are required"); + return; + } + + setIsLoading(true); + try { + const updatedAliases = { ...modelGroupAliases }; + + // If editing, remove the old alias first + if (editingAlias && editingAlias !== newAliasName) { + delete updatedAliases[editingAlias]; + } + + updatedAliases[newAliasName] = selectedModelForAlias; + + await updateRouterSettings(accessToken, { + model_group_alias: updatedAliases + }); + + setModelGroupAliases(updatedAliases); + setNewAliasName(""); + setSelectedModelForAlias(""); + setEditingAlias(null); + + message.success(`Model group alias ${editingAlias ? 'updated' : 'created'} successfully`); + onRefresh(); + } catch (error) { + console.error("Failed to save model group alias:", error); + message.error(`Failed to ${editingAlias ? 'update' : 'create'} model group alias`); + } finally { + setIsLoading(false); + } + }; + + // Handle deleting a model group alias + const handleDeleteModelGroupAlias = async (aliasName: string) => { + setIsLoading(true); + try { + const updatedAliases = { ...modelGroupAliases }; + delete updatedAliases[aliasName]; + + await updateRouterSettings(accessToken, { + model_group_alias: updatedAliases + }); + + setModelGroupAliases(updatedAliases); + message.success(`Model group alias deleted successfully`); + onRefresh(); + } catch (error) { + console.error("Failed to delete model group alias:", error); + message.error("Failed to delete model group alias"); + } finally { + setIsLoading(false); + } + }; + + // Handle editing a model group alias + const handleEditModelGroupAlias = (aliasName: string) => { + setEditingAlias(aliasName); + setNewAliasName(aliasName); + setSelectedModelForAlias(modelGroupAliases[aliasName] || ""); + }; + + return ( + + Model Group Aliases + + Map model alias names to actual model names. Aliases can be used interchangeably with the original model names in API calls. + + +
+
+
+
+ Alias Name + setNewAliasName(e.target.value)} + /> +
+ +
+ Target Model + +
+ + + {editingAlias ? "Update" : "Add"} Alias + + + {editingAlias && ( + { + setEditingAlias(null); + setNewAliasName(""); + setSelectedModelForAlias(""); + }} + disabled={isLoading} + > + Cancel + + )} +
+
+
+ + + + + Alias Name + Target Model + Actions + + + + {Object.entries(modelGroupAliases).map(([alias, model], idx) => ( + + {alias} + {model} + +
+
+
+
+ ))} + {Object.keys(modelGroupAliases).length === 0 && ( + + + No model group aliases defined + + + )} +
+
+
+ ); +}; + +export default ModelAliasManagement; \ No newline at end of file diff --git a/ui/litellm-dashboard/src/components/networking.tsx b/ui/litellm-dashboard/src/components/networking.tsx index 292acd7de76..6b209c6b437 100644 --- a/ui/litellm-dashboard/src/components/networking.tsx +++ b/ui/litellm-dashboard/src/components/networking.tsx @@ -5187,3 +5187,58 @@ export const updateGuardrailCall = async ( throw error; } }; + +export const getRouterSettings = async (accessToken: string) => { + try { + const url = proxyBaseUrl ? `${proxyBaseUrl}/router_settings` : `/router_settings`; + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + errorData.detail?.error || "Failed to fetch router settings" + ); + } + + return await response.json(); + } catch (error) { + handleError(`Error fetching router settings: ${error}`); + throw error; + } +}; + +export const updateRouterSettings = async ( + accessToken: string, + settings: Record +) => { + + const url = proxyBaseUrl ? `${proxyBaseUrl}/router_settings` : `/router_settings`; + try { + const response = await fetch(url, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${accessToken}`, + }, + body: JSON.stringify(settings), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error( + errorData.detail?.error || "Failed to update router settings" + ); + } + + return await response.json(); + } catch (error) { + handleError(`Error updating router settings: ${error}`); + throw error; + } +};