oracle-ads 2.12.8__py3-none-any.whl → 2.12.10__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.
Files changed (82) hide show
  1. ads/aqua/__init__.py +4 -3
  2. ads/aqua/app.py +40 -18
  3. ads/aqua/client/__init__.py +3 -0
  4. ads/aqua/client/client.py +799 -0
  5. ads/aqua/common/enums.py +3 -0
  6. ads/aqua/common/utils.py +62 -2
  7. ads/aqua/data.py +2 -19
  8. ads/aqua/evaluation/entities.py +6 -0
  9. ads/aqua/evaluation/evaluation.py +45 -15
  10. ads/aqua/extension/aqua_ws_msg_handler.py +14 -7
  11. ads/aqua/extension/base_handler.py +12 -9
  12. ads/aqua/extension/deployment_handler.py +8 -4
  13. ads/aqua/extension/finetune_handler.py +8 -14
  14. ads/aqua/extension/model_handler.py +30 -6
  15. ads/aqua/extension/ui_handler.py +13 -1
  16. ads/aqua/finetuning/constants.py +5 -2
  17. ads/aqua/finetuning/entities.py +73 -17
  18. ads/aqua/finetuning/finetuning.py +110 -82
  19. ads/aqua/model/entities.py +5 -1
  20. ads/aqua/model/model.py +230 -104
  21. ads/aqua/modeldeployment/deployment.py +35 -11
  22. ads/aqua/modeldeployment/entities.py +7 -4
  23. ads/aqua/ui.py +24 -2
  24. ads/cli.py +16 -8
  25. ads/common/auth.py +9 -9
  26. ads/llm/autogen/__init__.py +2 -0
  27. ads/llm/autogen/constants.py +15 -0
  28. ads/llm/autogen/reports/__init__.py +2 -0
  29. ads/llm/autogen/reports/base.py +67 -0
  30. ads/llm/autogen/reports/data.py +103 -0
  31. ads/llm/autogen/reports/session.py +526 -0
  32. ads/llm/autogen/reports/templates/chat_box.html +13 -0
  33. ads/llm/autogen/reports/templates/chat_box_lt.html +5 -0
  34. ads/llm/autogen/reports/templates/chat_box_rt.html +6 -0
  35. ads/llm/autogen/reports/utils.py +56 -0
  36. ads/llm/autogen/v02/__init__.py +4 -0
  37. ads/llm/autogen/{client_v02.py → v02/client.py} +23 -10
  38. ads/llm/autogen/v02/log_handlers/__init__.py +2 -0
  39. ads/llm/autogen/v02/log_handlers/oci_file_handler.py +83 -0
  40. ads/llm/autogen/v02/loggers/__init__.py +6 -0
  41. ads/llm/autogen/v02/loggers/metric_logger.py +320 -0
  42. ads/llm/autogen/v02/loggers/session_logger.py +580 -0
  43. ads/llm/autogen/v02/loggers/utils.py +86 -0
  44. ads/llm/autogen/v02/runtime_logging.py +163 -0
  45. ads/llm/guardrails/base.py +6 -5
  46. ads/llm/langchain/plugins/chat_models/oci_data_science.py +46 -20
  47. ads/llm/langchain/plugins/llms/oci_data_science_model_deployment_endpoint.py +38 -11
  48. ads/model/__init__.py +11 -13
  49. ads/model/artifact.py +47 -8
  50. ads/model/extractor/embedding_onnx_extractor.py +80 -0
  51. ads/model/framework/embedding_onnx_model.py +438 -0
  52. ads/model/generic_model.py +26 -24
  53. ads/model/model_metadata.py +8 -7
  54. ads/opctl/config/merger.py +13 -14
  55. ads/opctl/operator/common/operator_config.py +4 -4
  56. ads/opctl/operator/lowcode/common/transformations.py +50 -8
  57. ads/opctl/operator/lowcode/common/utils.py +22 -6
  58. ads/opctl/operator/lowcode/forecast/__main__.py +10 -0
  59. ads/opctl/operator/lowcode/forecast/const.py +3 -0
  60. ads/opctl/operator/lowcode/forecast/model/arima.py +19 -13
  61. ads/opctl/operator/lowcode/forecast/model/automlx.py +129 -36
  62. ads/opctl/operator/lowcode/forecast/model/autots.py +1 -0
  63. ads/opctl/operator/lowcode/forecast/model/base_model.py +58 -17
  64. ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py +1 -1
  65. ads/opctl/operator/lowcode/forecast/model/neuralprophet.py +10 -3
  66. ads/opctl/operator/lowcode/forecast/model/prophet.py +25 -18
  67. ads/opctl/operator/lowcode/forecast/model_evaluator.py +3 -2
  68. ads/opctl/operator/lowcode/forecast/operator_config.py +31 -0
  69. ads/opctl/operator/lowcode/forecast/schema.yaml +76 -0
  70. ads/opctl/operator/lowcode/forecast/utils.py +8 -6
  71. ads/opctl/operator/lowcode/forecast/whatifserve/__init__.py +7 -0
  72. ads/opctl/operator/lowcode/forecast/whatifserve/deployment_manager.py +233 -0
  73. ads/opctl/operator/lowcode/forecast/whatifserve/score.py +238 -0
  74. ads/telemetry/base.py +18 -11
  75. ads/telemetry/client.py +33 -13
  76. ads/templates/schemas/openapi.json +1740 -0
  77. ads/templates/score_embedding_onnx.jinja2 +202 -0
  78. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10.dist-info}/METADATA +11 -10
  79. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10.dist-info}/RECORD +82 -56
  80. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10.dist-info}/LICENSE.txt +0 -0
  81. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10.dist-info}/WHEEL +0 -0
  82. {oracle_ads-2.12.8.dist-info → oracle_ads-2.12.10.dist-info}/entry_points.txt +0 -0
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python
2
- # Copyright (c) 2024 Oracle and/or its affiliates.
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
4
 
