oracle-ads 2.13.4__py3-none-any.whl → 2.13.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
ads/aqua/model/model.py CHANGED
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  # Copyright (c) 2024, 2025 Oracle and/or its affiliates.
3
3
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
4
+ import json
4
5
  import os
5
6
  import pathlib
6
7
  from datetime import datetime, timedelta
@@ -14,6 +15,7 @@ from oci.data_science.models import JobRun, Metadata, Model, UpdateModelDetails
14
15
 
15
16
  from ads.aqua import ODSC_MODEL_COMPARTMENT_OCID, logger
16
17
  from ads.aqua.app import AquaApp
18
+ from ads.aqua.common.entities import AquaMultiModelRef
17
19
  from ads.aqua.common.enums import (
18
20
  ConfigFolder,
19
21
  CustomInferenceContainerTypeFamily,
@@ -42,7 +44,7 @@ from ads.aqua.common.utils import (
42
44
  read_file,
43
45
  upload_folder,
44
46
  )
45
- from ads.aqua.config.container_config import AquaContainerConfig
47
+ from ads.aqua.config.container_config import AquaContainerConfig, Usage
46
48
  from ads.aqua.constants import (
47
49
  AQUA_MODEL_ARTIFACT_CONFIG,
48
50
  AQUA_MODEL_ARTIFACT_CONFIG_MODEL_NAME,
@@ -77,6 +79,7 @@ from ads.aqua.model.entities import (
77
79
  ImportModelDetails,
78
80
  ModelValidationResult,
79
81
  )
82
+ from ads.aqua.model.enums import MultiModelSupportedTaskType
80
83
  from ads.common.auth import default_signer
81
84
  from ads.common.oci_resource import SEARCH_TYPE, OCIResource
82
85
  from ads.common.utils import UNKNOWN, get_console_link
@@ -91,6 +94,7 @@ from ads.config import (
91
94
  TENANCY_OCID,
92
95
  )
93
96
  from ads.model import DataScienceModel
97
+ from ads.model.common.utils import MetadataArtifactPathType
94
98
  from ads.model.model_metadata import (
95
99
  MetadataCustomCategory,
96
100
  ModelCustomMetadata,
@@ -135,40 +139,45 @@ class AquaModelApp(AquaApp):
135
139
  @telemetry(entry_point="plugin=model&action=create", name="aqua")
136
140
  def create(
137
141
  self,
138
- model_id: str,
139
- project_id: str,
140
- compartment_id: str = None,
141
- freeform_tags: Optional[dict] = None,
142
- defined_tags: Optional[dict] = None,
142
+ model_id: Union[str, AquaMultiModelRef],
143
+ project_id: Optional[str] = None,
144
+ compartment_id: Optional[str] = None,
145
+ freeform_tags: Optional[Dict] = None,
146
+ defined_tags: Optional[Dict] = None,
143
147
  **kwargs,
144
148
  ) -> DataScienceModel:
145
- """Creates custom aqua model from service model.
149
+ """
150
+ Creates a custom Aqua model from a service model.
146
151
 
147
152
  Parameters
148
153
  ----------
149
- model_id: str
150
- The service model id.
151
- project_id: str
152
- The project id for custom model.
153
- compartment_id: str
154
- The compartment id for custom model. Defaults to None.
155
- If not provided, compartment id will be fetched from environment variables.
156
- freeform_tags: dict
157
- Freeform tags for the model
158
- defined_tags: dict
159
- Defined tags for the model
154
+ model_id : Union[str, AquaMultiModelRef]
155
+ The model ID as a string or a AquaMultiModelRef instance to be deployed.
156
+ project_id : Optional[str]
157
+ The project ID for the custom model.
158
+ compartment_id : Optional[str]
159
+ The compartment ID for the custom model. Defaults to None.
160
+ If not provided, the compartment ID will be fetched from environment variables.
161
+ freeform_tags : Optional[Dict]
162
+ Freeform tags for the model.
163
+ defined_tags : Optional[Dict]
164
+ Defined tags for the model.
165
+
160
166
  Returns
161
167
  -------
162
- DataScienceModel:
168
+ DataScienceModel
163
169
  The instance of DataScienceModel.
164
170
  """
171
+ model_id = (
172
+ model_id.model_id if isinstance(model_id, AquaMultiModelRef) else model_id
173
+ )
165
174
  service_model = DataScienceModel.from_id(model_id)
166
175
  target_project = project_id or PROJECT_OCID
167
176
  target_compartment = compartment_id or COMPARTMENT_OCID
168
177
 
169
178
  if service_model.compartment_id != ODSC_MODEL_COMPARTMENT_OCID:
170
179
  logger.info(
171
- f"Aqua Model {model_id} already exists in user's compartment."
180
+ f"Aqua Model {model_id} already exists in the user's compartment."
172
181
  "Skipped copying."
173
182
  )
174
183
  return service_model
@@ -195,14 +204,13 @@ class AquaModelApp(AquaApp):
195
204
  .with_custom_metadata_list(service_model.custom_metadata_list)
196
205
  .with_defined_metadata_list(service_model.defined_metadata_list)
197
206
  .with_provenance_metadata(service_model.provenance_metadata)
198
- # TODO: decide what kwargs will be needed.
199
207
  .create(model_by_reference=True, **kwargs)
200
208
  )
201
209
  logger.info(
202
210
  f"Aqua Model {custom_model.id} created with the service model {model_id}."
203
211
  )
204
212
 
205
- # tracks unique models that were created in the user compartment
213
+ # Track unique models that were created in the user's compartment
206
214
  self.telemetry.record_event_async(
207
215
  category="aqua/service/model",
208
216
  action="create",
@@ -211,6 +219,207 @@ class AquaModelApp(AquaApp):
211
219
 
212
220
  return custom_model
213
221
 
222
+ @telemetry(entry_point="plugin=model&action=create", name="aqua")
223
+ def create_multi(
224
+ self,
225
+ models: List[AquaMultiModelRef],
226
+ project_id: Optional[str] = None,
227
+ compartment_id: Optional[str] = None,
228
+ freeform_tags: Optional[Dict] = None,
229
+ defined_tags: Optional[Dict] = None,
230
+ **kwargs, # noqa: ARG002
231
+ ) -> DataScienceModel:
232
+ """
233
+ Creates a multi-model grouping using the provided model list.
234
+
235
+ Parameters
236
+ ----------
237
+ models : List[AquaMultiModelRef]
238
+ List of AquaMultiModelRef instances for creating a multi-model group.
239
+ project_id : Optional[str]
240
+ The project ID for the multi-model group.
241
+ compartment_id : Optional[str]
242
+ The compartment ID for the multi-model group.
243
+ freeform_tags : Optional[Dict]
244
+ Freeform tags for the model.
245
+ defined_tags : Optional[Dict]
246
+ Defined tags for the model.
247
+
248
+ Returns
249
+ -------
250
+ DataScienceModel
251
+ Instance of DataScienceModel object.
252
+ """
253
+
254
+ if not models:
255
+ raise AquaValueError(
256
+ "Model list cannot be empty. Please provide at least one model for deployment."
257
+ )
258
+
259
+ artifact_list = []
260
+ display_name_list = []
261
+ model_custom_metadata = ModelCustomMetadata()
262
+
263
+ # Get container config
264
+ container_config = get_container_config()
265
+
266
+ service_inference_containers = AquaContainerConfig.from_container_index_json(
267
+ config=container_config
268
+ ).inference.values()
269
+
270
+ supported_container_families = [
271
+ container_config_item.family
272
+ for container_config_item in service_inference_containers
273
+ if any(
274
+ usage in container_config_item.usages
275
+ for usage in [Usage.MULTI_MODEL, Usage.OTHER]
276
+ )
277
+ ]
278
+
279
+ if not supported_container_families:
280
+ raise AquaValueError(
281
+ "Currently, there are no containers that support multi-model deployment."
282
+ )
283
+
284
+ selected_models_deployment_containers = set()
285
+
286
+ # Process each model
287
+ for model in models:
288
+ source_model = DataScienceModel.from_id(model.model_id)
289
+ display_name = source_model.display_name
290
+ # Update model name in user's input model
291
+ model.model_name = model.model_name or display_name
292
+
293
+ # TODO Uncomment the section below, if only service models should be allowed for multi-model deployment
294
+ # if not source_model.freeform_tags.get(Tags.AQUA_SERVICE_MODEL_TAG, UNKNOWN):
295
+ # raise AquaValueError(
296
+ # f"Invalid selected model {display_name}. "
297
+ # "Currently only service models are supported for multi model deployment."
298
+ # )
299
+
300
+ if (
301
+ source_model.freeform_tags.get(Tags.TASK, UNKNOWN).lower()
302
+ not in MultiModelSupportedTaskType
303
+ ):
304
+ raise AquaValueError(
305
+ f"Invalid or missing {Tags.TASK} tag for selected model {display_name}. "
306
+ f"Currently only `{MultiModelSupportedTaskType.values()}` models are supported for multi model deployment."
307
+ )
308
+
309
+ display_name_list.append(display_name)
310
+
311
+ # Retrieve model artifact
312
+ model_artifact_path = source_model.artifact
313
+ if not model_artifact_path:
314
+ raise AquaValueError(
315
+ f"Model '{display_name}' (ID: {model.model_id}) has no artifacts. "
316
+ "Please register the model first."
317
+ )
318
+
319
+ # Update model artifact location in user's input model
320
+ model.artifact_location = model_artifact_path
321
+
322
+ artifact_list.append(model_artifact_path)
323
+
324
+ # Validate deployment container consistency
325
+ deployment_container = source_model.custom_metadata_list.get(
326
+ ModelCustomMetadataFields.DEPLOYMENT_CONTAINER,
327
+ ModelCustomMetadataItem(
328
+ key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER
329
+ ),
330
+ ).value
331
+
332
+ if deployment_container not in supported_container_families:
333
+ raise AquaValueError(
334
+ f"Unsupported deployment container '{deployment_container}' for model '{source_model.id}'. "
335
+ f"Only '{supported_container_families}' are supported for multi-model deployments."
336
+ )
337
+
338
+ selected_models_deployment_containers.add(deployment_container)
339
+
340
+ # Check if the all models in the group shares same container family
341
+ if len(selected_models_deployment_containers) > 1:
342
+ raise AquaValueError(
343
+ "The selected models are associated with different container families: "
344
+ f"{list(selected_models_deployment_containers)}."
345
+ "For multi-model deployment, all models in the group must share the same container family."
346
+ )
347
+
348
+ deployment_container = selected_models_deployment_containers.pop()
349
+
350
+ # Generate model group details
351
+ timestamp = datetime.now().strftime("%Y%m%d")
352
+ model_group_display_name = f"model_group_{timestamp}"
353
+ combined_models = ", ".join(display_name_list)
354
+ model_group_description = f"Multi-model grouping using {combined_models}."
355
+
356
+ # Add global metadata
357
+ model_custom_metadata.add(
358
+ key=ModelCustomMetadataFields.DEPLOYMENT_CONTAINER,
359
+ value=deployment_container,
360
+ description=f"Inference container mapping for {model_group_display_name}",
361
+ category="Other",
362
+ )
363
+ model_custom_metadata.add(
364
+ key=ModelCustomMetadataFields.MULTIMODEL_GROUP_COUNT,
365
+ value=str(len(models)),
366
+ description="Number of models in the group.",
367
+ category="Other",
368
+ )
369
+
370
+ # Combine tags. The `Tags.AQUA_TAG` has been excluded, because we don't want to show
371
+ # the models created for multi-model purpose in the AQUA models list.
372
+ tags = {
373
+ # Tags.AQUA_TAG: "active",
374
+ Tags.MULTIMODEL_TYPE_TAG: "true",
375
+ **(freeform_tags or {}),
376
+ }
377
+
378
+ # Create multi-model group
379
+ custom_model = (
380
+ DataScienceModel()
381
+ .with_compartment_id(compartment_id)
382
+ .with_project_id(project_id)
383
+ .with_display_name(model_group_display_name)
384
+ .with_description(model_group_description)
385
+ .with_freeform_tags(**tags)
386
+ .with_defined_tags(**(defined_tags or {}))
387
+ .with_custom_metadata_list(model_custom_metadata)
388
+ )
389
+
390
+ # Attach artifacts
391
+ for artifact in artifact_list:
392
+ custom_model.add_artifact(uri=artifact)
393
+
394
+ # Finalize creation
395
+ custom_model.create(model_by_reference=True)
396
+
397
+ logger.info(
398
+ f"Aqua Model '{custom_model.id}' created with models: {', '.join(display_name_list)}."
399
+ )
400
+
401
+ # Create custom metadata for multi model metadata
402
+ custom_model.create_custom_metadata_artifact(
403
+ metadata_key_name=ModelCustomMetadataFields.MULTIMODEL_METADATA,
404
+ artifact_path_or_content=json.dumps(
405
+ [model.model_dump() for model in models]
406
+ ).encode(),
407
+ path_type=MetadataArtifactPathType.CONTENT,
408
+ )
409
+
410
+ logger.debug(
411
+ f"Multi model metadata uploaded for Aqua model: {custom_model.id}."
412
+ )
413
+
414
+ # Track telemetry event
415
+ self.telemetry.record_event_async(
416
+ category="aqua/multimodel",
417
+ action="create",
418
+ detail=combined_models,
419
+ )
420
+
421
+ return custom_model
422
+
214
423
  @telemetry(entry_point="plugin=model&action=get", name="aqua")
215
424
  def get(self, model_id: str, load_model_card: Optional[bool] = True) -> "AquaModel":
216
425
  """Gets the information of an Aqua model.
@@ -1448,8 +1657,9 @@ class AquaModelApp(AquaApp):
1448
1657
  self, import_model_details: ImportModelDetails = None, **kwargs
1449
1658
  ) -> AquaModel:
1450
1659
  """Loads the model from object storage and registers as Model in Data Science Model catalog
1451
- The inference container and finetuning container could be of type Service Manged Container(SMC) or custom.
1452
- If it is custom, full container URI is expected. If it of type SMC, only the container family name is expected.
1660
+ The inference container and finetuning container could be of type Service Managed Container(SMC) or custom.
1661
+ If it is custom, full container URI is expected. If it of type SMC, only the container family name is expected.\n
1662
+ For detailed information about CLI flags see: https://github.com/oracle-samples/oci-data-science-ai-samples/blob/main/ai-quick-actions/cli-tips.md#register-model
1453
1663
 
1454
1664
  Args:
1455
1665
  import_model_details (ImportModelDetails): Model details for importing the model.
@@ -1609,6 +1819,8 @@ class AquaModelApp(AquaApp):
1609
1819
  filter_tag = Tags.AQUA_FINE_TUNED_MODEL_TAG
1610
1820
  elif model_type == ModelType.BASE:
1611
1821
  filter_tag = Tags.BASE_MODEL_CUSTOM
1822
+ # elif model_type == ModelType.MULTIMODEL:
1823
+ # filter_tag = Tags.MULTIMODEL_TYPE_TAG
1612
1824
  else:
1613
1825
  raise AquaValueError(
1614
1826
  f"Model of type {model_type} is unknown. The values should be in {ModelType.values()}"