mlrun 1.7.2rc3__py3-none-any.whl → 1.8.0__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.

Files changed (275) hide show
  1. mlrun/__init__.py +26 -22
  2. mlrun/__main__.py +15 -16
  3. mlrun/alerts/alert.py +150 -15
  4. mlrun/api/schemas/__init__.py +1 -9
  5. mlrun/artifacts/__init__.py +2 -3
  6. mlrun/artifacts/base.py +62 -19
  7. mlrun/artifacts/dataset.py +17 -17
  8. mlrun/artifacts/document.py +454 -0
  9. mlrun/artifacts/manager.py +28 -18
  10. mlrun/artifacts/model.py +91 -59
  11. mlrun/artifacts/plots.py +2 -2
  12. mlrun/common/constants.py +8 -0
  13. mlrun/common/formatters/__init__.py +1 -0
  14. mlrun/common/formatters/artifact.py +1 -1
  15. mlrun/common/formatters/feature_set.py +2 -0
  16. mlrun/common/formatters/function.py +1 -0
  17. mlrun/{model_monitoring/db/stores/v3io_kv/__init__.py → common/formatters/model_endpoint.py} +17 -0
  18. mlrun/common/formatters/pipeline.py +1 -2
  19. mlrun/common/formatters/project.py +9 -0
  20. mlrun/common/model_monitoring/__init__.py +0 -5
  21. mlrun/common/model_monitoring/helpers.py +12 -62
  22. mlrun/common/runtimes/constants.py +25 -4
  23. mlrun/common/schemas/__init__.py +9 -5
  24. mlrun/common/schemas/alert.py +114 -19
  25. mlrun/common/schemas/api_gateway.py +3 -3
  26. mlrun/common/schemas/artifact.py +22 -9
  27. mlrun/common/schemas/auth.py +8 -4
  28. mlrun/common/schemas/background_task.py +7 -7
  29. mlrun/common/schemas/client_spec.py +4 -4
  30. mlrun/common/schemas/clusterization_spec.py +2 -2
  31. mlrun/common/schemas/common.py +53 -3
  32. mlrun/common/schemas/constants.py +15 -0
  33. mlrun/common/schemas/datastore_profile.py +1 -1
  34. mlrun/common/schemas/feature_store.py +9 -9
  35. mlrun/common/schemas/frontend_spec.py +4 -4
  36. mlrun/common/schemas/function.py +10 -10
  37. mlrun/common/schemas/hub.py +1 -1
  38. mlrun/common/schemas/k8s.py +3 -3
  39. mlrun/common/schemas/memory_reports.py +3 -3
  40. mlrun/common/schemas/model_monitoring/__init__.py +4 -8
  41. mlrun/common/schemas/model_monitoring/constants.py +127 -46
  42. mlrun/common/schemas/model_monitoring/grafana.py +18 -12
  43. mlrun/common/schemas/model_monitoring/model_endpoints.py +154 -160
  44. mlrun/common/schemas/notification.py +24 -3
  45. mlrun/common/schemas/object.py +1 -1
  46. mlrun/common/schemas/pagination.py +4 -4
  47. mlrun/common/schemas/partition.py +142 -0
  48. mlrun/common/schemas/pipeline.py +3 -3
  49. mlrun/common/schemas/project.py +26 -18
  50. mlrun/common/schemas/runs.py +3 -3
  51. mlrun/common/schemas/runtime_resource.py +5 -5
  52. mlrun/common/schemas/schedule.py +1 -1
  53. mlrun/common/schemas/secret.py +1 -1
  54. mlrun/{model_monitoring/db/stores/sqldb/__init__.py → common/schemas/serving.py} +10 -1
  55. mlrun/common/schemas/tag.py +3 -3
  56. mlrun/common/schemas/workflow.py +6 -5
  57. mlrun/common/types.py +1 -0
  58. mlrun/config.py +157 -89
  59. mlrun/data_types/__init__.py +5 -3
  60. mlrun/data_types/infer.py +13 -3
  61. mlrun/data_types/spark.py +2 -1
  62. mlrun/datastore/__init__.py +59 -18
  63. mlrun/datastore/alibaba_oss.py +4 -1
  64. mlrun/datastore/azure_blob.py +4 -1
  65. mlrun/datastore/base.py +19 -24
  66. mlrun/datastore/datastore.py +10 -4
  67. mlrun/datastore/datastore_profile.py +178 -45
  68. mlrun/datastore/dbfs_store.py +4 -1
  69. mlrun/datastore/filestore.py +4 -1
  70. mlrun/datastore/google_cloud_storage.py +4 -1
  71. mlrun/datastore/hdfs.py +4 -1
  72. mlrun/datastore/inmem.py +4 -1
  73. mlrun/datastore/redis.py +4 -1
  74. mlrun/datastore/s3.py +14 -3
  75. mlrun/datastore/sources.py +89 -92
  76. mlrun/datastore/store_resources.py +7 -4
  77. mlrun/datastore/storeytargets.py +51 -16
  78. mlrun/datastore/targets.py +38 -31
  79. mlrun/datastore/utils.py +87 -4
  80. mlrun/datastore/v3io.py +4 -1
  81. mlrun/datastore/vectorstore.py +291 -0
  82. mlrun/datastore/wasbfs/fs.py +13 -12
  83. mlrun/db/base.py +286 -100
  84. mlrun/db/httpdb.py +1562 -490
  85. mlrun/db/nopdb.py +250 -83
  86. mlrun/errors.py +6 -2
  87. mlrun/execution.py +194 -50
  88. mlrun/feature_store/__init__.py +2 -10
  89. mlrun/feature_store/api.py +20 -458
  90. mlrun/feature_store/common.py +9 -9
  91. mlrun/feature_store/feature_set.py +20 -18
  92. mlrun/feature_store/feature_vector.py +105 -479
  93. mlrun/feature_store/feature_vector_utils.py +466 -0
  94. mlrun/feature_store/retrieval/base.py +15 -11
  95. mlrun/feature_store/retrieval/job.py +2 -1
  96. mlrun/feature_store/retrieval/storey_merger.py +1 -1
  97. mlrun/feature_store/steps.py +3 -3
  98. mlrun/features.py +30 -13
  99. mlrun/frameworks/__init__.py +1 -2
  100. mlrun/frameworks/_common/__init__.py +1 -2
  101. mlrun/frameworks/_common/artifacts_library.py +2 -2
  102. mlrun/frameworks/_common/mlrun_interface.py +10 -6
  103. mlrun/frameworks/_common/model_handler.py +31 -31
  104. mlrun/frameworks/_common/producer.py +3 -1
  105. mlrun/frameworks/_dl_common/__init__.py +1 -2
  106. mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
  107. mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
  108. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
  109. mlrun/frameworks/_ml_common/__init__.py +1 -2
  110. mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
  111. mlrun/frameworks/_ml_common/model_handler.py +21 -21
  112. mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
  113. mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
  114. mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
  115. mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
  116. mlrun/frameworks/auto_mlrun/__init__.py +1 -2
  117. mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
  118. mlrun/frameworks/huggingface/__init__.py +1 -2
  119. mlrun/frameworks/huggingface/model_server.py +9 -9
  120. mlrun/frameworks/lgbm/__init__.py +47 -44
  121. mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
  122. mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
  123. mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
  124. mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
  125. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
  126. mlrun/frameworks/lgbm/model_handler.py +15 -11
  127. mlrun/frameworks/lgbm/model_server.py +11 -7
  128. mlrun/frameworks/lgbm/utils.py +2 -2
  129. mlrun/frameworks/onnx/__init__.py +1 -2
  130. mlrun/frameworks/onnx/dataset.py +3 -3
  131. mlrun/frameworks/onnx/mlrun_interface.py +2 -2
  132. mlrun/frameworks/onnx/model_handler.py +7 -5
  133. mlrun/frameworks/onnx/model_server.py +8 -6
  134. mlrun/frameworks/parallel_coordinates.py +11 -11
  135. mlrun/frameworks/pytorch/__init__.py +22 -23
  136. mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
  137. mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
  138. mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
  139. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
  140. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
  141. mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
  142. mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
  143. mlrun/frameworks/pytorch/model_handler.py +21 -17
  144. mlrun/frameworks/pytorch/model_server.py +13 -9
  145. mlrun/frameworks/sklearn/__init__.py +19 -18
  146. mlrun/frameworks/sklearn/estimator.py +2 -2
  147. mlrun/frameworks/sklearn/metric.py +3 -3
  148. mlrun/frameworks/sklearn/metrics_library.py +8 -6
  149. mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
  150. mlrun/frameworks/sklearn/model_handler.py +4 -3
  151. mlrun/frameworks/tf_keras/__init__.py +11 -12
  152. mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
  153. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
  154. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
  155. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
  156. mlrun/frameworks/tf_keras/model_handler.py +17 -13
  157. mlrun/frameworks/tf_keras/model_server.py +12 -8
  158. mlrun/frameworks/xgboost/__init__.py +19 -18
  159. mlrun/frameworks/xgboost/model_handler.py +13 -9
  160. mlrun/k8s_utils.py +2 -5
  161. mlrun/launcher/base.py +3 -4
  162. mlrun/launcher/client.py +2 -2
  163. mlrun/launcher/local.py +6 -2
  164. mlrun/launcher/remote.py +1 -1
  165. mlrun/lists.py +8 -4
  166. mlrun/model.py +132 -46
  167. mlrun/model_monitoring/__init__.py +3 -5
  168. mlrun/model_monitoring/api.py +113 -98
  169. mlrun/model_monitoring/applications/__init__.py +0 -5
  170. mlrun/model_monitoring/applications/_application_steps.py +81 -50
  171. mlrun/model_monitoring/applications/base.py +467 -14
  172. mlrun/model_monitoring/applications/context.py +212 -134
  173. mlrun/model_monitoring/{db/stores/base → applications/evidently}/__init__.py +6 -2
  174. mlrun/model_monitoring/applications/evidently/base.py +146 -0
  175. mlrun/model_monitoring/applications/histogram_data_drift.py +89 -56
  176. mlrun/model_monitoring/applications/results.py +67 -15
  177. mlrun/model_monitoring/controller.py +701 -315
  178. mlrun/model_monitoring/db/__init__.py +0 -2
  179. mlrun/model_monitoring/db/_schedules.py +242 -0
  180. mlrun/model_monitoring/db/_stats.py +189 -0
  181. mlrun/model_monitoring/db/tsdb/__init__.py +33 -22
  182. mlrun/model_monitoring/db/tsdb/base.py +243 -49
  183. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +76 -36
  184. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
  185. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +213 -0
  186. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +534 -88
  187. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
  188. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +436 -106
  189. mlrun/model_monitoring/helpers.py +356 -114
  190. mlrun/model_monitoring/stream_processing.py +190 -345
  191. mlrun/model_monitoring/tracking_policy.py +11 -4
  192. mlrun/model_monitoring/writer.py +49 -90
  193. mlrun/package/__init__.py +3 -6
  194. mlrun/package/context_handler.py +2 -2
  195. mlrun/package/packager.py +12 -9
  196. mlrun/package/packagers/__init__.py +0 -2
  197. mlrun/package/packagers/default_packager.py +14 -11
  198. mlrun/package/packagers/numpy_packagers.py +16 -7
  199. mlrun/package/packagers/pandas_packagers.py +18 -18
  200. mlrun/package/packagers/python_standard_library_packagers.py +25 -11
  201. mlrun/package/packagers_manager.py +35 -32
  202. mlrun/package/utils/__init__.py +0 -3
  203. mlrun/package/utils/_pickler.py +6 -6
  204. mlrun/platforms/__init__.py +47 -16
  205. mlrun/platforms/iguazio.py +4 -1
  206. mlrun/projects/operations.py +30 -30
  207. mlrun/projects/pipelines.py +116 -47
  208. mlrun/projects/project.py +1292 -329
  209. mlrun/render.py +5 -9
  210. mlrun/run.py +57 -14
  211. mlrun/runtimes/__init__.py +1 -3
  212. mlrun/runtimes/base.py +30 -22
  213. mlrun/runtimes/daskjob.py +9 -9
  214. mlrun/runtimes/databricks_job/databricks_runtime.py +6 -5
  215. mlrun/runtimes/function_reference.py +5 -2
  216. mlrun/runtimes/generators.py +3 -2
  217. mlrun/runtimes/kubejob.py +6 -7
  218. mlrun/runtimes/mounts.py +574 -0
  219. mlrun/runtimes/mpijob/__init__.py +0 -2
  220. mlrun/runtimes/mpijob/abstract.py +7 -6
  221. mlrun/runtimes/nuclio/api_gateway.py +7 -7
  222. mlrun/runtimes/nuclio/application/application.py +11 -13
  223. mlrun/runtimes/nuclio/application/reverse_proxy.go +66 -64
  224. mlrun/runtimes/nuclio/function.py +127 -70
  225. mlrun/runtimes/nuclio/serving.py +105 -37
  226. mlrun/runtimes/pod.py +159 -54
  227. mlrun/runtimes/remotesparkjob.py +3 -2
  228. mlrun/runtimes/sparkjob/__init__.py +0 -2
  229. mlrun/runtimes/sparkjob/spark3job.py +22 -12
  230. mlrun/runtimes/utils.py +7 -6
  231. mlrun/secrets.py +2 -2
  232. mlrun/serving/__init__.py +8 -0
  233. mlrun/serving/merger.py +7 -5
  234. mlrun/serving/remote.py +35 -22
  235. mlrun/serving/routers.py +186 -240
  236. mlrun/serving/server.py +41 -10
  237. mlrun/serving/states.py +432 -118
  238. mlrun/serving/utils.py +13 -2
  239. mlrun/serving/v1_serving.py +3 -2
  240. mlrun/serving/v2_serving.py +161 -203
  241. mlrun/track/__init__.py +1 -1
  242. mlrun/track/tracker.py +2 -2
  243. mlrun/track/trackers/mlflow_tracker.py +6 -5
  244. mlrun/utils/async_http.py +35 -22
  245. mlrun/utils/clones.py +7 -4
  246. mlrun/utils/helpers.py +511 -58
  247. mlrun/utils/logger.py +119 -13
  248. mlrun/utils/notifications/notification/__init__.py +22 -19
  249. mlrun/utils/notifications/notification/base.py +39 -15
  250. mlrun/utils/notifications/notification/console.py +6 -6
  251. mlrun/utils/notifications/notification/git.py +11 -11
  252. mlrun/utils/notifications/notification/ipython.py +10 -9
  253. mlrun/utils/notifications/notification/mail.py +176 -0
  254. mlrun/utils/notifications/notification/slack.py +16 -8
  255. mlrun/utils/notifications/notification/webhook.py +24 -8
  256. mlrun/utils/notifications/notification_pusher.py +191 -200
  257. mlrun/utils/regex.py +12 -2
  258. mlrun/utils/version/version.json +2 -2
  259. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/METADATA +81 -54
  260. mlrun-1.8.0.dist-info/RECORD +351 -0
  261. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/WHEEL +1 -1
  262. mlrun/model_monitoring/applications/evidently_base.py +0 -137
  263. mlrun/model_monitoring/db/stores/__init__.py +0 -136
  264. mlrun/model_monitoring/db/stores/base/store.py +0 -213
  265. mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -71
  266. mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -190
  267. mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -103
  268. mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -40
  269. mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -659
  270. mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -726
  271. mlrun/model_monitoring/model_endpoint.py +0 -118
  272. mlrun-1.7.2rc3.dist-info/RECORD +0 -351
  273. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/entry_points.txt +0 -0
  274. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info/licenses}/LICENSE +0 -0
  275. {mlrun-1.7.2rc3.dist-info → mlrun-1.8.0.dist-info}/top_level.txt +0 -0