5
5
  from typing import Optional
@@ -8,6 +8,9 @@ from urllib.parse import urlparse
8
8
  from tornado.web import HTTPError
9
9
 
10
10
  from ads.aqua.common.decorator import handle_exceptions
11
+ from ads.aqua.common.enums import (
12
+ CustomInferenceContainerTypeFamily,
13
+ )
11
14
  from ads.aqua.common.errors import AquaRuntimeError, AquaValueError
12
15
  from ads.aqua.common.utils import (
13
16
  get_hf_model_info,
@@ -96,7 +99,7 @@ class AquaModelHandler(AquaAPIhandler):
96
99
  )
97
100
 
98
101
  @handle_exceptions
99
- def post(self, *args, **kwargs):
102
+ def post(self, *args, **kwargs): # noqa: ARG002
100
103
  """
101
104
  Handles post request for the registering any Aqua model.
102
105
  Raises
@@ -128,15 +131,27 @@ class AquaModelHandler(AquaAPIhandler):
128
131
  download_from_hf = (
129
132
  str(input_data.get("download_from_hf", "false")).lower() == "true"
130
133
  )
134
+ local_dir = input_data.get("local_dir")
135
+ cleanup_model_cache = (
136
+ str(input_data.get("cleanup_model_cache", "false")).lower() == "true"
137
+ )
131
138
  inference_container_uri = input_data.get("inference_container_uri")
132
139
  allow_patterns = input_data.get("allow_patterns")
133
140
  ignore_patterns = input_data.get("ignore_patterns")
141
+ freeform_tags = input_data.get("freeform_tags")
142
+ defined_tags = input_data.get("defined_tags")
143
+ ignore_model_artifact_check = (
144
+ str(input_data.get("ignore_model_artifact_check", "false")).lower()
145
+ == "true"
146
+ )
134
147
 
135
148
  return self.finish(
136
149
  AquaModelApp().register(
137
150
  model=model,
138
151
  os_path=os_path,
139
152
  download_from_hf=download_from_hf,
153
+ local_dir=local_dir,
154
+ cleanup_model_cache=cleanup_model_cache,
140
155
  inference_container=inference_container,
141
156
  finetuning_container=finetuning_container,
142
157
  compartment_id=compartment_id,
@@ -145,6 +160,9 @@ class AquaModelHandler(AquaAPIhandler):
145
160
  inference_container_uri=inference_container_uri,
146
161
  allow_patterns=allow_patterns,
147
162
  ignore_patterns=ignore_patterns,
163
+ freeform_tags=freeform_tags,
164
+ defined_tags=defined_tags,
165
+ ignore_model_artifact_check=ignore_model_artifact_check,
148
166
  )
149
167
  )
150
168
 
@@ -159,7 +177,9 @@ class AquaModelHandler(AquaAPIhandler):
159
177
  raise HTTPError(400, Errors.NO_INPUT_DATA)
160
178
 
161
179
  inference_container = input_data.get("inference_container")
180
+ inference_container_uri = input_data.get("inference_container_uri")
162
181
  inference_containers = AquaModelApp.list_valid_inference_containers()
182
+ inference_containers.extend(CustomInferenceContainerTypeFamily.values())
163
183
  if (
164
184
  inference_container is not None
165
185
  and inference_container not in inference_containers
@@ -170,10 +190,14 @@ class AquaModelHandler(AquaAPIhandler):
170
190
 
171
191
  enable_finetuning = input_data.get("enable_finetuning")
172
192
  task = input_data.get("task")
173
- app=AquaModelApp()
193
+ app = AquaModelApp()
174
194
  self.finish(
175
195
  app.edit_registered_model(
176
- id, inference_container, enable_finetuning, task
196
+ id,
197
+ inference_container,
198
+ inference_container_uri,
199
+ enable_finetuning,
200
+ task,
177
201
  )
178
202
  )
179
203
  app.clear_model_details_cache(model_id=id)
@@ -218,7 +242,7 @@ class AquaHuggingFaceHandler(AquaAPIhandler):
218
242
  return None
219
243
 
220
244
  @handle_exceptions
221
- def get(self, *args, **kwargs):
245
+ def get(self, *args, **kwargs): # noqa: ARG002
222
246
  """
