From 08b24fe753191482030db55690c0a33819977d1a Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Wed, 16 Oct 2024 15:31:26 +0530 Subject: [PATCH 1/9] Adding model delete and MD delete/deactivate APIs --- ads/aqua/extension/deployment_handler.py | 8 ++++++++ ads/aqua/extension/model_handler.py | 2 ++ ads/aqua/model/model.py | 9 +++++++++ ads/aqua/modeldeployment/deployment.py | 9 +++++++++ 4 files changed, 28 insertions(+) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index 3e3a6c277..c8ae63273 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -54,6 +54,14 @@ def get(self, id=""): else: raise HTTPError(400, f"The request {self.request.path} is invalid.") + @handle_exceptions + def delete(self,model_deployment_id): + return self.finish(AquaDeploymentApp().delete(model_deployment_id)) + + @handle_exceptions + def put(self,model_deployment_id): + return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) + @handle_exceptions def post(self, *args, **kwargs): """ diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 5fa25992f..f6c1d6c83 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -73,6 +73,8 @@ def delete(self, id=""): paths = url_parse.path.strip("/") if paths.startswith("aqua/model/cache"): return self.finish(AquaModelApp().clear_model_list_cache()) + elif id: + return self.finish(AquaModelApp().delete_registered_model(id)) else: raise HTTPError(400, f"The request {self.request.path} is invalid.") diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index b27d28c49..6920e653a 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -323,6 +323,15 @@ def get(self, model_id: str, load_model_card: Optional[bool] = True) -> "AquaMod return model_details + @telemetry(entry_point="plugin=model&action=delete", name="aqua") + def delete_registered_model(self,model_id): + ds_model=DataScienceModel.from_id(model_id) + is_registered_model=ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None) + if is_registered_model: + return ds_model.delete() + else: + raise AquaRuntimeError(f"Failed to delete model:{model_id}. Only registered models can be deleted.") + def _fetch_metric_from_metadata( self, custom_metadata_list: ModelCustomMetadata, diff --git a/ads/aqua/modeldeployment/deployment.py b/ads/aqua/modeldeployment/deployment.py index 654e00dc8..62de4d0fb 100644 --- a/ads/aqua/modeldeployment/deployment.py +++ b/ads/aqua/modeldeployment/deployment.py @@ -485,6 +485,15 @@ def list(self, **kwargs) -> List["AquaDeployment"]: return results + + @telemetry(entry_point="plugin=deployment&action=delete", name="aqua") + def delete(self,model_deployment_id:str): + return self.ds_client.delete_model_deployment(model_deployment_id=model_deployment_id).data + + @telemetry(entry_point="plugin=deployment&action=deactivate",name="aqua") + def deactivate(self,model_deployment_id:str): + return self.ds_client.deactivate_model_deployment(model_deployment_id=model_deployment_id).data + @telemetry(entry_point="plugin=deployment&action=get", name="aqua") def get(self, model_deployment_id: str, **kwargs) -> "AquaDeploymentDetail": """Gets the information of Aqua model deployment. From 75a5f5cecf82e674a3d3136a4a5142a95bd6b8cf Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Wed, 16 Oct 2024 16:02:44 +0530 Subject: [PATCH 2/9] Adding MD activate API --- ads/aqua/extension/deployment_handler.py | 24 ++++++++++++++++++++++-- ads/aqua/modeldeployment/deployment.py | 5 ++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index c8ae63273..508a2b0a5 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -59,8 +59,28 @@ def delete(self,model_deployment_id): return self.finish(AquaDeploymentApp().delete(model_deployment_id)) @handle_exceptions - def put(self,model_deployment_id): - return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) + def put(self,*args,**kwargs): + try: + input_data = self.get_json_body() + except Exception as ex: + raise HTTPError(400, Errors.INVALID_INPUT_DATA_FORMAT) from ex + if not input_data: + raise HTTPError(400, Errors.NO_INPUT_DATA) + + # required input parameters + model_deployment_id = input_data.get("model_deployment_id") + if not model_deployment_id: + raise HTTPError( + 400, Errors.MISSING_REQUIRED_PARAMETER.format("model_deployment_id") + ) + url_parse = urlparse(self.request.path) + paths = url_parse.path.strip("/") + if paths.startswith("aqua/deployments/activate"): + return self.finish(AquaDeploymentApp().activate(model_deployment_id)) + elif paths.startswith("aqua/deployments/deactivate"): + return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) + else: + raise HTTPError(400, f"The request {self.request.path} is invalid.") @handle_exceptions def post(self, *args, **kwargs): diff --git a/ads/aqua/modeldeployment/deployment.py b/ads/aqua/modeldeployment/deployment.py index 62de4d0fb..3fdceac04 100644 --- a/ads/aqua/modeldeployment/deployment.py +++ b/ads/aqua/modeldeployment/deployment.py @@ -485,7 +485,6 @@ def list(self, **kwargs) -> List["AquaDeployment"]: return results - @telemetry(entry_point="plugin=deployment&action=delete", name="aqua") def delete(self,model_deployment_id:str): return self.ds_client.delete_model_deployment(model_deployment_id=model_deployment_id).data @@ -494,6 +493,10 @@ def delete(self,model_deployment_id:str): def deactivate(self,model_deployment_id:str): return self.ds_client.deactivate_model_deployment(model_deployment_id=model_deployment_id).data + @telemetry(entry_point="plugin=deployment&action=activate",name="aqua") + def activate(self,model_deployment_id:str): + return self.ds_client.activate_model_deployment(model_deployment_id=model_deployment_id).data + @telemetry(entry_point="plugin=deployment&action=get", name="aqua") def get(self, model_deployment_id: str, **kwargs) -> "AquaDeploymentDetail": """Gets the information of Aqua model deployment. From c7ff5847b389357cd7713b769d6da4a056d2eb52 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Wed, 16 Oct 2024 22:12:22 +0530 Subject: [PATCH 3/9] Adding edit registered model api --- ads/aqua/extension/errors.py | 1 + ads/aqua/extension/model_handler.py | 23 +++++++++ ads/aqua/model/model.py | 72 +++++++++++++++++++++++++++-- 3 files changed, 93 insertions(+), 3 deletions(-) diff --git a/ads/aqua/extension/errors.py b/ads/aqua/extension/errors.py index d5e44944c..9829ff9e4 100644 --- a/ads/aqua/extension/errors.py +++ b/ads/aqua/extension/errors.py @@ -8,3 +8,4 @@ class Errors(str): NO_INPUT_DATA = "No input data provided." MISSING_REQUIRED_PARAMETER = "Missing required parameter: '{}'" MISSING_ONEOF_REQUIRED_PARAMETER = "Either '{}' or '{}' is required." + INVALID_VALUE_OF_PARAMETER = "Invalid value of parameter: '{}'" diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index f6c1d6c83..49f30457a 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -8,6 +8,7 @@ from tornado.web import HTTPError from ads.aqua.common.decorator import handle_exceptions +from ads.aqua.common.enums import InferenceContainerTypeFamily from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import get_hf_model_info, list_hf_models from ads.aqua.extension.base_handler import AquaAPIhandler @@ -139,6 +140,28 @@ def post(self, *args, **kwargs): ) ) + @handle_exceptions + def put(self,id): + try: + input_data = self.get_json_body() + except Exception as ex: + raise HTTPError(400, Errors.INVALID_INPUT_DATA_FORMAT) from ex + + if not input_data: + raise HTTPError(400, Errors.NO_INPUT_DATA) + + inference_container=input_data.get('inference_container') + if inference_container is not None and inference_container not in [ + InferenceContainerTypeFamily.AQUA_TGI_CONTAINER_FAMILY, + InferenceContainerTypeFamily.AQUA_VLLM_CONTAINER_FAMILY, + InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY + ]: + raise HTTPError(400,Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container")) + enable_finetuning=input_data.get('enable_finetuning') + task=input_data.get('task') + return self.finish(AquaModelApp().edit_registered_model(id,inference_container,enable_finetuning,task)) + + class AquaModelLicenseHandler(AquaAPIhandler): """Handler for Aqua Model license REST APIs.""" diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 6920e653a..27883e043 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -10,11 +10,11 @@ import oci from cachetools import TTLCache from huggingface_hub import snapshot_download -from oci.data_science.models import JobRun, Model +from oci.data_science.models import JobRun, Model, UpdateModelDetails, Metadata from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger from ads.aqua.app import AquaApp -from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags +from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags, FineTuningContainerTypeFamily from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import ( LifecycleStatus, @@ -75,7 +75,7 @@ TENANCY_OCID, ) from ads.model import DataScienceModel -from ads.model.model_metadata import ModelCustomMetadata, ModelCustomMetadataItem +from ads.model.model_metadata import ModelCustomMetadata, ModelCustomMetadataItem, MetadataCustomCategory from ads.telemetry import telemetry @@ -332,6 +332,72 @@ def delete_registered_model(self,model_id): else: raise AquaRuntimeError(f"Failed to delete model:{model_id}. Only registered models can be deleted.") + @telemetry(entry_point="plugin=model&action=delete", name="aqua") + def edit_registered_model(self,id,inference_container,enable_finetuning,task): + """Edits the default config of unverified registered model. + + Parameters + ---------- + id: str + The model OCID. + inference_container: str. + The inference container family name + enable_finetuning: str + Flag to enable or disable finetuning over the model. Defaults to None + + Returns + ------- + Model: + The instance of oci.data_science.models.Model. + + """ + ds_model=DataScienceModel.from_id(id) + if ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None): + if ds_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG,None): + raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be edited.") + else: + custom_metadata_list=ds_model.custom_metadata_list + freeform_tags=ds_model.freeform_tags + if inference_container: + custom_metadata_list.add(key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER, + value=inference_container, + category=MetadataCustomCategory.OTHER, + description="Deployment container mapping for SMC", + replace=True + ) + if enable_finetuning is not None: + if enable_finetuning.lower()=="true": + custom_metadata_list.add(key=ModelCustomMetadataFields.FINETUNE_CONTAINER, + value=FineTuningContainerTypeFamily.AQUA_FINETUNING_CONTAINER_FAMILY, + category=MetadataCustomCategory.OTHER, + description="Fine-tuning container mapping for SMC", + replace=True + ) + freeform_tags.update({Tags.READY_TO_FINE_TUNE:"true"}) + elif enable_finetuning.lower()=="false": + try: + custom_metadata_list.remove(ModelCustomMetadataFields.FINETUNE_CONTAINER) + freeform_tags.pop(Tags.READY_TO_FINE_TUNE) + except Exception as ex: + raise AquaRuntimeError(f"The given model already doesn't support finetuning: {ex}") + + custom_metadata_list.remove("modelDescription") + if task: + freeform_tags.update({"task":task}) + + updated_custom_metadata_list = [ + Metadata(**metadata) + for metadata in custom_metadata_list.to_dict()["data"] + ] + update_model_details = UpdateModelDetails( + custom_metadata_list=updated_custom_metadata_list, + freeform_tags=freeform_tags + ) + return self.ds_client.update_model(id,update_model_details).data + else: + raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be deleted.") + + def _fetch_metric_from_metadata( self, custom_metadata_list: ModelCustomMetadata, From 5688652a859b7a745b5c1f5da5d7acac84faa5ea Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Sun, 20 Oct 2024 18:36:24 +0530 Subject: [PATCH 4/9] Addressing review comments --- ads/aqua/extension/deployment_handler.py | 35 +++--- ads/aqua/extension/model_handler.py | 42 ++++--- ads/aqua/model/model.py | 134 ++++++++++++----------- 3 files changed, 117 insertions(+), 94 deletions(-) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index 508a2b0a5..381c24b85 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -55,29 +55,28 @@ def get(self, id=""): raise HTTPError(400, f"The request {self.request.path} is invalid.") @handle_exceptions - def delete(self,model_deployment_id): + def delete(self, model_deployment_id): return self.finish(AquaDeploymentApp().delete(model_deployment_id)) @handle_exceptions - def put(self,*args,**kwargs): - try: - input_data = self.get_json_body() - except Exception as ex: - raise HTTPError(400, Errors.INVALID_INPUT_DATA_FORMAT) from ex - if not input_data: - raise HTTPError(400, Errors.NO_INPUT_DATA) - - # required input parameters - model_deployment_id = input_data.get("model_deployment_id") - if not model_deployment_id: - raise HTTPError( - 400, Errors.MISSING_REQUIRED_PARAMETER.format("model_deployment_id") - ) + def put(self, *args, **kwargs): + """ + Handles put request for the activating and deactivating OCI datascience model deployments + Raises + ------ + HTTPError + Raises HTTPError if inputs are missing or are invalid + """ url_parse = urlparse(self.request.path) - paths = url_parse.path.strip("/") - if paths.startswith("aqua/deployments/activate"): + paths = url_parse.path.strip("/").split("/") + if len(paths) != 4 or paths[0] != "aqua" or paths[1] != "deployments": + raise HTTPError(400, f"The request {self.request.path} is invalid.") + + model_deployment_id = paths[2] + action = paths[3] + if action == "activate": return self.finish(AquaDeploymentApp().activate(model_deployment_id)) - elif paths.startswith("aqua/deployments/deactivate"): + elif action == "deactivate": return self.finish(AquaDeploymentApp().deactivate(model_deployment_id)) else: raise HTTPError(400, f"The request {self.request.path} is invalid.") diff --git a/ads/aqua/extension/model_handler.py b/ads/aqua/extension/model_handler.py index 49f30457a..279ea412b 100644 --- a/ads/aqua/extension/model_handler.py +++ b/ads/aqua/extension/model_handler.py @@ -8,14 +8,17 @@ from tornado.web import HTTPError from ads.aqua.common.decorator import handle_exceptions -from ads.aqua.common.enums import InferenceContainerTypeFamily from ads.aqua.common.errors import AquaRuntimeError, AquaValueError -from ads.aqua.common.utils import get_hf_model_info, list_hf_models +from ads.aqua.common.utils import ( + get_container_config, + get_hf_model_info, + list_hf_models, +) from ads.aqua.extension.base_handler import AquaAPIhandler from ads.aqua.extension.errors import Errors from ads.aqua.model import AquaModelApp from ads.aqua.model.entities import AquaModelSummary, HFModelSummary -from ads.aqua.ui import ModelFormat +from ads.aqua.ui import AquaContainerConfig, ModelFormat class AquaModelHandler(AquaAPIhandler): @@ -75,7 +78,7 @@ def delete(self, id=""): if paths.startswith("aqua/model/cache"): return self.finish(AquaModelApp().clear_model_list_cache()) elif id: - return self.finish(AquaModelApp().delete_registered_model(id)) + return self.finish(AquaModelApp().delete_model(id)) else: raise HTTPError(400, f"The request {self.request.path} is invalid.") @@ -141,7 +144,7 @@ def post(self, *args, **kwargs): ) @handle_exceptions - def put(self,id): + def put(self, id): try: input_data = self.get_json_body() except Exception as ex: @@ -150,17 +153,26 @@ def put(self,id): if not input_data: raise HTTPError(400, Errors.NO_INPUT_DATA) - inference_container=input_data.get('inference_container') - if inference_container is not None and inference_container not in [ - InferenceContainerTypeFamily.AQUA_TGI_CONTAINER_FAMILY, - InferenceContainerTypeFamily.AQUA_VLLM_CONTAINER_FAMILY, - InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY - ]: - raise HTTPError(400,Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container")) - enable_finetuning=input_data.get('enable_finetuning') - task=input_data.get('task') - return self.finish(AquaModelApp().edit_registered_model(id,inference_container,enable_finetuning,task)) + inference_container = input_data.get("inference_container") + containers = list( + AquaContainerConfig.from_container_index_json( + config=get_container_config(), enable_spec=True + ).inference.values() + ) + family_values = [item.family for item in containers] + + if inference_container is not None and inference_container not in family_values: + raise HTTPError( + 400, Errors.INVALID_VALUE_OF_PARAMETER.format("inference_container") + ) + enable_finetuning = input_data.get("enable_finetuning") + task = input_data.get("task") + return self.finish( + AquaModelApp().edit_registered_model( + id, inference_container, enable_finetuning, task + ) + ) class AquaModelLicenseHandler(AquaAPIhandler): diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 27883e043..aef18fc88 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -10,11 +10,15 @@ import oci from cachetools import TTLCache from huggingface_hub import snapshot_download -from oci.data_science.models import JobRun, Model, UpdateModelDetails, Metadata +from oci.data_science.models import JobRun, Metadata, Model, UpdateModelDetails from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger from ads.aqua.app import AquaApp -from ads.aqua.common.enums import InferenceContainerTypeFamily, Tags, FineTuningContainerTypeFamily +from ads.aqua.common.enums import ( + FineTuningContainerTypeFamily, + InferenceContainerTypeFamily, + Tags, +) from ads.aqua.common.errors import AquaRuntimeError, AquaValueError from ads.aqua.common.utils import ( LifecycleStatus, @@ -75,7 +79,11 @@ TENANCY_OCID, ) from ads.model import DataScienceModel -from ads.model.model_metadata import ModelCustomMetadata, ModelCustomMetadataItem, MetadataCustomCategory +from ads.model.model_metadata import ( + MetadataCustomCategory, + ModelCustomMetadata, + ModelCustomMetadataItem, +) from ads.telemetry import telemetry @@ -324,16 +332,21 @@ def get(self, model_id: str, load_model_card: Optional[bool] = True) -> "AquaMod return model_details @telemetry(entry_point="plugin=model&action=delete", name="aqua") - def delete_registered_model(self,model_id): - ds_model=DataScienceModel.from_id(model_id) - is_registered_model=ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None) - if is_registered_model: + def delete_model(self, model_id): + ds_model = DataScienceModel.from_id(model_id) + is_registered_model = ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM, None) + is_fine_tuned_model = ds_model.freeform_tags.get( + Tags.AQUA_FINE_TUNED_MODEL_TAG, None + ) + if is_registered_model or is_fine_tuned_model: return ds_model.delete() else: - raise AquaRuntimeError(f"Failed to delete model:{model_id}. Only registered models can be deleted.") + raise AquaRuntimeError( + f"Failed to delete model:{model_id}. Only registered models or finetuned model can be deleted." + ) @telemetry(entry_point="plugin=model&action=delete", name="aqua") - def edit_registered_model(self,id,inference_container,enable_finetuning,task): + def edit_registered_model(self, id, inference_container, enable_finetuning, task): """Edits the default config of unverified registered model. Parameters @@ -351,39 +364,47 @@ def edit_registered_model(self,id,inference_container,enable_finetuning,task): The instance of oci.data_science.models.Model. """ - ds_model=DataScienceModel.from_id(id) - if ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM,None): - if ds_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG,None): - raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be edited.") + ds_model = DataScienceModel.from_id(id) + if ds_model.freeform_tags.get(Tags.BASE_MODEL_CUSTOM, None): + if ds_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG, None): + raise AquaRuntimeError( + f"Failed to edit model:{id}. Only registered unverified models can be edited." + ) else: - custom_metadata_list=ds_model.custom_metadata_list - freeform_tags=ds_model.freeform_tags + custom_metadata_list = ds_model.custom_metadata_list + freeform_tags = ds_model.freeform_tags if inference_container: - custom_metadata_list.add(key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER, - value=inference_container, - category=MetadataCustomCategory.OTHER, - description="Deployment container mapping for SMC", - replace=True - ) + custom_metadata_list.add( + key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER, + value=inference_container, + category=MetadataCustomCategory.OTHER, + description="Deployment container mapping for SMC", + replace=True, + ) if enable_finetuning is not None: - if enable_finetuning.lower()=="true": - custom_metadata_list.add(key=ModelCustomMetadataFields.FINETUNE_CONTAINER, - value=FineTuningContainerTypeFamily.AQUA_FINETUNING_CONTAINER_FAMILY, - category=MetadataCustomCategory.OTHER, - description="Fine-tuning container mapping for SMC", - replace=True - ) - freeform_tags.update({Tags.READY_TO_FINE_TUNE:"true"}) - elif enable_finetuning.lower()=="false": + if enable_finetuning.lower() == "true": + custom_metadata_list.add( + key=ModelCustomMetadataFields.FINETUNE_CONTAINER, + value=FineTuningContainerTypeFamily.AQUA_FINETUNING_CONTAINER_FAMILY, + category=MetadataCustomCategory.OTHER, + description="Fine-tuning container mapping for SMC", + replace=True, + ) + freeform_tags.update({Tags.READY_TO_FINE_TUNE: "true"}) + elif enable_finetuning.lower() == "false": try: - custom_metadata_list.remove(ModelCustomMetadataFields.FINETUNE_CONTAINER) + custom_metadata_list.remove( + ModelCustomMetadataFields.FINETUNE_CONTAINER + ) freeform_tags.pop(Tags.READY_TO_FINE_TUNE) except Exception as ex: - raise AquaRuntimeError(f"The given model already doesn't support finetuning: {ex}") + raise AquaRuntimeError( + f"The given model already doesn't support finetuning: {ex}" + ) custom_metadata_list.remove("modelDescription") if task: - freeform_tags.update({"task":task}) + freeform_tags.update({"task": task}) updated_custom_metadata_list = [ Metadata(**metadata) @@ -391,12 +412,13 @@ def edit_registered_model(self,id,inference_container,enable_finetuning,task): ] update_model_details = UpdateModelDetails( custom_metadata_list=updated_custom_metadata_list, - freeform_tags=freeform_tags + freeform_tags=freeform_tags, ) - return self.ds_client.update_model(id,update_model_details).data + return AquaApp().update_model(id, update_model_details).data else: - raise AquaRuntimeError(f"Failed to edit model:{id}. Only registered unverified models can be deleted.") - + raise AquaRuntimeError( + f"Failed to edit model:{id}. Only registered unverified models can be deleted." + ) def _fetch_metric_from_metadata( self, @@ -1010,14 +1032,15 @@ def _validate_model( # gguf extension exist. if {ModelFormat.SAFETENSORS, ModelFormat.GGUF}.issubset(set(model_formats)): if ( - import_model_details.inference_container.lower() == InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY + import_model_details.inference_container.lower() + == InferenceContainerTypeFamily.AQUA_LLAMA_CPP_CONTAINER_FAMILY ): self._validate_gguf_format( import_model_details=import_model_details, verified_model=verified_model, gguf_model_files=gguf_model_files, validation_result=validation_result, - model_name=model_name + model_name=model_name, ) else: self._validate_safetensor_format( @@ -1025,7 +1048,7 @@ def _validate_model( verified_model=verified_model, validation_result=validation_result, hf_download_config_present=hf_download_config_present, - model_name=model_name + model_name=model_name, ) elif ModelFormat.SAFETENSORS in model_formats: self._validate_safetensor_format( @@ -1033,7 +1056,7 @@ def _validate_model( verified_model=verified_model, validation_result=validation_result, hf_download_config_present=hf_download_config_present, - model_name=model_name + model_name=model_name, ) elif ModelFormat.GGUF in model_formats: self._validate_gguf_format( @@ -1041,7 +1064,7 @@ def _validate_model( verified_model=verified_model, gguf_model_files=gguf_model_files, validation_result=validation_result, - model_name=model_name + model_name=model_name, ) return validation_result @@ -1052,7 +1075,7 @@ def _validate_safetensor_format( verified_model: DataScienceModel = None, validation_result: ModelValidationResult = None, hf_download_config_present: bool = None, - model_name: str = None + model_name: str = None, ): if import_model_details.download_from_hf: # validates config.json exists for safetensors model from hugginface @@ -1079,20 +1102,13 @@ def _validate_safetensor_format( ) from ex else: try: - metadata_model_type = ( - verified_model.custom_metadata_list.get( - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - ).value - ) + metadata_model_type = verified_model.custom_metadata_list.get( + AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE + ).value if metadata_model_type: - if ( - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - in model_config - ): + if AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE in model_config: if ( - model_config[ - AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE - ] + model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE] != metadata_model_type ): raise AquaRuntimeError( @@ -1110,9 +1126,7 @@ def _validate_safetensor_format( except Exception: pass if verified_model: - validation_result.telemetry_model_name = ( - verified_model.display_name - ) + validation_result.telemetry_model_name = verified_model.display_name elif ( model_config is not None and AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME in model_config @@ -1124,9 +1138,7 @@ def _validate_safetensor_format( ): validation_result.telemetry_model_name = f"{AQUA_MODEL_TYPE_CUSTOM}_{model_config[AQUA_MODEL_ARTIFACT_CONFIG_MODEL_TYPE]}" else: - validation_result.telemetry_model_name = ( - AQUA_MODEL_TYPE_CUSTOM - ) + validation_result.telemetry_model_name = AQUA_MODEL_TYPE_CUSTOM @staticmethod def _validate_gguf_format( From fb60ed0b79ef8b470b00c6381e2915af51c06956 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Sun, 20 Oct 2024 19:21:45 +0530 Subject: [PATCH 5/9] Adding review comments --- ads/aqua/extension/deployment_handler.py | 2 ++ ads/aqua/model/model.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/ads/aqua/extension/deployment_handler.py b/ads/aqua/extension/deployment_handler.py index 381c24b85..72df4200f 100644 --- a/ads/aqua/extension/deployment_handler.py +++ b/ads/aqua/extension/deployment_handler.py @@ -291,5 +291,7 @@ def post(self, *args, **kwargs): ("deployments/?([^/]*)/params", AquaDeploymentParamsHandler), ("deployments/config/?([^/]*)", AquaDeploymentHandler), ("deployments/?([^/]*)", AquaDeploymentHandler), + ("deployments/?([^/]*)/activate", AquaDeploymentHandler), + ("deployments/?([^/]*)/deactivate", AquaDeploymentHandler), ("inference", AquaDeploymentInferenceHandler), ] diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index aef18fc88..402b63432 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -404,7 +404,7 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task custom_metadata_list.remove("modelDescription") if task: - freeform_tags.update({"task": task}) + freeform_tags.update({Tags.TASK: task}) updated_custom_metadata_list = [ Metadata(**metadata) From e1b985f8e19ab57b3251b232daeb60983e68c55d Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Sun, 20 Oct 2024 19:27:20 +0530 Subject: [PATCH 6/9] Adding review comments --- ads/aqua/model/model.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 402b63432..03e4b595e 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -357,6 +357,8 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task The inference container family name enable_finetuning: str Flag to enable or disable finetuning over the model. Defaults to None + task: + The usecase type of the model. e.g , text-generation , text_embedding etc. Returns ------- From 10d06ccb804b47b847a9745e502e834910e83fb5 Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 22 Oct 2024 16:45:58 +0530 Subject: [PATCH 7/9] Adding UT for delete model --- .../with_extras/aqua/test_model_handler.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index cb7a27080..f5015763a 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -19,7 +19,6 @@ AquaModelLicenseHandler, ) from ads.aqua.model import AquaModelApp -from ads.aqua.model.constants import ModelTask from ads.aqua.model.entities import AquaModel, AquaModelSummary, HFModelSummary @@ -79,6 +78,21 @@ def test_delete(self, mock_urlparse, mock_clear_model_list_cache): mock_urlparse.assert_called() mock_clear_model_list_cache.assert_called() + @patch("ads.aqua.extension.model_handler.urlparse") + @patch.object(AquaModelApp, "delete_model") + def test_delete_with_id(self, mock_delete, mock_urlparse): + request_path = MagicMock(path="aqua/model/ocid1.datasciencemodel.oc1.iad.xxx") + mock_urlparse.return_value = request_path + mock_delete.return_value = {"state": "DELETED"} + with patch( + "ads.aqua.extension.base_handler.AquaAPIhandler.finish" + ) as mock_finish: + mock_finish.side_effect = lambda x: x + result = self.model_handler.delete(id="ocid1.datasciencemodel.oc1.iad.xxx") + assert result["state"] is "DELETED" + mock_urlparse.assert_called() + mock_delete.assert_called() + @patch.object(AquaModelApp, "list") def test_list(self, mock_list): with patch( From 5baf623d3a46a220702ecb938136cd53a6ab3b6c Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 22 Oct 2024 21:55:04 +0530 Subject: [PATCH 8/9] Adding UTs for model deployment --- .../aqua/test_deployment_handler.py | 20 ++++++++++++++ .../with_extras/aqua/test_model_handler.py | 27 +++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/tests/unitary/with_extras/aqua/test_deployment_handler.py b/tests/unitary/with_extras/aqua/test_deployment_handler.py index 54756545a..75999fade 100644 --- a/tests/unitary/with_extras/aqua/test_deployment_handler.py +++ b/tests/unitary/with_extras/aqua/test_deployment_handler.py @@ -92,6 +92,26 @@ def test_get_deployment(self, mock_get): self.deployment_handler.get(id="mock-model-id") mock_get.assert_called() + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.delete") + def test_delete_deployment(self,mock_delete): + self.deployment_handler.request.path = "aqua/deployments" + self.deployment_handler.delete("mock-model-id") + mock_delete.assert_called() + + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.activate") + def test_activate_deployment(self,mock_activate): + self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/activate" + mock_activate.return_value={"lifecycle_state":"UPDATING"} + self.deployment_handler.put() + mock_activate.assert_called() + + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.deactivate") + def test_deactivate_deployment(self,mock_deactivate): + self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/deactivate" + mock_deactivate.return_value={"lifecycle_state":"UPDATING"} + self.deployment_handler.put() + mock_deactivate.assert_called() + @patch("ads.aqua.modeldeployment.AquaDeploymentApp.list") def test_list_deployment(self, mock_list): """Test get method to return a list of model deployments.""" diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index f5015763a..58148fc95 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -20,6 +20,7 @@ ) from ads.aqua.model import AquaModelApp from ads.aqua.model.entities import AquaModel, AquaModelSummary, HFModelSummary +from ads.aqua.ui import AquaContainerConfig class ModelHandlerTestCase(TestCase): @@ -93,6 +94,32 @@ def test_delete_with_id(self, mock_delete, mock_urlparse): mock_urlparse.assert_called() mock_delete.assert_called() + @patch.object(AquaContainerConfig,"from_container_index_json") + @patch.object(AquaModelApp,"edit_registered_model") + def test_put(self,mock_edit,mock_container_index): + mock_edit.return_value={"state":"EDITED"} + mock_inference = MagicMock() + mock_inference.values.return_value = [ + MagicMock(family="odsc-vllm-serving"), + MagicMock(family="odsc-tgi-serving"), + MagicMock(family="odsc-vllm-serving"), + ] + + mock_container_index.return_value = MagicMock(inference=mock_inference) + self.model_handler.get_json_body = MagicMock( + return_value=dict( + task="text_generation", + enable_finetuning="true", + inference_container="odsc-tgi-serving", + ) + ) + with patch("ads.aqua.extension.base_handler.AquaAPIhandler.finish") as mock_finish: + mock_finish.side_effect = lambda x: x + result = self.model_handler.put(id="ocid1.datasciencemodel.oc1.iad.xxx") + print(f"result: ",result) + assert result["state"] is "EDITED" + mock_edit.assert_called() + @patch.object(AquaModelApp, "list") def test_list(self, mock_list): with patch( From 3582437e5bdead848a46a9b5ee4e2e809cc46eaa Mon Sep 17 00:00:00 2001 From: kumar shivam ranjan Date: Tue, 22 Oct 2024 22:02:10 +0530 Subject: [PATCH 9/9] formatting --- ads/aqua/model/model.py | 2 +- .../aqua/test_deployment_handler.py | 18 +++++++++++------- .../with_extras/aqua/test_model_handler.py | 14 ++++++++------ 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/ads/aqua/model/model.py b/ads/aqua/model/model.py index 03e4b595e..d07c4ecab 100644 --- a/ads/aqua/model/model.py +++ b/ads/aqua/model/model.py @@ -419,7 +419,7 @@ def edit_registered_model(self, id, inference_container, enable_finetuning, task return AquaApp().update_model(id, update_model_details).data else: raise AquaRuntimeError( - f"Failed to edit model:{id}. Only registered unverified models can be deleted." + f"Failed to edit model:{id}. Only registered unverified models can be edited." ) def _fetch_metric_from_metadata( diff --git a/tests/unitary/with_extras/aqua/test_deployment_handler.py b/tests/unitary/with_extras/aqua/test_deployment_handler.py index 75999fade..a3c843113 100644 --- a/tests/unitary/with_extras/aqua/test_deployment_handler.py +++ b/tests/unitary/with_extras/aqua/test_deployment_handler.py @@ -93,22 +93,26 @@ def test_get_deployment(self, mock_get): mock_get.assert_called() @patch("ads.aqua.modeldeployment.AquaDeploymentApp.delete") - def test_delete_deployment(self,mock_delete): + def test_delete_deployment(self, mock_delete): self.deployment_handler.request.path = "aqua/deployments" self.deployment_handler.delete("mock-model-id") mock_delete.assert_called() @patch("ads.aqua.modeldeployment.AquaDeploymentApp.activate") - def test_activate_deployment(self,mock_activate): - self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/activate" - mock_activate.return_value={"lifecycle_state":"UPDATING"} + def test_activate_deployment(self, mock_activate): + self.deployment_handler.request.path = ( + "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/activate" + ) + mock_activate.return_value = {"lifecycle_state": "UPDATING"} self.deployment_handler.put() mock_activate.assert_called() @patch("ads.aqua.modeldeployment.AquaDeploymentApp.deactivate") - def test_deactivate_deployment(self,mock_deactivate): - self.deployment_handler.request.path = "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/deactivate" - mock_deactivate.return_value={"lifecycle_state":"UPDATING"} + def test_deactivate_deployment(self, mock_deactivate): + self.deployment_handler.request.path = ( + "aqua/deployments/ocid1.datasciencemodeldeployment.oc1.iad.xxx/deactivate" + ) + mock_deactivate.return_value = {"lifecycle_state": "UPDATING"} self.deployment_handler.put() mock_deactivate.assert_called() diff --git a/tests/unitary/with_extras/aqua/test_model_handler.py b/tests/unitary/with_extras/aqua/test_model_handler.py index 58148fc95..db5475798 100644 --- a/tests/unitary/with_extras/aqua/test_model_handler.py +++ b/tests/unitary/with_extras/aqua/test_model_handler.py @@ -94,10 +94,10 @@ def test_delete_with_id(self, mock_delete, mock_urlparse): mock_urlparse.assert_called() mock_delete.assert_called() - @patch.object(AquaContainerConfig,"from_container_index_json") - @patch.object(AquaModelApp,"edit_registered_model") - def test_put(self,mock_edit,mock_container_index): - mock_edit.return_value={"state":"EDITED"} + @patch.object(AquaContainerConfig, "from_container_index_json") + @patch.object(AquaModelApp, "edit_registered_model") + def test_put(self, mock_edit, mock_container_index): + mock_edit.return_value = {"state": "EDITED"} mock_inference = MagicMock() mock_inference.values.return_value = [ MagicMock(family="odsc-vllm-serving"), @@ -113,10 +113,12 @@ def test_put(self,mock_edit,mock_container_index): inference_container="odsc-tgi-serving", ) ) - with patch("ads.aqua.extension.base_handler.AquaAPIhandler.finish") as mock_finish: + with patch( + "ads.aqua.extension.base_handler.AquaAPIhandler.finish" + ) as mock_finish: mock_finish.side_effect = lambda x: x result = self.model_handler.put(id="ocid1.datasciencemodel.oc1.iad.xxx") - print(f"result: ",result) + print(f"result: ", result) assert result["state"] is "EDITED" mock_edit.assert_called()