@@ -12,84 +12,92 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- import json
16
15
  import socket
17
- from typing import Any, Optional, cast
16
+ from typing import Any, Optional, Protocol, cast
18
17
 
18
+ import nuclio.request
19
19
  import numpy as np
20
20
  import pandas as pd
21
21
 
22
22
  import mlrun.common.constants as mlrun_constants
23
23
  import mlrun.common.schemas.model_monitoring.constants as mm_constants
24
+ import mlrun.errors
24
25
  import mlrun.feature_store as fstore
26
+ import mlrun.feature_store.feature_set as fs
25
27
  import mlrun.features
26
28
  import mlrun.serving
27
29
  import mlrun.utils
28
30
  from mlrun.artifacts import Artifact, DatasetArtifact, ModelArtifact, get_model
29
- from mlrun.common.model_monitoring.helpers import FeatureStats, pad_features_hist
31
+ from mlrun.common.model_monitoring.helpers import FeatureStats
32
+ from mlrun.common.schemas import ModelEndpoint
30
33
  from mlrun.model_monitoring.helpers import (
31
34
  calculate_inputs_statistics,
32
- get_endpoint_record,
33
35
  )
34
- from mlrun.model_monitoring.model_endpoint import ModelEndpoint
35
36
 
36
37
 
37
- class MonitoringApplicationContext:
38
+ class _ArtifactsLogger(Protocol):
38
39
  """
39
- The monitoring context holds all the relevant information for the monitoring application,
40
- and also it can be used for logging artifacts and results.
41
- The monitoring context has the following attributes:
42
-
43
- :param application_name: (str) The model monitoring application name.
44
- :param project_name: (str) The project name.
45
- :param project: (MlrunProject) The project object.
46
- :param logger: (mlrun.utils.Logger) MLRun logger.
47
- :param nuclio_logger: (nuclio.request.Logger) Nuclio logger.
48
- :param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
49
- :param feature_stats: (FeatureStats) The train sample distribution dictionary.
50
- :param sample_df: (pd.DataFrame) The new sample DataFrame.
51
- :param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
52
- :param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
53
- :param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
54
- :param endpoint_id: (str) ID of the monitored model endpoint
55
- :param output_stream_uri: (str) URI of the output stream for results
56
- :param model_endpoint: (ModelEndpoint) The model endpoint object.
57
- :param feature_names: (list[str]) List of models feature names.
58
- :param label_names: (list[str]) List of models label names.
59
- :param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
60
- and a list of extra data items.
40
+ Classes that implement this protocol are :code:`MlrunProject` and :code:`MLClientCtx`.
61
41
  """