223
247
  Finds a list of matching models from hugging face based on query string provided from users.
224
248
 
@@ -239,7 +263,7 @@ class AquaHuggingFaceHandler(AquaAPIhandler):
239
263
  return self.finish({"models": models})
240
264
 
241
265
  @handle_exceptions
242
- def post(self, *args, **kwargs):
266
+ def post(self, *args, **kwargs): # noqa: ARG002
243
267
  """Handles post request for the HF Models APIs
244
268
 
245
269
  Raises
@@ -68,6 +68,8 @@ class AquaUIHandler(AquaAPIhandler):
68
68
  return self.list_buckets()
69
69
  elif paths.startswith("aqua/job/shapes"):
70
70
  return self.list_job_shapes()
71
+ elif paths.startswith("aqua/modeldeployment/shapes"):
72
+ return self.list_model_deployment_shapes()
71
73
  elif paths.startswith("aqua/vcn"):
72
74
  return self.list_vcn()
73
75
  elif paths.startswith("aqua/subnets"):
@@ -160,6 +162,15 @@ class AquaUIHandler(AquaAPIhandler):
160
162
  AquaUIApp().list_job_shapes(compartment_id=compartment_id, **kwargs)
161
163
  )
162
164
 
165
+ def list_model_deployment_shapes(self, **kwargs):
166
+ """Lists model deployment shapes available in the specified compartment."""
167
+ compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID)
168
+ return self.finish(
169
+ AquaUIApp().list_model_deployment_shapes(
170
+ compartment_id=compartment_id, **kwargs
171
+ )
172
+ )
173
+
163
174
  def list_vcn(self, **kwargs):
164
175
  """Lists the virtual cloud networks (VCNs) in the specified compartment."""
165
176
  compartment_id = self.get_argument("compartment_id", default=COMPARTMENT_OCID)
@@ -255,8 +266,9 @@ class AquaCLIHandler(AquaAPIhandler):
255
266
  __handlers__ = [
256
267
  ("logging/?([^/]*)", AquaUIHandler),
257
268
  ("compartments/?([^/]*)", AquaUIHandler),
258
- # TODO: change url to evaluation/experiements/?([^/]*)
269
+ # TODO: change url to evaluation/experiments/?([^/]*)
259
270
  ("experiment/?([^/]*)", AquaUIHandler),
271
+ ("modeldeployment/?([^/]*)", AquaUIHandler),
260
272
  ("versionsets/?([^/]*)", AquaUIHandler),
261
273
  ("buckets/?([^/]*)", AquaUIHandler),
262
274
  ("job/shapes/?([^/]*)", AquaUIHandler),
@@ -1,6 +1,5 @@
1
1
  #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
- # Copyright (c) 2024 Oracle and/or its affiliates.
2
+ # Copyright (c) 2024, 2025 Oracle and/or its affiliates.
4
3
  # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
4
 
6
5
  from ads.common.extended_enum import ExtendedEnumMeta
@@ -17,4 +16,8 @@ class FineTuneCustomMetadata(str, metaclass=ExtendedEnumMeta):
17
16
  SERVICE_MODEL_FINE_TUNE_CONTAINER = "finetune-container"
18
17
 
19
18
 
19
+ class FineTuningRestrictedParams(str, metaclass=ExtendedEnumMeta):
20
+ OPTIMIZER = "optimizer"
21
+
22
+
20
23
  ENV_AQUA_FINE_TUNING_CONTAINER = "AQUA_FINE_TUNING_CONTAINER"
@@ -1,18 +1,24 @@
1
1
  #!/usr/bin/env python
2
- # Copyright (c) 2024 Oracle and/or its affiliates.
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
- from dataclasses import dataclass, field
5
- from typing import List, Optional
6
4
 
7
- from ads.aqua.data import AquaJobSummary
8
- from ads.common.serializer import DataClassSerializable
5
+ import json
6
+ from typing import List, Literal, Optional, Union
9
7
 
8
+ from pydantic import Field, model_validator
10
9
 
11
- @dataclass(repr=False)
12
- class AquaFineTuningParams(DataClassSerializable):
13
- epochs: int
10
+ from ads.aqua.common.errors import AquaValueError
11
+ from ads.aqua.config.utils.serializer import Serializable
12
+ from ads.aqua.data import AquaResourceIdentifier
13
+ from ads.aqua.finetuning.constants import FineTuningRestrictedParams
14
+
15
+
16
+ class AquaFineTuningParams(Serializable):
17
+ """Class for maintaining aqua fine-tuning model parameters"""
18
+
19
+ epochs: Optional[int] = None
14
20
  learning_rate: Optional[float] = None
15
- sample_packing: Optional[bool] = "auto"
21
+ sample_packing: Union[bool, None, Literal["auto"]] = "auto"
16
22
  batch_size: Optional[int] = (
17
23
  None # make it batch_size for user, but internally this is micro_batch_size
18
24
  )
@@ -22,21 +28,59 @@ class AquaFineTuningParams(DataClassSerializable):
22
28
  lora_alpha: Optional[int] = None
23
29
  lora_dropout: Optional[float] = None
24
30
  lora_target_linear: Optional[bool] = None
25
- lora_target_modules: Optional[List] = None
31
+ lora_target_modules: Optional[List[str]] = None
26
32
  early_stopping_patience: Optional[int] = None
27
33
  early_stopping_threshold: Optional[float] = None
28
34
 
35
+ class Config:
36
+ extra = "allow"
37
+
38
+ def to_dict(self) -> dict:
39
+ return json.loads(super().to_json(exclude_none=True))
40
+
41
+ @model_validator(mode="before")
42
+ @classmethod
43
+ def validate_restricted_fields(cls, data: dict):
44
+ # we may want to skip validation if loading data from config files instead of user entered parameters
45
+ validate = data.pop("_validate", True)
46
+ if not (validate and isinstance(data, dict)):
47
+ return data
48
+ restricted_params = [
49
+ param for param in data if param in FineTuningRestrictedParams.values()
50
+ ]
51
+ if restricted_params:
52
+ raise AquaValueError(
53
+ f"Found restricted parameter name: {restricted_params}"
54
+ )
55
+ return data
29
56
 
30
- @dataclass(repr=False)
31
- class AquaFineTuningSummary(AquaJobSummary, DataClassSerializable):
32
- parameters: AquaFineTuningParams = field(default_factory=AquaFineTuningParams)
33
57
 
58
+ class AquaFineTuningSummary(Serializable):
59
+ """Represents a summary of Aqua Finetuning job."""
34
60
 
35
- @dataclass(repr=False)
36
- class CreateFineTuningDetails(DataClassSerializable):
37
- """Dataclass to create aqua model fine tuning.
61
+ id: str
62
+ name: str
63
+ console_url: str
64
+ lifecycle_state: str
65
+ lifecycle_details: str
66
+ time_created: str
67
+ tags: dict
68
+ experiment: AquaResourceIdentifier = Field(default_factory=AquaResourceIdentifier)
69
+ source: AquaResourceIdentifier = Field(default_factory=AquaResourceIdentifier)
70
+ job: AquaResourceIdentifier = Field(default_factory=AquaResourceIdentifier)
71
+ parameters: AquaFineTuningParams = Field(default_factory=AquaFineTuningParams)
38
72
 
