diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index ac9cb56cc..f7b8447f1 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -2516,20 +2516,21 @@ class ProxyConfig: router = litellm.Router(**router_params) # type:ignore return router, model_list, general_settings - def get_model_info_with_id(self, model) -> RouterModelInfo: + def get_model_info_with_id(self, model, db_model=False) -> RouterModelInfo: """ Common logic across add + delete router models Parameters: - deployment + - db_model -> flag for differentiating model stored in db vs. config -> used on UI Return model info w/ id """ if model.model_info is not None and isinstance(model.model_info, dict): if "id" not in model.model_info: model.model_info["id"] = model.model_id - _model_info = RouterModelInfo(**model.model_info) + _model_info = RouterModelInfo(**model.model_info, db_model=db_model) else: - _model_info = RouterModelInfo(id=model.model_id) + _model_info = RouterModelInfo(id=model.model_id, db_model=db_model) return _model_info async def _delete_deployment(self, db_models: list) -> int: @@ -2624,7 +2625,9 @@ class ProxyConfig: f"Invalid model added to proxy db. Invalid litellm params. litellm_params={_litellm_params}" ) continue # skip to next model - _model_info = self.get_model_info_with_id(model=m) + _model_info = self.get_model_info_with_id( + model=m, db_model=True + ) ## 👈 FLAG = True for db_models added = llm_router.add_deployment( deployment=Deployment( @@ -7449,6 +7452,16 @@ async def update_model( ) ) if _existing_litellm_params is None: + if ( + llm_router is not None + and llm_router.get_deployment(model_id=_model_id) is not None + ): + raise HTTPException( + status_code=400, + detail={ + "error": "Can't edit model. Model in config. Store model in db via `/model/new`. to edit." + }, + ) raise Exception("model not found") _existing_litellm_params_dict = dict( _existing_litellm_params.litellm_params diff --git a/litellm/router.py b/litellm/router.py index ddb006d52..e2e1d3409 100644 --- a/litellm/router.py +++ b/litellm/router.py @@ -2595,11 +2595,21 @@ class Router: except: return None - def get_deployment(self, model_id: str): + def get_deployment(self, model_id: str) -> Optional[Deployment]: + """ + Returns -> Deployment or None + + Raise Exception -> if model found in invalid format + """ for model in self.model_list: if "model_info" in model and "id" in model["model_info"]: if model_id == model["model_info"]["id"]: - return model + if isinstance(model, dict): + return Deployment(**model) + elif isinstance(model, Deployment): + return model + else: + raise Exception("Model invalid format - {}".format(type(model))) return None def get_model_info(self, id: str) -> Optional[dict]: diff --git a/litellm/types/router.py b/litellm/types/router.py index 6ab83cec2..07d16a37a 100644 --- a/litellm/types/router.py +++ b/litellm/types/router.py @@ -1,6 +1,6 @@ from typing import List, Optional, Union, Dict, Tuple, Literal import httpx -from pydantic import BaseModel, validator +from pydantic import BaseModel, validator, Field from .completion import CompletionRequest from .embedding import EmbeddingRequest import uuid, enum @@ -70,6 +70,9 @@ class ModelInfo(BaseModel): id: Optional[ str ] # Allow id to be optional on input, but it will always be present as a str in the model instance + db_model: bool = ( + False # used for proxy - to separate models which are stored in the db vs. config. + ) def __init__(self, id: Optional[Union[str, int]] = None, **params): if id is None: diff --git a/ui/litellm-dashboard/src/components/model_dashboard.tsx b/ui/litellm-dashboard/src/components/model_dashboard.tsx index 6e207b430..d66a73485 100644 --- a/ui/litellm-dashboard/src/components/model_dashboard.tsx +++ b/ui/litellm-dashboard/src/components/model_dashboard.tsx @@ -37,7 +37,7 @@ import { Badge, BadgeDelta, Button } from "@tremor/react"; import RequestAccess from "./request_model_access"; import { Typography } from "antd"; import TextArea from "antd/es/input/TextArea"; -import { InformationCircleIcon, PencilAltIcon, PencilIcon, StatusOnlineIcon, TrashIcon, RefreshIcon } from "@heroicons/react/outline"; +import { InformationCircleIcon, PencilAltIcon, PencilIcon, StatusOnlineIcon, TrashIcon, RefreshIcon, CheckCircleIcon, XCircleIcon } from "@heroicons/react/outline"; import DeleteModelButton from "./delete_model_button"; const { Title: Title2, Link } = Typography; import { UploadOutlined } from '@ant-design/icons'; @@ -921,6 +921,7 @@ const handleEditSubmit = async (formValues: Record) => { Input Price per token ($) Output Price per token ($) Max Tokens + Status @@ -929,6 +930,7 @@ const handleEditSubmit = async (formValues: Record) => { selectedModelGroup === "all" || model.model_name === selectedModelGroup || selectedModelGroup === null || selectedModelGroup === undefined || selectedModelGroup === "" ) .map((model: any, index: number) => ( + {model.model_name} @@ -958,6 +960,12 @@ const handleEditSubmit = async (formValues: Record) => { {model.input_cost} {model.output_cost} {model.max_tokens} + + { + model.model_info.db_model ? DB Model : Config Model + } + +