mlrun 1.8.0rc45__py3-none-any.whl → 1.8.0rc47__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.

Potentially problematic release.


This version of mlrun might be problematic. Click here for more details.

mlrun/alerts/alert.py CHANGED
@@ -112,7 +112,7 @@ class AlertConfig(ModelObj):
112
112
  complex trigger which is based on a prometheus alert
113
113
  :param criteria: When the alert will be triggered based on the specified number of events within the
114
114
  defined time period.
115
- :param reset_policy: When to clear the alert. May be "manual" for manual reset of the alert, or
115
+ :param reset_policy: When to clear the alert. Either "manual" for manual reset of the alert, or
116
116
  "auto" if the criteria contains a time period
117
117
  :param notifications: List of notifications to invoke once the alert is triggered
118
118
  :param entities: Entities that the event relates to. The entity object will contain fields that
@@ -289,6 +289,11 @@ class ModelMonitoringMode(StrEnum):
289
289
  disabled = "disabled"
290
290
 
291
291
 
292
+ class ScheduleChiefFields(StrEnum):
293
+ LAST_REQUEST = "last_request"
294
+ LAST_ANALYZED = "last_analyzed"
295
+
296
+
292
297
  class EndpointType(IntEnum):
293
298
  NODE_EP = 1 # end point that is not a child of a router
294
299
  ROUTER = 2 # endpoint that is router
mlrun/config.py CHANGED
@@ -631,6 +631,8 @@ default_config = {
631
631
  "parquet_batching_max_events": 10_000,
632
632
  "parquet_batching_timeout_secs": timedelta(minutes=1).total_seconds(),
633
633
  "tdengine": {
634
+ "run_directly": True,
635
+ # timeout and retry are ignored when run_directly is set to True
634
636
  "timeout": 10,
635
637
  "retries": 1,
636
638
  },
@@ -27,8 +27,12 @@ class BaseDataInfer:
27
27
  get_stats = None
28
28
 
29
29
 
30
+ def is_spark_dataframe(df) -> bool:
31
+ return "rdd" in dir(df)
32
+
33
+
30
34
  def get_infer_interface(df) -> BaseDataInfer:
31
- if hasattr(df, "rdd"):
35
+ if is_spark_dataframe(df):
32
36
  from .spark import SparkDataInfer
33
37
 
34
38
  return SparkDataInfer
@@ -40,7 +40,7 @@ from mlrun.utils.helpers import to_parquet
40
40
  from mlrun.utils.v3io_clients import get_frames_client
41
41
 
42
42
  from .. import errors
43
- from ..data_types import ValueType
43
+ from ..data_types import ValueType, is_spark_dataframe
44
44
  from ..platforms.iguazio import parse_path, split_path
45
45
  from .datastore_profile import datastore_profile_read
46
46
  from .spark_utils import spark_session_update_hadoop_options
@@ -86,8 +86,10 @@ def generate_target_run_id():
86
86
 
87
87
 
88
88
  def write_spark_dataframe_with_options(spark_options, df, mode, write_format=None):
89
+ # TODO: Replace with just df.sparkSession when Spark 3.2 support is dropped
90
+ spark_session = getattr(df, "sparkSession", None) or df.sql_ctx.sparkSession
89
91
  non_hadoop_spark_options = spark_session_update_hadoop_options(
90
- df.sql_ctx.sparkSession, spark_options
92
+ spark_session, spark_options
91
93
  )
92
94
  if write_format:
93
95
  df.write.format(write_format).mode(mode).save(**non_hadoop_spark_options)
@@ -510,7 +512,7 @@ class BaseStoreTarget(DataTargetBase):
510
512
  chunk_id=0,
511
513
  **kwargs,
512
514
  ) -> Optional[int]:
513
- if hasattr(df, "rdd"):
515
+ if is_spark_dataframe(df):
514
516
  options = self.get_spark_options(key_column, timestamp_key)
515
517
  options.update(kwargs)
516
518
  df = self.prepare_spark_df(df, key_column, timestamp_key, options)
@@ -1376,7 +1378,7 @@ class NoSqlBaseTarget(BaseStoreTarget):
1376
1378
  def write_dataframe(
1377
1379
  self, df, key_column=None, timestamp_key=None, chunk_id=0, **kwargs
1378
1380
  ):
1379
- if hasattr(df, "rdd"):
1381
+ if is_spark_dataframe(df):
1380
1382
  options = self.get_spark_options(key_column, timestamp_key)
1381
1383
  options.update(kwargs)
1382
1384
  df = self.prepare_spark_df(df)
@@ -2108,7 +2110,7 @@ class SQLTarget(BaseStoreTarget):
2108
2110
 
2109
2111
  self._create_sql_table()
2110
2112
 