39
- Fields
73
+ class Config:
74
+ extra = "ignore"
75
+
76
+ def to_dict(self) -> dict:
77
+ return json.loads(super().to_json(exclude_none=True))
78
+
79
+
80
+ class CreateFineTuningDetails(Serializable):
81
+ """Class to create aqua model fine-tuning instance.
82
+
83
+ Properties
40
84
  ------
41
85
  ft_source_id: str
42
86
  The fine tuning source id. Must be model ocid.
@@ -78,8 +122,14 @@ class CreateFineTuningDetails(DataClassSerializable):
78
122
  The log group id for fine tuning job infrastructure.
79
123
  log_id: (str, optional). Defaults to `None`.
80
124
  The log id for fine tuning job infrastructure.
125
+ watch_logs: (bool, optional). Defaults to `False`.
126
+ The flag to watch the job run logs when a fine-tuning job is created.
81
127
  force_overwrite: (bool, optional). Defaults to `False`.
82
128
  Whether to force overwrite the existing file in object storage.
129
+ freeform_tags: (dict, optional)
130
+ Freeform tags for the fine-tuning model
131
+ defined_tags: (dict, optional)
132
+ Defined tags for the fine-tuning model
83
133
  """
84
134
 
85
135
  ft_source_id: str
@@ -100,4 +150,10 @@ class CreateFineTuningDetails(DataClassSerializable):
100
150
  subnet_id: Optional[str] = None
