oracle-ads 2.12.9__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 (74) hide show
  1. ads/aqua/__init__.py +4 -3
  2. ads/aqua/app.py +28 -16
  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/evaluation.py +20 -12
  9. ads/aqua/extension/aqua_ws_msg_handler.py +14 -7
  10. ads/aqua/extension/base_handler.py +12 -9
  11. ads/aqua/extension/finetune_handler.py +8 -14
  12. ads/aqua/extension/model_handler.py +24 -2
  13. ads/aqua/finetuning/constants.py +5 -2
  14. ads/aqua/finetuning/entities.py +67 -17
  15. ads/aqua/finetuning/finetuning.py +69 -54
  16. ads/aqua/model/entities.py +3 -1
  17. ads/aqua/model/model.py +196 -98
  18. ads/aqua/modeldeployment/deployment.py +22 -10
  19. ads/cli.py +16 -8
  20. ads/common/auth.py +9 -9
  21. ads/llm/autogen/__init__.py +2 -0
  22. ads/llm/autogen/constants.py +15 -0
  23. ads/llm/autogen/reports/__init__.py +2 -0
  24. ads/llm/autogen/reports/base.py +67 -0
  25. ads/llm/autogen/reports/data.py +103 -0
  26. ads/llm/autogen/reports/session.py +526 -0
  27. ads/llm/autogen/reports/templates/chat_box.html +13 -0
  28. ads/llm/autogen/reports/templates/chat_box_lt.html +5 -0
  29. ads/llm/autogen/reports/templates/chat_box_rt.html +6 -0
  30. ads/llm/autogen/reports/utils.py +56 -0
  31. ads/llm/autogen/v02/__init__.py +4 -0
  32. ads/llm/autogen/{client_v02.py → v02/client.py} +23 -10
  33. ads/llm/autogen/v02/log_handlers/__init__.py +2 -0
  34. ads/llm/autogen/v02/log_handlers/oci_file_handler.py +83 -0
  35. ads/llm/autogen/v02/loggers/__init__.py +6 -0
  36. ads/llm/autogen/v02/loggers/metric_logger.py +320 -0
  37. ads/llm/autogen/v02/loggers/session_logger.py +580 -0
  38. ads/llm/autogen/v02/loggers/utils.py +86 -0
  39. ads/llm/autogen/v02/runtime_logging.py +163 -0
  40. ads/llm/langchain/plugins/chat_models/oci_data_science.py +12 -11
  41. ads/model/__init__.py +11 -13
  42. ads/model/artifact.py +47 -8
  43. ads/model/extractor/embedding_onnx_extractor.py +80 -0
  44. ads/model/framework/embedding_onnx_model.py +438 -0
  45. ads/model/generic_model.py +26 -24
  46. ads/model/model_metadata.py +8 -7
  47. ads/opctl/config/merger.py +13 -14
  48. ads/opctl/operator/common/operator_config.py +4 -4
  49. ads/opctl/operator/lowcode/common/transformations.py +50 -8
  50. ads/opctl/operator/lowcode/common/utils.py +22 -6
  51. ads/opctl/operator/lowcode/forecast/__main__.py +10 -0
  52. ads/opctl/operator/lowcode/forecast/const.py +2 -0
  53. ads/opctl/operator/lowcode/forecast/model/arima.py +19 -13
  54. ads/opctl/operator/lowcode/forecast/model/automlx.py +129 -36
  55. ads/opctl/operator/lowcode/forecast/model/autots.py +1 -0
  56. ads/opctl/operator/lowcode/forecast/model/base_model.py +61 -14
  57. ads/opctl/operator/lowcode/forecast/model/forecast_datasets.py +1 -1
  58. ads/opctl/operator/lowcode/forecast/model/neuralprophet.py +10 -3
  59. ads/opctl/operator/lowcode/forecast/model/prophet.py +25 -18
  60. ads/opctl/operator/lowcode/forecast/operator_config.py +31 -0
  61. ads/opctl/operator/lowcode/forecast/schema.yaml +76 -0
  62. ads/opctl/operator/lowcode/forecast/utils.py +4 -3
  63. ads/opctl/operator/lowcode/forecast/whatifserve/__init__.py +7 -0
  64. ads/opctl/operator/lowcode/forecast/whatifserve/deployment_manager.py +233 -0
  65. ads/opctl/operator/lowcode/forecast/whatifserve/score.py +238 -0
  66. ads/telemetry/base.py +18 -11
  67. ads/telemetry/client.py +33 -13
  68. ads/templates/schemas/openapi.json +1740 -0
  69. ads/templates/score_embedding_onnx.jinja2 +202 -0
  70. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/METADATA +9 -8
  71. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/RECORD +74 -48
  72. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/LICENSE.txt +0 -0
  73. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/WHEEL +0 -0
  74. {oracle_ads-2.12.9.dist-info → oracle_ads-2.12.10.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,233 @@
1
+ #!/usr/bin/env python
2
+ import json
3
+ # Copyright (c) 2023, 2024 Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ import os
7
+ import pickle
8
+ import shutil
9
+ import sys
10
+ import tempfile
11
+ import oci
12
+
13
+ import pandas as pd
14
+ import cloudpickle
15
+
16
+ from ads.opctl import logger
17
+ from ads.common.model_export_util import prepare_generic_model
18
+ from ads.opctl.operator.lowcode.common.utils import write_data, write_simple_json
19
+ from ads.opctl.operator.lowcode.common.utils import default_signer
20
+ from ..model.forecast_datasets import AdditionalData
21
+ from ..operator_config import ForecastOperatorSpec
22
+
23
+ from oci.data_science import DataScienceClient, DataScienceClientCompositeOperations
24
+
25
+ from oci.data_science.models import ModelConfigurationDetails, InstanceConfiguration, \
26
+ FixedSizeScalingPolicy, CategoryLogDetails, LogDetails, \
27
+ SingleModelDeploymentConfigurationDetails, CreateModelDeploymentDetails
28
+ from ads.common.object_storage_details import ObjectStorageDetails
29
+
30
+
31
+ class ModelDeploymentManager:
32
+ def __init__(self, spec: ForecastOperatorSpec, additional_data: AdditionalData, previous_model_version=None):
33
+ self.spec = spec
34
+ self.model_name = spec.model
35
+ self.horizon = spec.horizon
36
+ self.additional_data = additional_data.get_dict_by_series()
37
+ self.model_obj = {}
38
+ self.display_name = spec.what_if_analysis.model_display_name
39
+ self.project_id = spec.what_if_analysis.project_id if spec.what_if_analysis.project_id \
40
+ else os.environ.get('PROJECT_OCID')
41
+ self.compartment_id = spec.what_if_analysis.compartment_id if spec.what_if_analysis.compartment_id \
42
+ else os.environ.get('NB_SESSION_COMPARTMENT_OCID')
43
+ if self.project_id is None or self.compartment_id is None:
44
+ raise ValueError("Either project_id or compartment_id cannot be None.")
45
+ self.path_to_artifact = f"{self.spec.output_directory.url}/artifacts/"
46
+ self.pickle_file_path = f"{self.spec.output_directory.url}/model.pkl"
47
+ self.model_version = previous_model_version + 1 if previous_model_version else 1
48
+ self.catalog_id = None
49
+ self.test_mode = os.environ.get("TEST_MODE", False)
50
+ self.deployment_info = {}
51
+
52
+ def _sanity_test(self):
53
+ """
54
+ Function perform sanity test for saved artifact
55
+ """
56
+ org_sys_path = sys.path[:]
57
+ try:
58
+ sys.path.insert(0, f"{self.path_to_artifact}")
59
+ from score import load_model, predict
60
+ _ = load_model()
61
+
62
+ # Write additional data to tmp file and perform sanity check
63
+ with tempfile.NamedTemporaryFile(suffix='.csv') as temp_file:
64
+ one_series = next(iter(self.additional_data))
65
+ sample_prediction_data = self.additional_data[one_series].tail(self.horizon)
66
+ sample_prediction_data[self.spec.target_category_columns[0]] = one_series
67
+ date_col_name = self.spec.datetime_column.name
68
+ date_col_format = self.spec.datetime_column.format
69
+ sample_prediction_data[date_col_name] = sample_prediction_data[date_col_name].dt.strftime(
70
+ date_col_format)
71
+ sample_prediction_data.to_csv(temp_file.name, index=False)
72
+ input_data = {"additional_data": {"url": temp_file.name}}
73
+ prediction_test = predict(input_data, _)
74
+ logger.info(f"prediction test completed with result :{prediction_test}")
75
+ except Exception as e:
76
+ logger.error(f"An error occurred during the sanity test: {e}")
77
+ raise
78
+ finally:
79
+ sys.path = org_sys_path
80
+
81
+ def _copy_score_file(self):
82
+ """
83
+ Copies the score.py to the artifact_path.
84
+ """
85
+ try:
86
+ current_dir = os.path.dirname(os.path.abspath(__file__))
87
+ score_file = os.path.join(current_dir, "score.py")
88
+ destination_file = os.path.join(self.path_to_artifact, os.path.basename(score_file))
89
+ shutil.copy2(score_file, destination_file)
90
+ logger.info(f"score.py copied successfully to {self.path_to_artifact}")
91
+ except Exception as e:
92
+ logger.warn(f"Error copying file: {e}")
93
+ raise e
94
+
95
+ def save_to_catalog(self):
96
+ """Save the model to a model catalog"""
97
+ with open(self.pickle_file_path, 'rb') as file:
98
+ self.model_obj = pickle.load(file)
99
+
100
+ if not os.path.exists(self.path_to_artifact):
101
+ os.mkdir(self.path_to_artifact)
102
+
103
+ artifact_dict = {"spec": self.spec.to_dict(), "models": self.model_obj}
104
+ with open(f"{self.path_to_artifact}/models.pickle", "wb") as f:
105
+ cloudpickle.dump(artifact_dict, f)
106
+ artifact = prepare_generic_model(
107
+ self.path_to_artifact,
108
+ function_artifacts=False,
109
+ force_overwrite=True,
110
+ data_science_env=True)
111
+
112
+ self._copy_score_file()
113
+ self._sanity_test()
114
+
115
+ if isinstance(self.model_obj, dict):
116
+ series = self.model_obj.keys()
117
+ else:
118
+ series = self.additional_data.keys()
119
+ description = f"The object contains {len(series)} {self.model_name} models"
120
+
121
+ if not self.test_mode:
122
+ catalog_entry = artifact.save(
123
+ display_name=self.display_name,
124
+ compartment_id=self.compartment_id,
125
+ project_id=self.project_id,
126
+ description=description)
127
+ self.catalog_id = catalog_entry.id
128
+
129
+ logger.info(f"Saved {self.model_name} version-v{self.model_version} to model catalog"
130
+ f" with model ocid : {self.catalog_id}")
131
+
132
+ self.deployment_info = {"model_ocid": self.catalog_id, "series": list(series)}
133
+
134
+ def create_deployment(self):
135
+ """Create a model deployment serving"""
136
+
137
+ # create new model deployment
138
+ initial_shape = self.spec.what_if_analysis.model_deployment.initial_shape
139
+ name = self.spec.what_if_analysis.model_deployment.display_name
140
+ description = self.spec.what_if_analysis.model_deployment.description
141
+ auto_scaling_config = self.spec.what_if_analysis.model_deployment.auto_scaling
142
+
143
+ # if auto_scaling_config is defined
144
+ if auto_scaling_config:
145
+ scaling_policy = oci.data_science.models.AutoScalingPolicy(
146
+ policy_type="AUTOSCALING",
147
+ auto_scaling_policies=[
148
+ oci.data_science.models.ThresholdBasedAutoScalingPolicyDetails(
149
+ auto_scaling_policy_type="THRESHOLD",
150
+ rules=[
151
+ oci.data_science.models.PredefinedMetricExpressionRule(
152
+ metric_expression_rule_type="PREDEFINED_EXPRESSION",
153
+ metric_type=auto_scaling_config.scaling_metric,
154
+ scale_in_configuration=oci.data_science.models.PredefinedExpressionThresholdScalingConfiguration(
155
+ scaling_configuration_type="THRESHOLD",
156
+ threshold=auto_scaling_config.scale_in_threshold
157
+ ),
158
+ scale_out_configuration=oci.data_science.models.PredefinedExpressionThresholdScalingConfiguration(
159
+ scaling_configuration_type="THRESHOLD",
160
+ threshold=auto_scaling_config.scale_out_threshold
161
+ )
162
+ )],
163
+ maximum_instance_count=auto_scaling_config.maximum_instance,
164
+ minimum_instance_count=auto_scaling_config.minimum_instance,
165
+ initial_instance_count=auto_scaling_config.minimum_instance)],
166
+ cool_down_in_seconds=auto_scaling_config.cool_down_in_seconds,
167
+ is_enabled=True)
168
+ logger.info(f"Using autoscaling {auto_scaling_config.scaling_metric} for creating MD")
169
+ else:
170
+ scaling_policy = FixedSizeScalingPolicy(instance_count=1)
171
+ logger.info("Using fixed size policy for creating MD")
172
+
173
+ model_configuration_details_object = ModelConfigurationDetails(
174
+ model_id=self.catalog_id,
175
+ instance_configuration=InstanceConfiguration(
176
+ instance_shape_name=initial_shape),
177
+ scaling_policy=scaling_policy,
178
+ bandwidth_mbps=20)
179
+
180
+ single_model_config = SingleModelDeploymentConfigurationDetails(
181
+ deployment_type='SINGLE_MODEL',
182
+ model_configuration_details=model_configuration_details_object
183
+ )
184
+
185
+ log_group = self.spec.what_if_analysis.model_deployment.log_group
186
+ log_id = self.spec.what_if_analysis.model_deployment.log_id
187
+
188
+ logs_configuration_details_object = CategoryLogDetails(
189
+ access=LogDetails(log_group_id=log_group,
190
+ log_id=log_id),
191
+ predict=LogDetails(log_group_id=log_group,
192
+ log_id=log_id))
193
+
194
+ model_deploy_configuration = CreateModelDeploymentDetails(
195
+ display_name=name,
196
+ description=description,
197
+ project_id=self.project_id,
198
+ compartment_id=self.compartment_id,
199
+ model_deployment_configuration_details=single_model_config,
200
+ category_log_details=logs_configuration_details_object)
201
+
202
+ if not self.test_mode:
203
+ auth = oci.auth.signers.get_resource_principals_signer()
204
+ data_science = DataScienceClient({}, signer=auth)
205
+ data_science_composite = DataScienceClientCompositeOperations(data_science)
206
+ model_deployment = data_science_composite.create_model_deployment_and_wait_for_state(
207
+ model_deploy_configuration,
208
+ wait_for_states=[
209
+ "SUCCEEDED", "FAILED"])
210
+ self.deployment_info['work_request'] = model_deployment.data.id
211
+ logger.info(f"deployment metadata :{model_deployment.data}")
212
+ md = data_science.get_model_deployment(model_deployment_id=model_deployment.data.resources[0].identifier)
213
+ self.deployment_info['model_deployment_ocid'] = md.data.id
214
+ endpoint_url = md.data.model_deployment_url
215
+ self.deployment_info['model_deployment_endpoint'] = f"{endpoint_url}/predict"
216
+
217
+ def save_deployment_info(self):
218
+ output_dir = self.spec.output_directory.url
219
+ if ObjectStorageDetails.is_oci_path(output_dir):
220
+ storage_options = default_signer()
221
+ else:
222
+ storage_options = {}
223
+ write_data(
224
+ data=pd.DataFrame.from_dict(self.deployment_info),
225
+ filename=os.path.join(output_dir, "deployment_info.json"),
226
+ format="json",
227
+ storage_options=storage_options,
228
+ index=False,
229
+ indent=4,
230
+ orient="records"
231
+ )
232
+ write_simple_json(self.deployment_info, os.path.join(output_dir, "deployment_info.json"))
233
+ logger.info(f"Saved deployment info to {output_dir}")
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env python
2
+
3
+ # Copyright (c) 2023, 2024 Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ import json
7
+ import os
8
+ from joblib import load
9
+ import pandas as pd
10
+ import numpy as np
11
+ from functools import lru_cache
12
+ import logging
13
+ import ads
14
+ from ads.opctl.operator.lowcode.common.utils import load_data
15
+ from ads.opctl.operator.common.operator_config import InputData
16
+ from ads.opctl.operator.lowcode.forecast.const import SupportedModels
17
+
18
+ ads.set_auth("resource_principal")
19
+
20
+ logging.basicConfig(format='%(name)s - %(levelname)s - %(message)s', level=logging.INFO)
21
+ logger_pred = logging.getLogger('model-prediction')
22
+ logger_pred.setLevel(logging.INFO)
23
+ logger_feat = logging.getLogger('input-features')
24
+ logger_feat.setLevel(logging.INFO)
25
+
26
+ """
27
+ Inference script. This script is used for prediction by scoring server when schema is known.
28
+ """
29
+
30
+
31
+ @lru_cache(maxsize=10)
32
+ def load_model():
33
+ """
34
+ Loads model from the serialized format
35
+
36
+ Returns
37
+ -------
38
+ model: a model instance on which predict API can be invoked
39
+ """
40
+ model_dir = os.path.dirname(os.path.realpath(__file__))
41
+ contents = os.listdir(model_dir)
42
+ model_file_name = "models.pickle"
43
+ if model_file_name in contents:
44
+ with open(os.path.join(os.path.dirname(os.path.realpath(__file__)), model_file_name), "rb") as file:
45
+ model = load(file)
46
+ else:
47
+ raise Exception('{0} is not found in model directory {1}'.format(model_file_name, model_dir))
48
+ return model
49
+
50
+
51
+ @lru_cache(maxsize=1)
52
+ def fetch_data_type_from_schema(
53
+ input_schema_path=os.path.join(os.path.dirname(os.path.realpath(__file__)), "input_schema.json")):
54
+ """
55
+ Returns data type information fetch from input_schema.json.
56
+
57
+ Parameters
58
+ ----------
59
+ input_schema_path: path of input schema.
60
+
61
+ Returns
62
+ -------
63
+ data_type: data type fetch from input_schema.json.
64
+
65
+ """
66
+ data_type = {}
67
+ if os.path.exists(input_schema_path):
68
+ schema = json.load(open(input_schema_path))
69
+ for col in schema['schema']:
70
+ data_type[col['name']] = col['dtype']
71
+ else:
72
+ print(
73
+ "input_schema has to be passed in in order to recover the same data type. pass `X_sample` in `ads.model.framework.sklearn_model.SklearnModel.prepare` function to generate the input_schema. Otherwise, the data type might be changed after serialization/deserialization.")
74
+ return data_type
75
+
76
+
77
+ def deserialize(data, input_schema_path):
78
+ """
79
+ Deserialize json serialization data to data in original type when sent to predict.
80
+
81
+ Parameters
82
+ ----------
83
+ data: serialized input data.
84
+ input_schema_path: path of input schema.
85
+
86
+ Returns
87
+ -------
88
+ data: deserialized input data.
89
+
90
+ """
91
+
92
+ # Add further data deserialization if needed
93
+ return data
94
+
95
+
96
+ def pre_inference(data, input_schema_path):
97
+ """
98
+ Preprocess data
99
+
100
+ Parameters
101
+ ----------
102
+ data: Data format as expected by the predict API of the core estimator.
103
+ input_schema_path: path of input schema.
104
+
105
+ Returns
106
+ -------
107
+ data: Data format after any processing.
108
+
109
+ """
110
+ return deserialize(data, input_schema_path)
111
+
112
+
113
+ def post_inference(yhat):
114
+ """
115
+ Post-process the model results
116
+
117
+ Parameters
118
+ ----------
119
+ yhat: Data format after calling model.predict.
120
+
121
+ Returns
122
+ -------
123
+ yhat: Data format after any processing.
124
+
125
+ """
126
+ if isinstance(yhat, pd.core.frame.DataFrame):
127
+ yhat = yhat.values
128
+ if isinstance(yhat, np.ndarray):
129
+ yhat = yhat.tolist()
130
+ return yhat
131
+
132
+
133
+ def get_forecast(future_df, model_name, series_id, model_object, date_col, target_column, target_cat_col, horizon):
134
+ date_col_name = date_col["name"]
135
+ date_col_format = date_col["format"]
136
+ future_df[target_cat_col] = future_df[target_cat_col].astype("str")
137
+ future_df[date_col_name] = pd.to_datetime(
138
+ future_df[date_col_name], format=date_col_format
139
+ )
140
+ if model_name == SupportedModels.AutoTS:
141
+ series_id_col = "Series"
142
+ full_data_indexed = future_df.rename(columns={target_cat_col: series_id_col})
143
+ additional_regressors = list(
144
+ set(full_data_indexed.columns) - {target_column, series_id_col, date_col_name}
145
+ )
146
+ future_reg = full_data_indexed.reset_index().pivot(
147
+ index=date_col_name,
148
+ columns=series_id_col,
149
+ values=additional_regressors,
150
+ )
151
+ pred_obj = model_object.predict(future_regressor=future_reg)
152
+ return pred_obj.forecast[series_id].tolist()
153
+ elif model_name == SupportedModels.Prophet and series_id in model_object:
154
+ model = model_object[series_id]
155
+ processed = future_df.rename(columns={date_col_name: 'ds', target_column: 'y'})
156
+ forecast = model.predict(processed)
157
+ return forecast['yhat'].tolist()
158
+ elif model_name == SupportedModels.NeuralProphet and series_id in model_object:
159
+ model = model_object[series_id]
160
+ model.restore_trainer()
161
+ accepted_regressors = list(model.config_regressors.regressors.keys())
162
+ data = future_df.rename(columns={date_col_name: 'ds', target_column: 'y'})
163
+ future = data[accepted_regressors + ["ds"]].reset_index(drop=True)
164
+ future["y"] = None
165
+ forecast = model.predict(future)
166
+ return forecast['yhat1'].tolist()
167
+ elif model_name == SupportedModels.Arima and series_id in model_object:
168
+ model = model_object[series_id]
169
+ future_df = future_df.set_index(date_col_name)
170
+ x_pred = future_df.drop(target_cat_col, axis=1)
171
+ yhat, conf_int = model.predict(
172
+ n_periods=horizon,
173
+ X=x_pred,
174
+ return_conf_int=True
175
+ )
176
+ yhat_clean = pd.DataFrame(yhat, index=yhat.index, columns=["yhat"])
177
+ return yhat_clean['yhat'].tolist()
178
+ elif model_name == SupportedModels.AutoMLX and series_id in model_object:
179
+ # automlx model
180
+ model = model_object[series_id]
181
+ x_pred = future_df.drop(target_cat_col, axis=1)
182
+ x_pred = x_pred.set_index(date_col_name)
183
+ forecast = model.forecast(
184
+ X=x_pred,
185
+ periods=horizon
186
+ )
187
+ return forecast[target_column].tolist()
188
+ else:
189
+ raise Exception(f"Invalid model object type: {type(model_object).__name__}.")
190
+
191
+
192
+ def predict(data, model=load_model()) -> dict:
193
+ """
194
+ Returns prediction given the model and data to predict
195
+
196
+ Parameters
197
+ ----------
198
+ model: Model instance returned by load_model API
199
+ data: Data format as expected by the predict API of the core estimator. For eg. in case of sckit models it could be numpy array/List of list/Panda DataFrame
200
+
201
+ Returns
202
+ -------
203
+ predictions: Output from scoring server
204
+ Format: { 'prediction': output from `model.predict` method }
205
+
206
+ """
207
+ assert model is not None, "Model is not loaded"
208
+
209
+ models = model["models"]
210
+ specs = model["spec"]
211
+ horizon = specs["horizon"]
212
+ model_name = specs["model"]
213
+ date_col = specs["datetime_column"]
214
+ target_column = specs["target_column"]
215
+ target_category_column = specs["target_category_columns"][0]
216
+
217
+ try:
218
+ input_data = InputData(**data["additional_data"])
219
+ except TypeError as e:
220
+ raise ValueError(f"Validation error: {e}")
221
+ additional_data = load_data(input_data)
222
+
223
+ unique_values = additional_data[target_category_column].unique()
224
+ forecasts = {}
225
+ for key in unique_values:
226
+ try:
227
+ s_id = str(key)
228
+ filtered = additional_data[additional_data[target_category_column] == key]
229
+ future = filtered.tail(horizon)
230
+ forecast = get_forecast(future, model_name, s_id, models, date_col,
231
+ target_column, target_category_column, horizon)
232
+ forecasts[s_id] = json.dumps(forecast)
233
+ except Exception as e:
234
+ raise RuntimeError(
235
+ f"An error occurred during prediction: {e}."
236
+ f" Please ensure the input data matches the format and structure of the data used during training.")
237
+
238
+ return {'prediction': json.dumps(forecasts)}
ads/telemetry/base.py CHANGED
@@ -1,17 +1,18 @@
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
  import logging
7
6
 
8
- from ads import set_auth
7
+ import oci
8
+
9
9
  from ads.common import oci_client as oc
10
- from ads.common.auth import default_signer
10
+ from ads.common.auth import default_signer, resource_principal
11
11
  from ads.config import OCI_RESOURCE_PRINCIPAL_VERSION
12
12
 
13
-
14
13
  logger = logging.getLogger(__name__)
14
+
15
+
15
16
  class TelemetryBase:
16
17
  """Base class for Telemetry Client."""
17
18
 
@@ -25,15 +26,21 @@ class TelemetryBase:
25
26
  namespace : str, optional
26
27
  Namespace of the OCI object storage bucket, by default None.
27
28
  """
29
+ # Use resource principal as authentication method if available,
30
+ # however, do not change the ADS authentication if user configured it by set_auth.
28
31
  if OCI_RESOURCE_PRINCIPAL_VERSION:
29
- set_auth("resource_principal")
30
- self._auth = default_signer()
31
- self.os_client = oc.OCIClientFactory(**self._auth).object_storage
32
+ self._auth = resource_principal()
33
+ else:
34
+ self._auth = default_signer()
35
+ self.os_client: oci.object_storage.ObjectStorageClient = oc.OCIClientFactory(
36
+ **self._auth
37
+ ).object_storage
32
38
  self.bucket = bucket
33
39
  self._namespace = namespace
34
40
  self._service_endpoint = None
35
- logger.debug(f"Initialized Telemetry. Namespace: {self.namespace}, Bucket: {self.bucket}")
36
-
41
+ logger.debug(
42
+ f"Initialized Telemetry. Namespace: {self.namespace}, Bucket: {self.bucket}"
43
+ )
37
44
 
38
45
  @property
39
46
  def namespace(self) -> str:
@@ -58,5 +65,5 @@ class TelemetryBase:
58
65
  Tenancy-specific endpoint.
59
66
  """
60
67
  if not self._service_endpoint:
61
- self._service_endpoint = self.os_client.base_client.endpoint
68
+ self._service_endpoint = str(self.os_client.base_client.endpoint)
62
69
  return self._service_endpoint
ads/telemetry/client.py CHANGED
@@ -1,17 +1,19 @@
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
 
7
6
  import logging
8
7
  import threading
8
+ import traceback
9
9
  import urllib.parse
10
- import requests
11
- from requests import Response
12
- from .base import TelemetryBase
10
+ from typing import Optional
11
+
12
+ import oci
13
+
13
14
  from ads.config import DEBUG_TELEMETRY
14
15
 
16
+ from .base import TelemetryBase
15
17
 
16
18
  logger = logging.getLogger(__name__)
17
19
 
@@ -32,7 +34,7 @@ class TelemetryClient(TelemetryBase):
32
34
  >>> import traceback
33
35
  >>> from ads.telemetry.client import TelemetryClient
34
36
  >>> AQUA_BUCKET = os.environ.get("AQUA_BUCKET", "service-managed-models")
35
- >>> AQUA_BUCKET_NS = os.environ.get("AQUA_BUCKET_NS", "ociodscdev")
37
+ >>> AQUA_BUCKET_NS = os.environ.get("AQUA_BUCKET_NS", "namespace")
36
38
  >>> telemetry = TelemetryClient(bucket=AQUA_BUCKET, namespace=AQUA_BUCKET_NS)
37
39
  >>> telemetry.record_event_async(category="aqua/service/model", action="create") # records create action
38
40
  >>> telemetry.record_event_async(category="aqua/service/model/create", action="shape", detail="VM.GPU.A10.1")
@@ -45,7 +47,7 @@ class TelemetryClient(TelemetryBase):
45
47
 
46
48
  def record_event(
47
49
  self, category: str = None, action: str = None, detail: str = None, **kwargs
48
- ) -> Response:
50
+ ) -> Optional[int]:
49
51
  """Send a head request to generate an event record.
50
52
 
51
53
  Parameters
@@ -62,23 +64,41 @@ class TelemetryClient(TelemetryBase):
62
64
 
63
65
  Returns
64
66
  -------
65
- Response
67
+ int
68
+ The status code for the telemetry request.
69
+ 200: The the object exists for the telemetry request
70
+ 404: The the object does not exist for the telemetry request.
71
+ Note that for telemetry purpose, the object does not need to be exist.
72
+ `None` will be returned if the telemetry request failed.
66
73
  """