62
42
 
43
+ def log_artifact(self, *args, **kwargs) -> Artifact: ...
44
+ def log_dataset(self, *args, **kwargs) -> DatasetArtifact: ...
45
+
46
+
47
+ class MonitoringApplicationContext:
48
+ _logger_name = "monitoring-application"
49
+
63
50
  def __init__(
64
51
  self,
65
52
  *,
66
- graph_context: mlrun.serving.GraphContext,
67
53
  application_name: str,
68
54
  event: dict[str, Any],
69
- model_endpoint_dict: dict[str, ModelEndpoint],
55
+ project: "mlrun.MlrunProject",
56
+ artifacts_logger: _ArtifactsLogger,
57
+ logger: mlrun.utils.Logger,
58
+ nuclio_logger: nuclio.request.Logger,
59
+ model_endpoint_dict: Optional[dict[str, ModelEndpoint]] = None,
60
+ sample_df: Optional[pd.DataFrame] = None,
61
+ feature_stats: Optional[FeatureStats] = None,
62
+ feature_sets_dict: Optional[dict[str, fs.FeatureSet]] = None,
70
63
  ) -> None:
71
64
  """
72
- Initialize a `MonitoringApplicationContext` object.
73
- Note: this object should not be instantiated manually.
74
-
75
- :param application_name: The application name.
76
- :param event: The instance data dictionary.
77
- :param model_endpoint_dict: Dictionary of model endpoints.
65
+ The :code:`MonitoringApplicationContext` object holds all the relevant information for the
66
+ model monitoring application, and can be used for logging artifacts and messages.
67
+ The monitoring context has the following attributes:
68
+
69
+ :param application_name: (str) The model monitoring application name.
70
+ :param project: (:py:class:`~mlrun.projects.MlrunProject`) The current MLRun project object.
71
+ :param project_name: (str) The project name.
72
+ :param logger: (:py:class:`~mlrun.utils.Logger`) MLRun logger.
73
+ :param nuclio_logger: (nuclio.request.Logger) Nuclio logger.
74
+ :param sample_df_stats: (FeatureStats) The new sample distribution dictionary.
75
+ :param feature_stats: (FeatureStats) The train sample distribution dictionary.
76
+ :param sample_df: (pd.DataFrame) The new sample DataFrame.
77
+ :param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
78
+ :param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
79
+ :param endpoint_id: (str) ID of the monitored model endpoint
80
+ :param feature_set: (FeatureSet) the model endpoint feature set
81
+ :param endpoint_name: (str) Name of the monitored model endpoint
82
+ :param output_stream_uri: (str) URI of the output stream for results
83
+ :param model_endpoint: (ModelEndpoint) The model endpoint object.
84
+ :param feature_names: (list[str]) List of models feature names.
85
+ :param label_names: (list[str]) List of models label names.
86
+ :param model: (tuple[str, ModelArtifact, dict]) The model file, model spec object,
87
+ and a list of extra data items.
78
88
  """