101
151
  log_id: Optional[str] = None
102
152
  log_group_id: Optional[str] = None
153
+ watch_logs: Optional[bool] = False
103
154
  force_overwrite: Optional[bool] = False
155
+ freeform_tags: Optional[dict] = None
156
+ defined_tags: Optional[dict] = None
157
+
158
+ class Config:
159
+ extra = "ignore"
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env python
2
- # Copyright (c) 2024 Oracle and/or its affiliates.
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
4
 
5
5
  import json
6
6
  import os
7
- from dataclasses import MISSING, asdict, fields
7
+ import time
8
+ import traceback
8
9
  from typing import Dict
9
10
 
10
11
  from oci.data_science.models import (
@@ -12,12 +13,14 @@ from oci.data_science.models import (
12
13
  UpdateModelDetails,
13
14
  UpdateModelProvenanceDetails,
14
15
  )
16
+ from pydantic import ValidationError
15
17
 
16
18
  from ads.aqua import logger
17
19
  from ads.aqua.app import AquaApp
18
20
  from ads.aqua.common.enums import Resource, Tags
19
21
  from ads.aqua.common.errors import AquaFileExistsError, AquaValueError
20
22
  from ads.aqua.common.utils import (
23
+ build_pydantic_error_message,
21
24
  get_container_image,
22
25
  upload_local_to_os,
23
26
  )
@@ -35,7 +38,11 @@ from ads.aqua.finetuning.constants import (
35
38
  ENV_AQUA_FINE_TUNING_CONTAINER,
36
39
  FineTuneCustomMetadata,
37
40
  )
38
- from ads.aqua.finetuning.entities import *
41
+ from ads.aqua.finetuning.entities import (
42
+ AquaFineTuningParams,
43
+ AquaFineTuningSummary,
44
+ CreateFineTuningDetails,
45
+ )
39
46
  from ads.common.auth import default_signer
40
47
  from ads.common.object_storage_details import ObjectStorageDetails
41
48
  from ads.common.utils import get_console_link
@@ -100,23 +107,11 @@ class AquaFineTuningApp(AquaApp):
100
107
  if not create_fine_tuning_details:
101
108
  try:
102
109
  create_fine_tuning_details = CreateFineTuningDetails(**kwargs)
103
- except:
104
- allowed_create_fine_tuning_details = ", ".join(
105
- field.name for field in fields(CreateFineTuningDetails)
106
- ).rstrip()
110
+ except ValidationError as ex:
111
+ custom_errors = build_pydantic_error_message(ex)
107
112
  raise AquaValueError(
108
- "Invalid create fine tuning parameters. Allowable parameters are: "
109
- f"{allowed_create_fine_tuning_details}."
110
- )
111
-
112
- source = self.get_source(create_fine_tuning_details.ft_source_id)
113
-
114
- # todo: revisit validation for fine tuned models
115
- # if source.compartment_id != ODSC_MODEL_COMPARTMENT_OCID:
116
- # raise AquaValueError(
117
- # f"Fine tuning is only supported for Aqua service models in {ODSC_MODEL_COMPARTMENT_OCID}. "
118
- # "Use a valid Aqua service model id instead."
119
- # )
113
+ f"Invalid parameters for creating a fine-tuned model. Error details: {custom_errors}."
114
+ ) from ex
120
115
 
121
116
  target_compartment = (
122
117
  create_fine_tuning_details.compartment_id or COMPARTMENT_OCID
@@ -148,29 +143,27 @@ class AquaFineTuningApp(AquaApp):
148
143
  "Specify the subnet id via API or environment variable AQUA_JOB_SUBNET_ID."
149
144
  )
150
145
 