67
74
  try:
68
75
  if not category or not action:
69
76
  raise ValueError("Please specify the category and the action.")
70
77
  if detail:
71
78
  category, action = f"{category}/{action}", detail
79
+ # Here `endpoint`` is for debugging purpose
80
+ # For some federated/domain users, the `endpoint` may not be a valid URL
72
81
  endpoint = f"{self.service_endpoint}/n/{self.namespace}/b/{self.bucket}/o/telemetry/{category}/{action}"
73
- headers = {"User-Agent": self._encode_user_agent(**kwargs)}
74
82
  logger.debug(f"Sending telemetry to endpoint: {endpoint}")
75
- signer = self._auth["signer"]
76
- response = requests.head(endpoint, auth=signer, headers=headers)
77
- logger.debug(f"Telemetry status code: {response.status_code}")
78
- return response
83
+
84
+ self.os_client.base_client.user_agent = self._encode_user_agent(**kwargs)
85
+ try:
86
+ response: oci.response.Response = self.os_client.head_object(
87
+ namespace_name=self.namespace,
88
+ bucket_name=self.bucket,
89
+ object_name=f"telemetry/{category}/{action}",
90
+ )
91
+ logger.debug(f"Telemetry status: {response.status}")
92
+ return response.status
93
+ except oci.exceptions.ServiceError as ex:
94
+ if ex.status == 404:
95
+ return ex.status
96
+ raise ex
79
97
  except Exception as e:
80
98
  if DEBUG_TELEMETRY:
81
99
  logger.error(f"There is an error recording telemetry: {e}")
100
+ traceback.print_exc()
101
+ return None
82
102
 
83
103
  def record_event_async(
84
104
  self, category: str = None, action: str = None, detail: str = None, **kwargs