2111
- if hasattr(df, "rdd"):
2113
+ if is_spark_dataframe(df):
2112
2114
  raise ValueError("Spark is not supported")
2113
2115
  else:
2114
2116
  (
@@ -50,8 +50,8 @@ DatasetType = typing.Union[
50
50
 
51
51
  def get_or_create_model_endpoint(
52
52
  project: str,
53
+ model_endpoint_name: str,
53
54
  model_path: str = "",
54
- model_endpoint_name: str = "",
55
55
  endpoint_id: str = "",
56
56
  function_name: str = "",
57
57
  function_tag: str = "latest",
@@ -59,6 +59,7 @@ def get_or_create_model_endpoint(
59
59
  sample_set_statistics: typing.Optional[dict[str, typing.Any]] = None,
60
60
  monitoring_mode: mm_constants.ModelMonitoringMode = mm_constants.ModelMonitoringMode.enabled,
61
61
  db_session=None,
62
+ feature_analysis: bool = False,
62
63
  ) -> ModelEndpoint:
63
64
  """
64
65
  Get a single model endpoint object. If not exist, generate a new model endpoint with the provided parameters. Note
@@ -66,9 +67,9 @@ def get_or_create_model_endpoint(
66
67
  features, set `monitoring_mode=enabled`.
67
68
 
68
69
  :param project: Project name.
69
- :param model_path: The model store path (applicable only to new endpoint_id).
70
70
  :param model_endpoint_name: If a new model endpoint is created, the model endpoint name will be presented
71
71
  under this endpoint (applicable only to new endpoint_id).
72
+ :param model_path: The model store path (applicable only to new endpoint_id).
72
73
  :param endpoint_id: Model endpoint unique ID. If not exist in DB, will generate a new record based
73
74
  on the provided `endpoint_id`.
74
75
  :param function_name: If a new model endpoint is created, use this function name.
@@ -80,6 +81,7 @@ def get_or_create_model_endpoint(
80
81
  :param monitoring_mode: If enabled, apply model monitoring features on the provided endpoint id
81
82
  (applicable only to new endpoint_id).
82
83
  :param db_session: A runtime session that manages the current dialog with the database.
84
+ :param feature_analysis: If True, the model endpoint will be retrieved with the feature analysis mode.
83
85
 
84
86
  :return: A ModelEndpoint object
85
87
  """
@@ -99,6 +101,7 @@ def get_or_create_model_endpoint(
99
101
  endpoint_id=endpoint_id,
100
102
  function_name=function_name,
101
103
  function_tag=function_tag or "latest",
104
+ feature_analysis=feature_analysis,
102
105
  )
103
106
  # If other fields provided, validate that they are correspond to the existing model endpoint data
104
107
  _model_endpoint_validations(
@@ -157,7 +160,8 @@ def record_results(
157
160
  :param context: MLRun context. Note that the context is required generating the model endpoint.
158
161
  :param infer_results_df: DataFrame that will be stored under the model endpoint parquet target. Will be
159
162
  used for doing the drift analysis. Please make sure that the dataframe includes
160
- both feature names and label columns.
163
+ both feature names and label columns. If you are recording results for existing
164
+ model endpoint, the endpoint should be a batch endpoint.
161
165
  :param sample_set_statistics: Dictionary of sample set statistics that will be used as a reference data for
162
166
  the current model endpoint.
163
167
  :param monitoring_mode: If enabled, apply model monitoring features on the provided endpoint id. Enabled
@@ -218,23 +222,32 @@ def record_results(
218
222
  )
219
223
  logger.debug("Model endpoint", endpoint=model_endpoint)
220
224
 
221
- timestamp = datetime_now()
222
225
  if infer_results_df is not None:
223
- # Write the monitoring parquet to the relevant model endpoint context
224
- write_monitoring_df(
225
- feature_set_uri=model_endpoint.spec.monitoring_feature_set_uri,
226
- infer_datetime=timestamp,
227
- endpoint_id=model_endpoint.metadata.uid,
228
- infer_results_df=infer_results_df,
229
- )
226
+ if (
227
+ model_endpoint.metadata.endpoint_type
228
+ != mlrun.common.schemas.model_monitoring.EndpointType.BATCH_EP
229
+ ):
230
+ logger.warning(
231
+ "Inference results can be recorded only for batch endpoints. "
232
+ "Therefore the current results won't be monitored."
233
+ )
234
+ else:
235
+ timestamp = datetime_now()
236
+ # Write the monitoring parquet to the relevant model endpoint context
237
+ write_monitoring_df(
238
+ feature_set_uri=model_endpoint.spec.monitoring_feature_set_uri,
239
+ infer_datetime=timestamp,
240
+ endpoint_id=model_endpoint.metadata.uid,
241
+ infer_results_df=infer_results_df,
242
+ )
230
243
 
231
- # Update the last request time
232
- update_model_endpoint_last_request(
233
- project=project,
234
- model_endpoint=model_endpoint,
235
- current_request=timestamp,
236
- db=db,
237
- )
244
+ # Update the last request time
245
+ update_model_endpoint_last_request(
246
+ project=project,
247
+ model_endpoint=model_endpoint,
248
+ current_request=timestamp,
249
+ db=db,
250
+ )
238
251
 
239
252
  return model_endpoint
240
253
 
@@ -76,7 +76,6 @@ class MonitoringApplicationContext:
76
76
  :param sample_df: (pd.DataFrame) The new sample DataFrame.
77
77
  :param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
78
78
  :param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
79
- :param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
80
79
  :param endpoint_id: (str) ID of the monitored model endpoint
81
80
  :param feature_set: (FeatureSet) the model endpoint feature set
82
81
  :param endpoint_name: (str) Name of the monitored model endpoint
@@ -208,6 +207,20 @@ class MonitoringApplicationContext:
208
207
  @property
209
208
  def sample_df(self) -> pd.DataFrame:
210
209
  if self._sample_df is None:
210
+ if (
211
+ self.endpoint_name is None
212
+ or self.endpoint_id is None
213
+ or pd.isnull(self.start_infer_time)
214
+ or pd.isnull(self.end_infer_time)
215
+ ):
216
+ raise mlrun.errors.MLRunValueError(
217
+ "You have tried to access `monitoring_context.sample_df`, but have not provided it directly "
218
+ "through `sample_data`, nor have you provided the model endpoint's name, ID, and the start and "
219
+ f"end times: `endpoint_name`={self.endpoint_name}, `endpoint_uid`={self.endpoint_id}, "
220
+ f"`start`={self.start_infer_time}, and `end`={self.end_infer_time}. "
221
+ "You can either provide the sample dataframe directly, the model endpoint's details and times, "
222
+ "or adapt the application's logic to not access the sample dataframe."
223
+ )
211
224
  feature_set = self.feature_set
212
225
  features = [f"{feature_set.metadata.name}.*"]
213
226
  vector = fstore.FeatureVector(
@@ -12,12 +12,15 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
+ import json
16
+ import posixpath
15
17
  import uuid
16
18
  import warnings
17
19
  from abc import ABC
18
20
 
19
21
  import pandas as pd
20
22
  import semver
23
+ from evidently.ui.storage.local.base import METADATA_PATH, FSLocation
21
24
 
22
25
  import mlrun.model_monitoring.applications.base as mm_base
23
26
  import mlrun.model_monitoring.applications.context as mm_context
@@ -81,12 +84,47 @@ class EvidentlyModelMonitoringApplicationBase(
81
84
  # TODO : more then one project (mep -> project)
82
85
  if not _HAS_EVIDENTLY:
83
86
  raise ModuleNotFoundError("Evidently is not installed - the app cannot run")
87
+ self._log_location(evidently_workspace_path)
84
88
  self.evidently_workspace = Workspace.create(evidently_workspace_path)
85
89
  self.evidently_project_id = evidently_project_id
86
90
  self.evidently_project = self.evidently_workspace.get_project(
87
91
  evidently_project_id
88
92
  )
89
93
 
94
+ @staticmethod
95
+ def _log_location(evidently_workspace_path):
96
+ # TODO remove function + usage after solving issue ML-9530
97
+ location = FSLocation(base_path=evidently_workspace_path)
98
+ location.invalidate_cache("")
99
+ paths = [p for p in location.listdir("") if location.isdir(p)]
100
+
101
+ for path in paths:
102
+ metadata_path = posixpath.join(path, METADATA_PATH)
103
+ full_path = posixpath.join(location.path, metadata_path)
104
+ print(f"evidently json issue, working on path: {full_path}")
105
+ try:
106
+ with location.open(metadata_path) as f:
107
+ content = json.load(f)
108
+ print(
109
+ f"evidently json issue, successful load path: {full_path}, content: {content}"
110
+ )
111
+ except FileNotFoundError:
112
+ print(f"evidently json issue, path not found: {full_path}")
113
+ continue
114
+ except json.decoder.JSONDecodeError as json_error:
115
+ print(
116
+ f"evidently json issue, path got json error, path:{full_path}, error: {json_error}"
117
+ )
118
+ print("evidently json issue, file content:")
119
+ with location.open(metadata_path) as f:
120
+ print(f.read())
121
+ continue
122
+ except Exception as error:
123
+ print(
124
+ f"evidently json issue, path got general error, path:{full_path}, error: {error}"
125
+ )
126
+ continue
127
+
90
128
  @staticmethod
91
129
  def log_evidently_object(
92
130
  monitoring_context: mm_context.MonitoringApplicationContext,