151
- if create_fine_tuning_details.replica > DEFAULT_FT_REPLICA:
152
- if not (
153
- create_fine_tuning_details.log_id
154
- and create_fine_tuning_details.log_group_id
155
- ):
156
- raise AquaValueError(
157
- f"Logging is required for fine tuning if replica is larger than {DEFAULT_FT_REPLICA}."
158
- )
159
-
160
- ft_parameters = None
161
- try:
162
- ft_parameters = AquaFineTuningParams(
163
- **create_fine_tuning_details.ft_parameters,
146
+ if create_fine_tuning_details.replica > DEFAULT_FT_REPLICA and not (
147
+ create_fine_tuning_details.log_id
148
+ and create_fine_tuning_details.log_group_id
149
+ ):
150
+ raise AquaValueError(
151
+ f"Logging is required for fine tuning if replica is larger than {DEFAULT_FT_REPLICA}."
164
152
  )
165
- except:
166
- allowed_fine_tuning_parameters = ", ".join(
167
- field.name for field in fields(AquaFineTuningParams)
168
- ).rstrip()
153
+
154
+ if create_fine_tuning_details.watch_logs and not (
155
+ create_fine_tuning_details.log_id
156
+ and create_fine_tuning_details.log_group_id
157
+ ):
169
158
  raise AquaValueError(
170
- "Invalid fine tuning parameters. Fine tuning parameters should "
171
- f"be a dictionary with keys: {allowed_fine_tuning_parameters}."
159
+ "Logging is required for fine tuning if watch_logs is set to True. "
160
+ "Please provide log_id and log_group_id with the request parameters."
172
161
  )
173
162
 
163
+ ft_parameters = self._get_finetuning_params(
164
+ create_fine_tuning_details.ft_parameters
165
+ )
166
+
174
167
  experiment_model_version_set_id = create_fine_tuning_details.experiment_id
175
168
  experiment_model_version_set_name = create_fine_tuning_details.experiment_name
176
169
 
@@ -197,11 +190,11 @@ class AquaFineTuningApp(AquaApp):
197
190
  auth=default_signer(),
198
191
  force_overwrite=create_fine_tuning_details.force_overwrite,
199
192
  )
200
- except FileExistsError:
193
+ except FileExistsError as fe:
201
194
  raise AquaFileExistsError(
202
195
  f"Dataset {dataset_file} already exists in {create_fine_tuning_details.report_path}. "
203
196
  "Please use a new dataset file name, report path or set `force_overwrite` as True."
204
- )
197
+ ) from fe
205
198
  logger.debug(
206
199
  f"Uploaded local file {ft_dataset_path} to object storage {dst_uri}."
207
200
  )
@@ -222,8 +215,12 @@ class AquaFineTuningApp(AquaApp):
222
215
  description=create_fine_tuning_details.experiment_description,
223
216
  compartment_id=target_compartment,
224
217
  project_id=target_project,
218
+ freeform_tags=create_fine_tuning_details.freeform_tags,
219
+ defined_tags=create_fine_tuning_details.defined_tags,
225
220
  )
226
221
 
222
+ source = self.get_source(create_fine_tuning_details.ft_source_id)
223
+
227
224
  ft_model_custom_metadata = ModelCustomMetadata()
228
225
  ft_model_custom_metadata.add(
229
226
  key=FineTuneCustomMetadata.FINE_TUNE_SOURCE,
@@ -272,6 +269,7 @@ class AquaFineTuningApp(AquaApp):
272
269
  ft_job_freeform_tags = {
273
270
  Tags.AQUA_TAG: UNKNOWN,
274
271
  Tags.AQUA_FINE_TUNED_MODEL_TAG: f"{source.id}#{source.display_name}",
272
+ **(create_fine_tuning_details.freeform_tags or {}),
275
273
  }
276
274
 
277
275
  ft_job = Job(name=ft_model.display_name).with_infrastructure(
@@ -286,6 +284,7 @@ class AquaFineTuningApp(AquaApp):
286
284
  or DEFAULT_FT_BLOCK_STORAGE_SIZE
287
285
  )
288
286
  .with_freeform_tag(**ft_job_freeform_tags)
287
+ .with_defined_tag(**(create_fine_tuning_details.defined_tags or {}))
289
288
  )
290
289
 
291
290
  if not subnet_id:
@@ -353,6 +352,7 @@ class AquaFineTuningApp(AquaApp):
353
352
  ft_job_run = ft_job.run(
354
353
  name=ft_model.display_name,
355
354
  freeform_tags=ft_job_freeform_tags,
355
+ defined_tags=create_fine_tuning_details.defined_tags or {},
356
356
  wait=False,
357
357
  )
358
358
  logger.debug(
@@ -372,24 +372,30 @@ class AquaFineTuningApp(AquaApp):
372
372
  for metadata in ft_model_custom_metadata.to_dict()["data"]
373
373
  ]