79
89
  self.application_name = application_name
80
90
 
81
- self.project_name = graph_context.project
82
- self.project = mlrun.load_project(url=self.project_name)
91
+ self.project = project
92
+ self.project_name = project.name
93
+
94
+ self._artifacts_logger = artifacts_logger
83
95
 
84
96
  # MLRun Logger
85
- self.logger = mlrun.utils.create_logger(
86
- level=mlrun.mlconf.log_level,
87
- formatter_kind=mlrun.mlconf.log_formatter,
88
- name="monitoring-application",
89
- )
97
+ self.logger = logger
90
98
  # Nuclio logger - `nuclio.request.Logger`.
91
- # Note: this logger does not accept keyword arguments.
92
- self.nuclio_logger = graph_context.logger
99
+ # Note: this logger accepts keyword arguments only in its `_with` methods, e.g. `info_with`.
100
+ self.nuclio_logger = nuclio_logger
93
101
 
94
102
  # event data
95
103
  self.start_infer_time = pd.Timestamp(
@@ -101,29 +109,101 @@ class MonitoringApplicationContext:
101
109
  self.endpoint_id = cast(
102
110
  str, event.get(mm_constants.ApplicationEvent.ENDPOINT_ID)
103
111
  )
104
- self.output_stream_uri = cast(
105
- str, event.get(mm_constants.ApplicationEvent.OUTPUT_STREAM_URI)
112
+ self.endpoint_name = cast(
113
+ str, event.get(mm_constants.ApplicationEvent.ENDPOINT_NAME)
106
114
  )
107
115
 
108
- self._feature_stats: Optional[FeatureStats] = None
116
+ self._feature_stats: Optional[FeatureStats] = feature_stats
109
117
  self._sample_df_stats: Optional[FeatureStats] = None
110
118
 
111
119
  # Default labels for the artifacts
112
120
  self._default_labels = self._get_default_labels()
113
121
 
114
122
  # Persistent data - fetched when needed
115
- self._sample_df: Optional[pd.DataFrame] = None
116
- self._model_endpoint: Optional[ModelEndpoint] = model_endpoint_dict.get(
117
- self.endpoint_id
123
+ self._sample_df: Optional[pd.DataFrame] = sample_df
124
+ self._model_endpoint: Optional[ModelEndpoint] = (
125
+ model_endpoint_dict.get(self.endpoint_id) if model_endpoint_dict else None
126
+ )
127
+ self._feature_set: Optional[fs.FeatureSet] = (
128
+ feature_sets_dict.get(self.endpoint_id) if feature_sets_dict else None
129
+ )
130
+ store, _, _ = mlrun.store_manager.get_or_create_store(
131
+ mlrun.mlconf.artifact_path
132
+ )
133
+ self.storage_options = store.get_storage_options()
134
+
135
+ @classmethod
136
+ def _from_ml_ctx(
137
+ cls,
138
+ context: "mlrun.MLClientCtx",
139
+ *,
140
+ application_name: str,
141
+ event: dict[str, Any],
142
+ model_endpoint_dict: Optional[dict[str, ModelEndpoint]] = None,
143
+ sample_df: Optional[pd.DataFrame] = None,
144
+ feature_stats: Optional[FeatureStats] = None,
145
+ ) -> "MonitoringApplicationContext":
146
+ project = context.get_project_object()
147
+ if not project:
148
+ raise mlrun.errors.MLRunValueError("Could not load project from context")
149
+ logger = context.logger
150
+ artifacts_logger = context
151
+ nuclio_logger = nuclio.request.Logger(
152
+ level=mlrun.mlconf.log_level, name=cls._logger_name
153
+ )
154
+ return cls(
155
+ application_name=application_name,
156
+ event=event,
157
+ model_endpoint_dict=model_endpoint_dict,
158
+ project=project,
159
+ logger=logger,
160
+ nuclio_logger=nuclio_logger,
161
+ artifacts_logger=artifacts_logger,
162
+ sample_df=sample_df,
163
+ feature_stats=feature_stats,
164
+ )
165
+
166
+ @classmethod
167
+ def _from_graph_ctx(
168
+ cls,
169
+ graph_context: mlrun.serving.GraphContext,
170
+ *,
171
+ application_name: str,
172
+ event: dict[str, Any],
173
+ model_endpoint_dict: Optional[dict[str, ModelEndpoint]] = None,
174
+ sample_df: Optional[pd.DataFrame] = None,
175
+ feature_stats: Optional[FeatureStats] = None,
176
+ feature_sets_dict: Optional[dict[str, fs.FeatureSet]] = None,
177
+ ) -> "MonitoringApplicationContext":
178
+ nuclio_logger = graph_context.logger
179
+ artifacts_logger = graph_context.project_obj
180
+ logger = mlrun.utils.create_logger(
181
+ level=mlrun.mlconf.log_level,
182
+ formatter_kind=mlrun.mlconf.log_formatter,
183
+ name=cls._logger_name,
184
+ )
185
+ return cls(
186
+ application_name=application_name,
187
+ event=event,
188
+ project=graph_context.project_obj,
189
+ model_endpoint_dict=model_endpoint_dict,
190
+ logger=logger,
191
+ nuclio_logger=nuclio_logger,
192
+ artifacts_logger=artifacts_logger,
193
+ sample_df=sample_df,
194
+ feature_stats=feature_stats,
195
+ feature_sets_dict=feature_sets_dict,
118
196
  )
119
197
 
120
198
  def _get_default_labels(self) -> dict[str, str]:
121
- return {
199
+ labels = {
122
200
  mlrun_constants.MLRunInternalLabels.runner_pod: socket.gethostname(),
123
201
  mlrun_constants.MLRunInternalLabels.producer_type: "model-monitoring-app",
124
202
  mlrun_constants.MLRunInternalLabels.app_name: self.application_name,
125
203
  mlrun_constants.MLRunInternalLabels.endpoint_id: self.endpoint_id,
204
+ mlrun_constants.MLRunInternalLabels.endpoint_name: self.endpoint_name,
126
205
  }
206
+ return {key: value for key, value in labels.items() if value is not None}
127
207
 
128
208
  def _add_default_labels(self, labels: Optional[dict[str, str]]) -> dict[str, str]:
129
209
  """Add the default labels to logged artifacts labels"""
@@ -132,39 +212,60 @@ class MonitoringApplicationContext:
132
212
  @property
133
213
  def sample_df(self) -> pd.DataFrame:
134
214
  if self._sample_df is None:
135
- feature_set = fstore.get_feature_set(
136
- self.model_endpoint.status.monitoring_feature_set_uri
137
- )
138
- features = [f"{feature_set.metadata.name}.*"]
139
- vector = fstore.FeatureVector(
140
- name=f"{self.endpoint_id}_vector",
141
- features=features,
142
- with_indexes=True,
143
- )
144
- vector.metadata.tag = self.application_name
145
- vector.feature_set_objects = {feature_set.metadata.name: feature_set}
146
-
147
- offline_response = vector.get_offline_features(
215
+ if (
216
+ self.endpoint_name is None
217
+ or self.endpoint_id is None
218
+ or pd.isnull(self.start_infer_time)
219
+ or pd.isnull(self.end_infer_time)
220
+ ):
221
+ raise mlrun.errors.MLRunValueError(
222
+ "You have tried to access `monitoring_context.sample_df`, but have not provided it directly "
223
+ "through `sample_data`, nor have you provided the model endpoint's name, ID, and the start and "
224
+ f"end times: `endpoint_name`={self.endpoint_name}, `endpoint_uid`={self.endpoint_id}, "
225
+ f"`start`={self.start_infer_time}, and `end`={self.end_infer_time}. "
226
+ "You can either provide the sample dataframe directly, the model endpoint's details and times, "
227
+ "or adapt the application's logic to not access the sample dataframe."
228
+ )
229
+ df = self.feature_set.to_dataframe(
148
230
  start_time=self.start_infer_time,
149
231
  end_time=self.end_infer_time,
150
- timestamp_for_filtering=mm_constants.FeatureSetFeatures.time_stamp(),
232
+ time_column=mm_constants.EventFieldType.TIMESTAMP,
233
+ storage_options=self.storage_options,
151
234
  )
152
- self._sample_df = offline_response.to_dataframe().reset_index(drop=True)
235
+ self._sample_df = df.reset_index(drop=True)
153
236
  return self._sample_df
154
237
 
155
238
  @property
156
239
  def model_endpoint(self) -> ModelEndpoint:
157
240
  if not self._model_endpoint:
158
- self._model_endpoint = ModelEndpoint.from_flat_dict(
159
- get_endpoint_record(self.project_name, self.endpoint_id)
241
+ if self.endpoint_name is None or self.endpoint_id is None:
242
+ raise mlrun.errors.MLRunValueError(
243
+ "You have NOT provided the model endpoint's name and ID: "
244
+ f"`endpoint_name`={self.endpoint_name} and `endpoint_id`={self.endpoint_id}, "
245
+ "but you have tried to access `monitoring_context.model_endpoint` "
246
+ "directly or indirectly in your application. You can either provide them, "
247
+ "or adapt the application's logic to not access the model endpoint."
248
+ )
249
+ self._model_endpoint = mlrun.db.get_run_db().get_model_endpoint(
250
+ name=self.endpoint_name,
251
+ project=self.project_name,
252
+ endpoint_id=self.endpoint_id,
253
+ feature_analysis=True,
160
254
  )
161
255
  return self._model_endpoint
162
256
 
257
+ @property
258
+ def feature_set(self) -> fs.FeatureSet:
259
+ if not self._feature_set and self.model_endpoint:
260
+ self._feature_set = fstore.get_feature_set(
261
+ self.model_endpoint.spec.monitoring_feature_set_uri
262
+ )
263
+ return self._feature_set
264
+
163
265
  @property
164
266
  def feature_stats(self) -> FeatureStats:
165
267
  if not self._feature_stats:
166
- self._feature_stats = json.loads(self.model_endpoint.status.feature_stats)
167
- pad_features_hist(self._feature_stats)
268
+ self._feature_stats = self.model_endpoint.spec.feature_stats
168
269
  return self._feature_stats
169
270
 
170
271
  @property
@@ -179,18 +280,12 @@ class MonitoringApplicationContext:
179
280
  @property
180
281
  def feature_names(self) -> list[str]:
181
282
  """The feature names of the model"""
182
- feature_names = self.model_endpoint.spec.feature_names
183
- return (
184
- feature_names
185
- if isinstance(feature_names, list)
186
- else json.loads(feature_names)
187
- )
283
+ return self.model_endpoint.spec.feature_names
188
284
 
189
285
  @property
190
286
  def label_names(self) -> list[str]:
191
287
  """The label names of the model"""
192
- label_names = self.model_endpoint.spec.label_names
193
- return label_names if isinstance(label_names, list) else json.loads(label_names)
288
+ return self.model_endpoint.spec.label_names
194
289
 
195
290
  @property
196
291
  def model(self) -> tuple[str, ModelArtifact, dict]:
@@ -230,14 +325,32 @@ class MonitoringApplicationContext:
230
325
  upload: Optional[bool] = None,
231
326
  labels: Optional[dict[str, str]] = None,
232
327
  target_path: Optional[str] = None,
328
+ unique_per_endpoint: bool = True,
233
329
  **kwargs,
234
330
  ) -> Artifact:
235
331
  """
236
332
  Log an artifact.
237
- See :func:`~mlrun.projects.MlrunProject.log_artifact` for the documentation.
333
+
334
+ .. caution::
335
+
336
+ Logging artifacts in every model monitoring window may cause scale issues.
337
+ This method should be called on special occasions only.
338
+
339
+ See :func:`~mlrun.projects.MlrunProject.log_artifact` for the full documentation, except for one
340
+ new argument:
341
+
342
+ :param unique_per_endpoint: by default ``True``, we will log different artifact for each model endpoint,
343
+ set to ``False`` without changing item key will cause artifact override.
238
344
  """
239
345
  labels = self._add_default_labels(labels)
240
- return self.project.log_artifact(
346
+ # By default, we want to log different artifact for each model endpoint
347
+ endpoint_id = labels.get(mlrun_constants.MLRunInternalLabels.endpoint_id, "")
348
+ if unique_per_endpoint and isinstance(item, str):
349
+ item = f"{item}-{endpoint_id}" if endpoint_id else item
350
+ elif unique_per_endpoint: # isinstance(item, Artifact) is True
351
+ item.key = f"{item.key}-{endpoint_id}" if endpoint_id else item.key
352
+
353
+ return self._artifacts_logger.log_artifact(
241
354
  item,
242
355
  body=body,
243
356
  tag=tag,
@@ -265,14 +378,30 @@ class MonitoringApplicationContext:
265
378
  target_path="",
266
379
  extra_data=None,
267
380
  label_column: Optional[str] = None,
381
+ unique_per_endpoint: bool = True,
268
382
  **kwargs,
269
383
  ) -> DatasetArtifact:
270
384
  """
271
385
  Log a dataset artifact.
272
- See :func:`~mlrun.projects.MlrunProject.log_dataset` for the documentation.
386
+
387
+ .. caution::
388
+
389
+ Logging datasets in every model monitoring window may cause scale issues.
390
+ This method should be called on special occasions only.
391
+
392
+ See :func:`~mlrun.projects.MlrunProject.log_dataset` for the full documentation, except for one
393
+ new argument:
394
+
395
+ :param unique_per_endpoint: by default ``True``, we will log different artifact for each model endpoint,
396
+ set to ``False`` without changing item key will cause artifact override.
273
397
  """
274
398
  labels = self._add_default_labels(labels)
275
- return self.project.log_dataset(
399
+ # By default, we want to log different artifact for each model endpoint
400
+ endpoint_id = labels.get(mlrun_constants.MLRunInternalLabels.endpoint_id, "")
401
+ if unique_per_endpoint and isinstance(key, str):
402
+ key = f"{key}-{endpoint_id}" if endpoint_id else key
403
+
404
+ return self._artifacts_logger.log_dataset(
276
405
  key,
277
406
  df,
278
407
  tag=tag,
@@ -288,54 +417,3 @@ class MonitoringApplicationContext:
288
417
  label_column=label_column,
289
418
  **kwargs,
290
419
  )
291
-
292
- def log_model(
293
- self,
294
- key,
295
- body=None,
296
- framework="",
297
- tag="",
298
- model_dir=None,
299
- model_file=None,
300
- algorithm=None,
301
- metrics=None,
302
- parameters=None,
303
- artifact_path=None,
304
- upload=None,
305
- labels=None,
306
- inputs: Optional[list[mlrun.features.Feature]] = None,
307
- outputs: Optional[list[mlrun.features.Feature]] = None,
308
- feature_vector: Optional[str] = None,
309
- feature_weights: Optional[list] = None,
310
- training_set=None,
311
- label_column=None,
312
- extra_data=None,
313
- **kwargs,
314
- ) -> ModelArtifact:
315
- """
316
- Log a model artifact.
317
- See :func:`~mlrun.projects.MlrunProject.log_model` for the documentation.
318
- """
319
- labels = self._add_default_labels(labels)
320
- return self.project.log_model(
321
- key,
322
- body=body,
323
- framework=framework,
324
- tag=tag,
325
- model_dir=model_dir,
326
- model_file=model_file,
327
- algorithm=algorithm,
328
- metrics=metrics,
329
- parameters=parameters,
330
- artifact_path=artifact_path,
331
- upload=upload,
332
- labels=labels,
333
- inputs=inputs,
334
- outputs=outputs,
335
- feature_vector=feature_vector,
336
- feature_weights=feature_weights,
337
- training_set=training_set,
338
- label_column=label_column,
339
- extra_data=extra_data,
340
- **kwargs,
341
- )
@@ -1,4 +1,4 @@
1
- # Copyright 2024 Iguazio
1
+ # Copyright 2025 Iguazio
2
2
  #
3
3
  # Licensed under the Apache License, Version 2.0 (the "License");
4
4
  # you may not use this file except in compliance with the License.
@@ -12,4 +12,8 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
 
15
- from .store import StoreBase
15
+ from .base import (
16
+ _HAS_EVIDENTLY,
17
+ SUPPORTED_EVIDENTLY_VERSION,
18
+ EvidentlyModelMonitoringApplicationBase,
19
+ )
@@ -0,0 +1,146 @@
1
+ # Copyright 2023 Iguazio
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ import warnings
16
+ from abc import ABC
17
+ from tempfile import NamedTemporaryFile
18
+ from typing import Optional
19
+
20
+ import semver
21
+
22
+ import mlrun.model_monitoring.applications.base as mm_base
23
+ import mlrun.model_monitoring.applications.context as mm_context
24
+ from mlrun.errors import MLRunIncompatibleVersionError, MLRunValueError
25
+
26
+ SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.7.5")
27
+
28
+
29
+ def _check_evidently_version(*, cur: semver.Version, ref: semver.Version) -> None:
30
+ if ref.is_compatible(cur) or (
31
+ cur.major == ref.major == 0 and cur.minor == ref.minor and cur.patch > ref.patch
32
+ ):
33
+ return
34
+ if cur.major == ref.major == 0 and cur.minor > ref.minor:
35
+ warnings.warn(
36
+ f"Evidently version {cur} is not compatible with the tested "
37
+ f"version {ref}, use at your own risk."
38
+ )
39
+ else:
40
+ raise MLRunIncompatibleVersionError(
41
+ f"Evidently version {cur} is not supported, please change to "
42
+ f"{ref} (or another compatible version)."
43
+ )
44
+
45
+
46
+ _HAS_EVIDENTLY = False
47
+ try:
48
+ import evidently # noqa: F401
49
+
50
+ _check_evidently_version(
51
+ cur=semver.Version.parse(evidently.__version__),
52
+ ref=SUPPORTED_EVIDENTLY_VERSION,
53
+ )
54
+ _HAS_EVIDENTLY = True
55
+ except ModuleNotFoundError:
56
+ pass
57
+
58
+
59
+ if _HAS_EVIDENTLY:
60
+ from evidently.core.report import Snapshot
61
+ from evidently.ui.workspace import (
62
+ STR_UUID,
63
+ CloudWorkspace,
64
+ Project,
65
+ Workspace,
66
+ WorkspaceBase,
67
+ )
68
+
69
+
70
+ class EvidentlyModelMonitoringApplicationBase(
71
+ mm_base.ModelMonitoringApplicationBase, ABC
72
+ ):
73
+ def __init__(
74
+ self,
75
+ evidently_project_id: "STR_UUID",
76
+ evidently_workspace_path: Optional[str] = None,
77
+ cloud_workspace: bool = False,
78
+ ) -> None:
79
+ """
80
+ A class for integrating Evidently for MLRun model monitoring within a monitoring application.
81
+
82
+ .. note::
83
+
84
+ The ``evidently`` package is not installed by default in the mlrun/mlrun image.
85
+ It must be installed separately to use this class.
86
+
87
+ :param evidently_project_id: (str) The ID of the Evidently project.
88
+ :param evidently_workspace_path: (str) The path to the Evidently workspace.
89
+ :param cloud_workspace: (bool) Whether the workspace is an Evidently Cloud workspace.
90
+ """
91
+ if not _HAS_EVIDENTLY:
92
+ raise ModuleNotFoundError("Evidently is not installed - the app cannot run")
93
+ self.evidently_workspace_path = evidently_workspace_path
94
+ if cloud_workspace:
95
+ self.get_workspace = self.get_cloud_workspace
96
+ self.evidently_workspace = self.get_workspace()
97
+ self.evidently_project_id = evidently_project_id
98
+ self.evidently_project = self.load_project()
99
+
100
+ def load_project(self) -> "Project":
101
+ """Load the Evidently project."""
102
+ return self.evidently_workspace.get_project(self.evidently_project_id)
103
+
104
+ def get_workspace(self) -> "WorkspaceBase":
105
+ """Get the Evidently workspace. Override this method for customize access to the workspace."""
106
+ if self.evidently_workspace_path:
107
+ return Workspace.create(self.evidently_workspace_path)
108
+ else:
109
+ raise MLRunValueError(
110
+ "A local workspace could not be created as `evidently_workspace_path` is not set.\n"
111
+ "If you intend to use a cloud workspace, please use `cloud_workspace=True` and set the "
112
+ "`EVIDENTLY_API_KEY` environment variable. In other cases, override this method."
113
+ )
114
+
115
+ def get_cloud_workspace(self) -> "CloudWorkspace":
116
+ """Load the Evidently cloud workspace according to the `EVIDENTLY_API_KEY` environment variable."""
117
+ return CloudWorkspace()
118
+
119
+ @staticmethod
120
+ def log_evidently_object(
121
+ monitoring_context: mm_context.MonitoringApplicationContext,
122
+ evidently_object: "Snapshot",
123
+ artifact_name: str,
124
+ unique_per_endpoint: bool = True,
125
+ ) -> None:
126
+ """
127
+ Logs an Evidently report or suite as an artifact.
128
+
129
+ .. caution::
130
+
131
+ Logging Evidently objects in every model monitoring window may cause scale issues.
132
+ This method should be called on special occasions only.
133
+
134
+ :param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
135
+ :param evidently_object: (Snapshot) The Evidently run to log, e.g. a report run.
136
+ :param artifact_name: (str) The name for the logged artifact.
137
+ :param unique_per_endpoint: by default ``True``, we will log different artifact for each model endpoint,
138
+ set to ``False`` without changing item key will cause artifact override.
139
+ """
140
+ with NamedTemporaryFile(suffix=".html") as file:
141
+ evidently_object.save_html(filename=file.name)
142
+ monitoring_context.log_artifact(
143
+ artifact_name,
144
+ local_path=file.name,
145
+ unique_per_endpoint=unique_per_endpoint,
146
+ )