374
374
 
375
- source_freeform_tags = source.freeform_tags or {}
376
- source_freeform_tags.pop(Tags.LICENSE, None)
377
- source_freeform_tags.update({Tags.READY_TO_FINE_TUNE: "false"})
378
- source_freeform_tags.update({Tags.AQUA_TAG: UNKNOWN})
379
- source_freeform_tags.pop(Tags.BASE_MODEL_CUSTOM, None)
375
+ model_freeform_tags = source.freeform_tags or {}
376
+ model_freeform_tags.pop(Tags.LICENSE, None)
377
+ model_freeform_tags.pop(Tags.BASE_MODEL_CUSTOM, None)
378
+
379
+ model_freeform_tags = {
380
+ **model_freeform_tags,
381
+ Tags.READY_TO_FINE_TUNE: "false",
382
+ Tags.AQUA_TAG: UNKNOWN,
383
+ Tags.AQUA_FINE_TUNED_MODEL_TAG: f"{source.id}#{source.display_name}",
384
+ **(create_fine_tuning_details.freeform_tags or {}),
385
+ }
386
+ model_defined_tags = create_fine_tuning_details.defined_tags or {}
380
387
 
381
388
  self.update_model(
382
389
  model_id=ft_model.id,
383
390
  update_model_details=UpdateModelDetails(
384
391
  custom_metadata_list=updated_custom_metadata_list,
385
- freeform_tags={
386
- Tags.AQUA_FINE_TUNED_MODEL_TAG: (
387
- f"{source.id}#{source.display_name}"
388
- ),
389
- **source_freeform_tags,
390
- },
392
+ freeform_tags=model_freeform_tags,
393
+ defined_tags=model_defined_tags,
391
394
  ),
392
395
  )
396
+ logger.debug(
397
+ f"Successfully updated model custom metadata list and freeform tags for the model {ft_model.id}."
398
+ )
393
399
 
394
400
  self.update_model_provenance(
395
401
  model_id=ft_model.id,
@@ -397,6 +403,9 @@ class AquaFineTuningApp(AquaApp):
397
403
  training_id=ft_job_run.id
398
404
  ),
399
405
  )
406
+ logger.debug(
407
+ f"Successfully updated model provenance for the model {ft_model.id}."
408
+ )
400
409
 
401
410
  # tracks the shape and replica used for fine-tuning the service models
402
411
  telemetry_kwargs = (
@@ -424,6 +433,20 @@ class AquaFineTuningApp(AquaApp):
424
433
  value=source.display_name,
425
434
  )
426
435
 
436
+ if create_fine_tuning_details.watch_logs:
437
+ logger.info(
438
+ f"Watching fine-tuning job run logs for {ft_job_run.id}. Press Ctrl+C to stop watching logs.\n"
439
+ )
440
+ try:
441
+ ft_job_run.watch()
442
+ except KeyboardInterrupt:
443
+ logger.info(f"\nStopped watching logs for {ft_job_run.id}.\n")
444
+ time.sleep(1)
445
+ except Exception:
446
+ logger.debug(
447
+ f"Something unexpected occurred while watching logs.\n{traceback.format_exc()}"
448
+ )
449
+
427
450
  return AquaFineTuningSummary(
428
451
  id=ft_model.id,
429
452
  name=ft_model.display_name,
@@ -462,17 +485,15 @@ class AquaFineTuningApp(AquaApp):
462
485
  region=self.region,
463
486
  ),
464
487
  ),
465
- tags=dict(
466
- aqua_finetuning=Tags.AQUA_FINE_TUNING,
467
- finetuning_job_id=ft_job.id,
468
- finetuning_source=source.id,
469
- finetuning_experiment_id=experiment_model_version_set_id,
470
- ),
471
- parameters={
472
- key: value
473
- for key, value in asdict(ft_parameters).items()
474
- if value is not None
488
+ tags={
489
+ "aqua_finetuning": Tags.AQUA_FINE_TUNING,
490
+ "finetuning_job_id": ft_job.id,
491
+ "finetuning_source": source.id,
492
+ "finetuning_experiment_id": experiment_model_version_set_id,
493
+ **model_freeform_tags,
494
+ **model_defined_tags,
475
495
  },
496
+ parameters=ft_parameters,
476
497
  )
477
498
 
478
499
  def _build_fine_tuning_runtime(
@@ -535,7 +556,7 @@ class AquaFineTuningApp(AquaApp):
535
556
  ) -> str:
536
557
  """Builds the oci launch cmd for fine tuning container runtime."""
537
558
  oci_launch_cmd = f"--training_data {dataset_path} --output_dir {report_path} --val_set_size {val_set_size} "
538
- for key, value in asdict(parameters).items():
559
+ for key, value in parameters.to_dict().items():
539
560
  if value is not None:
540
561
  if key == "batch_size":
541
562
  oci_launch_cmd += f"--micro_{key} {value} "
@@ -574,7 +595,7 @@ class AquaFineTuningApp(AquaApp):
574
595
  config = self.get_config(model_id, AQUA_MODEL_FINETUNING_CONFIG)
575
596
  if not config:
576
597
  logger.debug(
577
- f"Fine-tuning config for custom model: {model_id} is not available."
598
+ f"Fine-tuning config for custom model: {model_id} is not available. Use defaults."
578
599
  )
579
600
  return config
580
601
 
@@ -600,15 +621,36 @@ class AquaFineTuningApp(AquaApp):
600
621
  default_params = {"params": {}}
601
622
  finetuning_config = self.get_finetuning_config(model_id)
602
623
  config_parameters = finetuning_config.get("configuration", UNKNOWN_DICT)
603
- dataclass_fields = {field.name for field in fields(AquaFineTuningParams)}
624
+ dataclass_fields = self._get_finetuning_params(
625
+ config_parameters, validate=False
626
+ ).to_dict()
604
627
  for name, value in config_parameters.items():
605
- if name == "micro_batch_size":
606
- name = "batch_size"
607
628
  if name in dataclass_fields:
629
+ if name == "micro_batch_size":
630
+ name = "batch_size"
608
631
  default_params["params"][name] = value
609
632
 
610
633
  return default_params
611
634
 
635
+ @staticmethod
636
+ def _get_finetuning_params(
637
+ params: Dict = None, validate: bool = True
638
+ ) -> AquaFineTuningParams:
639
+ """
640
+ Get and validate the fine-tuning params, and return an error message if validation fails. In order to skip
641
+ @model_validator decorator's validation, pass validate=False.
642
+ """
643
+ try:
644
+ finetuning_params = AquaFineTuningParams(
645
+ **{**params, **{"_validate": validate}}
646
+ )
647
+ except ValidationError as ex:
648
+ custom_errors = build_pydantic_error_message(ex)
649
+ raise AquaValueError(
650
+ f"Invalid finetuning parameters. Error details: {custom_errors}."
651
+ ) from ex
652
+ return finetuning_params
653
+
612
654
  def validate_finetuning_params(self, params: Dict = None) -> Dict:
613
655
  """Validate if the fine-tuning parameters passed by the user can be overridden. Parameter values are not
614
656
  validated, only param keys are validated.
@@ -622,19 +664,5 @@ class AquaFineTuningApp(AquaApp):
622
664
  -------
623
665
  Return a list of restricted params.
624
666
  """
625
- try:
626
- AquaFineTuningParams(
627
- **params,
628
- )
629
- except Exception as e:
630
- logger.debug(str(e))
631
- allowed_fine_tuning_parameters = ", ".join(
632
- f"{field.name} (required)" if field.default is MISSING else field.name
633
- for field in fields(AquaFineTuningParams)
634
- ).rstrip()
635
- raise AquaValueError(
636
- f"Invalid fine tuning parameters. Allowable parameters are: "
637
- f"{allowed_fine_tuning_parameters}."
638
- )
639
-
640
- return dict(valid=True)
667
+ self._get_finetuning_params(params or {})
668
+ return {"valid": True}
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env python
2
- # Copyright (c) 2024 Oracle and/or its affiliates.
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
4
 
5
5
  """
@@ -283,6 +283,7 @@ class ImportModelDetails(CLIBuilderMixin):
283
283
  os_path: str
284
284
  download_from_hf: Optional[bool] = True
285
285
  local_dir: Optional[str] = None
286
+ cleanup_model_cache: Optional[bool] = False
286
287
  inference_container: Optional[str] = None
287
288
  finetuning_container: Optional[str] = None
288
289
  compartment_id: Optional[str] = None
@@ -291,6 +292,9 @@ class ImportModelDetails(CLIBuilderMixin):
291
292
  inference_container_uri: Optional[str] = None
292
293
  allow_patterns: Optional[List[str]] = None
293
294
  ignore_patterns: Optional[List[str]] = None
295
+ freeform_tags: Optional[dict] = None
296
+ defined_tags: Optional[dict] = None
297
+ ignore_model_artifact_check: Optional[bool] = None
294
298
 
295
299
  def __post_init__(self):
296
300
  self._command = "model register"