mlrun 1.7.2rc4__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.
- mlrun/__init__.py +26 -22
- mlrun/__main__.py +15 -16
- mlrun/alerts/alert.py +150 -15
- mlrun/api/schemas/__init__.py +1 -9
- mlrun/artifacts/__init__.py +2 -3
- mlrun/artifacts/base.py +62 -19
- mlrun/artifacts/dataset.py +17 -17
- mlrun/artifacts/document.py +454 -0
- mlrun/artifacts/manager.py +28 -18
- mlrun/artifacts/model.py +91 -59
- mlrun/artifacts/plots.py +2 -2
- mlrun/common/constants.py +8 -0
- mlrun/common/formatters/__init__.py +1 -0
- mlrun/common/formatters/artifact.py +1 -1
- mlrun/common/formatters/feature_set.py +2 -0
- mlrun/common/formatters/function.py +1 -0
- mlrun/{model_monitoring/db/stores/v3io_kv/__init__.py → common/formatters/model_endpoint.py} +17 -0
- mlrun/common/formatters/pipeline.py +1 -2
- mlrun/common/formatters/project.py +9 -0
- mlrun/common/model_monitoring/__init__.py +0 -5
- mlrun/common/model_monitoring/helpers.py +12 -62
- mlrun/common/runtimes/constants.py +25 -4
- mlrun/common/schemas/__init__.py +9 -5
- mlrun/common/schemas/alert.py +114 -19
- mlrun/common/schemas/api_gateway.py +3 -3
- mlrun/common/schemas/artifact.py +22 -9
- mlrun/common/schemas/auth.py +8 -4
- mlrun/common/schemas/background_task.py +7 -7
- mlrun/common/schemas/client_spec.py +4 -4
- mlrun/common/schemas/clusterization_spec.py +2 -2
- mlrun/common/schemas/common.py +53 -3
- mlrun/common/schemas/constants.py +15 -0
- mlrun/common/schemas/datastore_profile.py +1 -1
- mlrun/common/schemas/feature_store.py +9 -9
- mlrun/common/schemas/frontend_spec.py +4 -4
- mlrun/common/schemas/function.py +10 -10
- mlrun/common/schemas/hub.py +1 -1
- mlrun/common/schemas/k8s.py +3 -3
- mlrun/common/schemas/memory_reports.py +3 -3
- mlrun/common/schemas/model_monitoring/__init__.py +4 -8
- mlrun/common/schemas/model_monitoring/constants.py +127 -46
- mlrun/common/schemas/model_monitoring/grafana.py +18 -12
- mlrun/common/schemas/model_monitoring/model_endpoints.py +154 -160
- mlrun/common/schemas/notification.py +24 -3
- mlrun/common/schemas/object.py +1 -1
- mlrun/common/schemas/pagination.py +4 -4
- mlrun/common/schemas/partition.py +142 -0
- mlrun/common/schemas/pipeline.py +3 -3
- mlrun/common/schemas/project.py +26 -18
- mlrun/common/schemas/runs.py +3 -3
- mlrun/common/schemas/runtime_resource.py +5 -5
- mlrun/common/schemas/schedule.py +1 -1
- mlrun/common/schemas/secret.py +1 -1
- mlrun/{model_monitoring/db/stores/sqldb/__init__.py → common/schemas/serving.py} +10 -1
- mlrun/common/schemas/tag.py +3 -3
- mlrun/common/schemas/workflow.py +6 -5
- mlrun/common/types.py +1 -0
- mlrun/config.py +157 -89
- mlrun/data_types/__init__.py +5 -3
- mlrun/data_types/infer.py +13 -3
- mlrun/data_types/spark.py +2 -1
- mlrun/datastore/__init__.py +59 -18
- mlrun/datastore/alibaba_oss.py +4 -1
- mlrun/datastore/azure_blob.py +4 -1
- mlrun/datastore/base.py +19 -24
- mlrun/datastore/datastore.py +10 -4
- mlrun/datastore/datastore_profile.py +178 -45
- mlrun/datastore/dbfs_store.py +4 -1
- mlrun/datastore/filestore.py +4 -1
- mlrun/datastore/google_cloud_storage.py +4 -1
- mlrun/datastore/hdfs.py +4 -1
- mlrun/datastore/inmem.py +4 -1
- mlrun/datastore/redis.py +4 -1
- mlrun/datastore/s3.py +14 -3
- mlrun/datastore/sources.py +89 -92
- mlrun/datastore/store_resources.py +7 -4
- mlrun/datastore/storeytargets.py +51 -16
- mlrun/datastore/targets.py +38 -31
- mlrun/datastore/utils.py +87 -4
- mlrun/datastore/v3io.py +4 -1
- mlrun/datastore/vectorstore.py +291 -0
- mlrun/datastore/wasbfs/fs.py +13 -12
- mlrun/db/base.py +286 -100
- mlrun/db/httpdb.py +1562 -490
- mlrun/db/nopdb.py +250 -83
- mlrun/errors.py +6 -2
- mlrun/execution.py +194 -50
- mlrun/feature_store/__init__.py +2 -10
- mlrun/feature_store/api.py +20 -458
- mlrun/feature_store/common.py +9 -9
- mlrun/feature_store/feature_set.py +20 -18
- mlrun/feature_store/feature_vector.py +105 -479
- mlrun/feature_store/feature_vector_utils.py +466 -0
- mlrun/feature_store/retrieval/base.py +15 -11
- mlrun/feature_store/retrieval/job.py +2 -1
- mlrun/feature_store/retrieval/storey_merger.py +1 -1
- mlrun/feature_store/steps.py +3 -3
- mlrun/features.py +30 -13
- mlrun/frameworks/__init__.py +1 -2
- mlrun/frameworks/_common/__init__.py +1 -2
- mlrun/frameworks/_common/artifacts_library.py +2 -2
- mlrun/frameworks/_common/mlrun_interface.py +10 -6
- mlrun/frameworks/_common/model_handler.py +31 -31
- mlrun/frameworks/_common/producer.py +3 -1
- mlrun/frameworks/_dl_common/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_dl_common/loggers/mlrun_logger.py +4 -4
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +3 -3
- mlrun/frameworks/_ml_common/__init__.py +1 -2
- mlrun/frameworks/_ml_common/loggers/__init__.py +1 -2
- mlrun/frameworks/_ml_common/model_handler.py +21 -21
- mlrun/frameworks/_ml_common/plans/__init__.py +1 -2
- mlrun/frameworks/_ml_common/plans/confusion_matrix_plan.py +3 -1
- mlrun/frameworks/_ml_common/plans/dataset_plan.py +3 -3
- mlrun/frameworks/_ml_common/plans/roc_curve_plan.py +4 -4
- mlrun/frameworks/auto_mlrun/__init__.py +1 -2
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +22 -15
- mlrun/frameworks/huggingface/__init__.py +1 -2
- mlrun/frameworks/huggingface/model_server.py +9 -9
- mlrun/frameworks/lgbm/__init__.py +47 -44
- mlrun/frameworks/lgbm/callbacks/__init__.py +1 -2
- mlrun/frameworks/lgbm/callbacks/logging_callback.py +4 -2
- mlrun/frameworks/lgbm/callbacks/mlrun_logging_callback.py +4 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/__init__.py +1 -2
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +5 -5
- mlrun/frameworks/lgbm/model_handler.py +15 -11
- mlrun/frameworks/lgbm/model_server.py +11 -7
- mlrun/frameworks/lgbm/utils.py +2 -2
- mlrun/frameworks/onnx/__init__.py +1 -2
- mlrun/frameworks/onnx/dataset.py +3 -3
- mlrun/frameworks/onnx/mlrun_interface.py +2 -2
- mlrun/frameworks/onnx/model_handler.py +7 -5
- mlrun/frameworks/onnx/model_server.py +8 -6
- mlrun/frameworks/parallel_coordinates.py +11 -11
- mlrun/frameworks/pytorch/__init__.py +22 -23
- mlrun/frameworks/pytorch/callbacks/__init__.py +1 -2
- mlrun/frameworks/pytorch/callbacks/callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +15 -8
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +19 -12
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +22 -15
- mlrun/frameworks/pytorch/callbacks_handler.py +36 -30
- mlrun/frameworks/pytorch/mlrun_interface.py +17 -17
- mlrun/frameworks/pytorch/model_handler.py +21 -17
- mlrun/frameworks/pytorch/model_server.py +13 -9
- mlrun/frameworks/sklearn/__init__.py +19 -18
- mlrun/frameworks/sklearn/estimator.py +2 -2
- mlrun/frameworks/sklearn/metric.py +3 -3
- mlrun/frameworks/sklearn/metrics_library.py +8 -6
- mlrun/frameworks/sklearn/mlrun_interface.py +3 -2
- mlrun/frameworks/sklearn/model_handler.py +4 -3
- mlrun/frameworks/tf_keras/__init__.py +11 -12
- mlrun/frameworks/tf_keras/callbacks/__init__.py +1 -2
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +17 -14
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +15 -12
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +21 -18
- mlrun/frameworks/tf_keras/model_handler.py +17 -13
- mlrun/frameworks/tf_keras/model_server.py +12 -8
- mlrun/frameworks/xgboost/__init__.py +19 -18
- mlrun/frameworks/xgboost/model_handler.py +13 -9
- mlrun/k8s_utils.py +2 -5
- mlrun/launcher/base.py +3 -4
- mlrun/launcher/client.py +2 -2
- mlrun/launcher/local.py +6 -2
- mlrun/launcher/remote.py +1 -1
- mlrun/lists.py +8 -4
- mlrun/model.py +132 -46
- mlrun/model_monitoring/__init__.py +3 -5
- mlrun/model_monitoring/api.py +113 -98
- mlrun/model_monitoring/applications/__init__.py +0 -5
- mlrun/model_monitoring/applications/_application_steps.py +81 -50
- mlrun/model_monitoring/applications/base.py +467 -14
- mlrun/model_monitoring/applications/context.py +212 -134
- mlrun/model_monitoring/{db/stores/base → applications/evidently}/__init__.py +6 -2
- mlrun/model_monitoring/applications/evidently/base.py +146 -0
- mlrun/model_monitoring/applications/histogram_data_drift.py +89 -56
- mlrun/model_monitoring/applications/results.py +67 -15
- mlrun/model_monitoring/controller.py +701 -315
- mlrun/model_monitoring/db/__init__.py +0 -2
- mlrun/model_monitoring/db/_schedules.py +242 -0
- mlrun/model_monitoring/db/_stats.py +189 -0
- mlrun/model_monitoring/db/tsdb/__init__.py +33 -22
- mlrun/model_monitoring/db/tsdb/base.py +243 -49
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +76 -36
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +33 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +213 -0
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +534 -88
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +1 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +436 -106
- mlrun/model_monitoring/helpers.py +356 -114
- mlrun/model_monitoring/stream_processing.py +190 -345
- mlrun/model_monitoring/tracking_policy.py +11 -4
- mlrun/model_monitoring/writer.py +49 -90
- mlrun/package/__init__.py +3 -6
- mlrun/package/context_handler.py +2 -2
- mlrun/package/packager.py +12 -9
- mlrun/package/packagers/__init__.py +0 -2
- mlrun/package/packagers/default_packager.py +14 -11
- mlrun/package/packagers/numpy_packagers.py +16 -7
- mlrun/package/packagers/pandas_packagers.py +18 -18
- mlrun/package/packagers/python_standard_library_packagers.py +25 -11
- mlrun/package/packagers_manager.py +35 -32
- mlrun/package/utils/__init__.py +0 -3
- mlrun/package/utils/_pickler.py +6 -6
- mlrun/platforms/__init__.py +47 -16
- mlrun/platforms/iguazio.py +4 -1
- mlrun/projects/operations.py +30 -30
- mlrun/projects/pipelines.py +116 -47
- mlrun/projects/project.py +1292 -329
- mlrun/render.py +5 -9
- mlrun/run.py +57 -14
- mlrun/runtimes/__init__.py +1 -3
- mlrun/runtimes/base.py +30 -22
- mlrun/runtimes/daskjob.py +9 -9
- mlrun/runtimes/databricks_job/databricks_runtime.py +6 -5
- mlrun/runtimes/function_reference.py +5 -2
- mlrun/runtimes/generators.py +3 -2
- mlrun/runtimes/kubejob.py +6 -7
- mlrun/runtimes/mounts.py +574 -0
- mlrun/runtimes/mpijob/__init__.py +0 -2
- mlrun/runtimes/mpijob/abstract.py +7 -6
- mlrun/runtimes/nuclio/api_gateway.py +7 -7
- mlrun/runtimes/nuclio/application/application.py +11 -13
- mlrun/runtimes/nuclio/application/reverse_proxy.go +66 -64
- mlrun/runtimes/nuclio/function.py +127 -70
- mlrun/runtimes/nuclio/serving.py +105 -37
- mlrun/runtimes/pod.py +159 -54
- mlrun/runtimes/remotesparkjob.py +3 -2
- mlrun/runtimes/sparkjob/__init__.py +0 -2
- mlrun/runtimes/sparkjob/spark3job.py +22 -12
- mlrun/runtimes/utils.py +7 -6
- mlrun/secrets.py +2 -2
- mlrun/serving/__init__.py +8 -0
- mlrun/serving/merger.py +7 -5
- mlrun/serving/remote.py +35 -22
- mlrun/serving/routers.py +186 -240
- mlrun/serving/server.py +41 -10
- mlrun/serving/states.py +432 -118
- mlrun/serving/utils.py +13 -2
- mlrun/serving/v1_serving.py +3 -2
- mlrun/serving/v2_serving.py +161 -203
- mlrun/track/__init__.py +1 -1
- mlrun/track/tracker.py +2 -2
- mlrun/track/trackers/mlflow_tracker.py +6 -5
- mlrun/utils/async_http.py +35 -22
- mlrun/utils/clones.py +7 -4
- mlrun/utils/helpers.py +511 -58
- mlrun/utils/logger.py +119 -13
- mlrun/utils/notifications/notification/__init__.py +22 -19
- mlrun/utils/notifications/notification/base.py +39 -15
- mlrun/utils/notifications/notification/console.py +6 -6
- mlrun/utils/notifications/notification/git.py +11 -11
- mlrun/utils/notifications/notification/ipython.py +10 -9
- mlrun/utils/notifications/notification/mail.py +176 -0
- mlrun/utils/notifications/notification/slack.py +16 -8
- mlrun/utils/notifications/notification/webhook.py +24 -8
- mlrun/utils/notifications/notification_pusher.py +191 -200
- mlrun/utils/regex.py +12 -2
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/METADATA +69 -54
- mlrun-1.8.0.dist-info/RECORD +351 -0
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/WHEEL +1 -1
- mlrun/model_monitoring/applications/evidently_base.py +0 -137
- mlrun/model_monitoring/db/stores/__init__.py +0 -136
- mlrun/model_monitoring/db/stores/base/store.py +0 -213
- mlrun/model_monitoring/db/stores/sqldb/models/__init__.py +0 -71
- mlrun/model_monitoring/db/stores/sqldb/models/base.py +0 -190
- mlrun/model_monitoring/db/stores/sqldb/models/mysql.py +0 -103
- mlrun/model_monitoring/db/stores/sqldb/models/sqlite.py +0 -40
- mlrun/model_monitoring/db/stores/sqldb/sql_store.py +0 -659
- mlrun/model_monitoring/db/stores/v3io_kv/kv_store.py +0 -726
- mlrun/model_monitoring/model_endpoint.py +0 -118
- mlrun-1.7.2rc4.dist-info/RECORD +0 -351
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info/licenses}/LICENSE +0 -0
- {mlrun-1.7.2rc4.dist-info → mlrun-1.8.0.dist-info}/top_level.txt +0 -0
|
@@ -12,39 +12,57 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
+
import socket
|
|
15
16
|
from abc import ABC, abstractmethod
|
|
16
|
-
from
|
|
17
|
+
from collections.abc import Iterator
|
|
18
|
+
from datetime import datetime, timedelta
|
|
19
|
+
from typing import Any, Optional, Union, cast
|
|
17
20
|
|
|
21
|
+
import pandas as pd
|
|
22
|
+
|
|
23
|
+
import mlrun
|
|
24
|
+
import mlrun.common.constants as mlrun_constants
|
|
25
|
+
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
26
|
+
import mlrun.errors
|
|
27
|
+
import mlrun.model_monitoring.api as mm_api
|
|
18
28
|
import mlrun.model_monitoring.applications.context as mm_context
|
|
19
29
|
import mlrun.model_monitoring.applications.results as mm_results
|
|
20
30
|
from mlrun.serving.utils import MonitoringApplicationToDict
|
|
31
|
+
from mlrun.utils import logger
|
|
21
32
|
|
|
22
33
|
|
|
23
34
|
class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
24
35
|
"""
|
|
25
|
-
|
|
36
|
+
The base class for a model monitoring application.
|
|
26
37
|
Inherit from this class to create a custom model monitoring application.
|
|
27
38
|
|
|
28
|
-
example
|
|
39
|
+
For example, :code:`MyApp` below is a simplistic custom application::
|
|
40
|
+
|
|
41
|
+
from mlrun.common.schemas.model_monitoring.constants import (
|
|
42
|
+
ResultKindApp,
|
|
43
|
+
ResultStatusApp,
|
|
44
|
+
)
|
|
45
|
+
from mlrun.model_monitoring.applications import (
|
|
46
|
+
ModelMonitoringApplicationBase,
|
|
47
|
+
ModelMonitoringApplicationResult,
|
|
48
|
+
MonitoringApplicationContext,
|
|
49
|
+
)
|
|
50
|
+
|
|
29
51
|
|
|
30
|
-
class MyApp(
|
|
52
|
+
class MyApp(ModelMonitoringApplicationBase):
|
|
31
53
|
def do_tracking(
|
|
32
|
-
self,
|
|
33
|
-
monitoring_context: mm_context.MonitoringApplicationContext,
|
|
54
|
+
self, monitoring_context: MonitoringApplicationContext
|
|
34
55
|
) -> ModelMonitoringApplicationResult:
|
|
35
|
-
monitoring_context.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
)
|
|
56
|
+
monitoring_context.logger.info(
|
|
57
|
+
"Running application",
|
|
58
|
+
application_name=monitoring_context.application_name,
|
|
39
59
|
)
|
|
40
60
|
return ModelMonitoringApplicationResult(
|
|
41
61
|
name="data_drift_test",
|
|
42
62
|
value=0.5,
|
|
43
|
-
kind=
|
|
44
|
-
status=
|
|
63
|
+
kind=ResultKindApp.data_drift,
|
|
64
|
+
status=ResultStatusApp.detected,
|
|
45
65
|
)
|
|
46
|
-
|
|
47
|
-
|
|
48
66
|
"""
|
|
49
67
|
|
|
50
68
|
kind = "monitoring_application"
|
|
@@ -62,6 +80,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
62
80
|
]:
|
|
63
81
|
"""
|
|
64
82
|
Process the monitoring event and return application results & metrics.
|
|
83
|
+
Note: this method is internal and should not be called directly or overridden.
|
|
65
84
|
|
|
66
85
|
:param monitoring_context: (MonitoringApplicationContext) The monitoring application context.
|
|
67
86
|
:returns: A tuple of:
|
|
@@ -80,6 +99,440 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
80
99
|
results = results if isinstance(results, list) else [results]
|
|
81
100
|
return results, monitoring_context
|
|
82
101
|
|
|
102
|
+
@staticmethod
|
|
103
|
+
def _flatten_data_result(
|
|
104
|
+
result: Union[
|
|
105
|
+
list[mm_results._ModelMonitoringApplicationDataRes],
|
|
106
|
+
mm_results._ModelMonitoringApplicationDataRes,
|
|
107
|
+
],
|
|
108
|
+
) -> Union[list[dict], dict]:
|
|
109
|
+
"""Flatten result/metric objects to dictionaries"""
|
|
110
|
+
if isinstance(result, mm_results._ModelMonitoringApplicationDataRes):
|
|
111
|
+
return result.to_dict()
|
|
112
|
+
if isinstance(result, list):
|
|
113
|
+
return [
|
|
114
|
+
element.to_dict()
|
|
115
|
+
if isinstance(element, mm_results._ModelMonitoringApplicationDataRes)
|
|
116
|
+
else element
|
|
117
|
+
for element in result
|
|
118
|
+
]
|
|
119
|
+
return result
|
|
120
|
+
|
|
121
|
+
def _handler(
|
|
122
|
+
self,
|
|
123
|
+
context: "mlrun.MLClientCtx",
|
|
124
|
+
sample_data: Optional[pd.DataFrame] = None,
|
|
125
|
+
reference_data: Optional[pd.DataFrame] = None,
|
|
126
|
+
endpoints: Optional[Union[list[tuple[str, str]], list[str], str]] = None,
|
|
127
|
+
start: Optional[str] = None,
|
|
128
|
+
end: Optional[str] = None,
|
|
129
|
+
base_period: Optional[int] = None,
|
|
130
|
+
):
|
|
131
|
+
"""
|
|
132
|
+
A custom handler that wraps the application's logic implemented in
|
|
133
|
+
:py:meth:`~mlrun.model_monitoring.applications.ModelMonitoringApplicationBase.do_tracking`
|
|
134
|
+
for an MLRun job.
|
|
135
|
+
This method should not be called directly.
|
|
136
|
+
"""
|
|
137
|
+
feature_stats = (
|
|
138
|
+
mm_api.get_sample_set_statistics(reference_data)
|
|
139
|
+
if reference_data is not None
|
|
140
|
+
else None
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
def call_do_tracking(event: Optional[dict] = None):
|
|
144
|
+
if event is None:
|
|
145
|
+
event = {}
|
|
146
|
+
monitoring_context = mm_context.MonitoringApplicationContext._from_ml_ctx(
|
|
147
|
+
event=event,
|
|
148
|
+
application_name=self.__class__.__name__,
|
|
149
|
+
context=context,
|
|
150
|
+
sample_df=sample_data,
|
|
151
|
+
feature_stats=feature_stats,
|
|
152
|
+
)
|
|
153
|
+
return self.do_tracking(monitoring_context)
|
|
154
|
+
|
|
155
|
+
if endpoints is not None:
|
|
156
|
+
for window_start, window_end in self._window_generator(
|
|
157
|
+
start, end, base_period
|
|
158
|
+
):
|
|
159
|
+
for endpoint_name, endpoint_id in endpoints:
|
|
160
|
+
result = call_do_tracking(
|
|
161
|
+
event={
|
|
162
|
+
mm_constants.ApplicationEvent.ENDPOINT_NAME: endpoint_name,
|
|
163
|
+
mm_constants.ApplicationEvent.ENDPOINT_ID: endpoint_id,
|
|
164
|
+
mm_constants.ApplicationEvent.START_INFER_TIME: window_start,
|
|
165
|
+
mm_constants.ApplicationEvent.END_INFER_TIME: window_end,
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
result_key = (
|
|
169
|
+
f"{endpoint_name}-{endpoint_id}_{window_start.isoformat()}_{window_end.isoformat()}"
|
|
170
|
+
if window_start and window_end
|
|
171
|
+
else f"{endpoint_name}-{endpoint_id}"
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
context.log_result(result_key, self._flatten_data_result(result))
|
|
175
|
+
else:
|
|
176
|
+
return self._flatten_data_result(call_do_tracking())
|
|
177
|
+
|
|
178
|
+
@staticmethod
|
|
179
|
+
def _handle_endpoints_type_evaluate(
|
|
180
|
+
project: str,
|
|
181
|
+
endpoints: Union[list[tuple[str, str]], list[str], str, None],
|
|
182
|
+
) -> list[tuple[str, str]]:
|
|
183
|
+
if endpoints:
|
|
184
|
+
if isinstance(endpoints, str) or (
|
|
185
|
+
isinstance(endpoints, list) and isinstance(endpoints[0], str)
|
|
186
|
+
):
|
|
187
|
+
endpoints_list = (
|
|
188
|
+
mlrun.get_run_db()
|
|
189
|
+
.list_model_endpoints(
|
|
190
|
+
project,
|
|
191
|
+
names=endpoints,
|
|
192
|
+
latest_only=True,
|
|
193
|
+
)
|
|
194
|
+
.endpoints
|
|
195
|
+
)
|
|
196
|
+
if endpoints_list:
|
|
197
|
+
list_endpoints_result = [
|
|
198
|
+
(endpoint.metadata.name, endpoint.metadata.uid)
|
|
199
|
+
for endpoint in endpoints_list
|
|
200
|
+
]
|
|
201
|
+
retrieve_ep_names = list(
|
|
202
|
+
map(lambda endpoint: endpoint[0], list_endpoints_result)
|
|
203
|
+
)
|
|
204
|
+
missing = set(
|
|
205
|
+
[endpoints] if isinstance(endpoints, str) else endpoints
|
|
206
|
+
) - set(retrieve_ep_names)
|
|
207
|
+
if missing:
|
|
208
|
+
logger.warning(
|
|
209
|
+
"Could not list all the required endpoints.",
|
|
210
|
+
missing_endpoint=missing,
|
|
211
|
+
endpoints=list_endpoints_result,
|
|
212
|
+
)
|
|
213
|
+
endpoints = list_endpoints_result
|
|
214
|
+
else:
|
|
215
|
+
raise mlrun.errors.MLRunNotFoundError(
|
|
216
|
+
f"Did not find any model_endpoint named ' {endpoints}'"
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
if not (
|
|
220
|
+
isinstance(endpoints, list) and isinstance(endpoints[0], (list, tuple))
|
|
221
|
+
):
|
|
222
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
223
|
+
"Could not resolve endpoints as list of [(name, uid)]"
|
|
224
|
+
)
|
|
225
|
+
return endpoints
|
|
226
|
+
|
|
227
|
+
@staticmethod
|
|
228
|
+
def _window_generator(
|
|
229
|
+
start: Optional[str], end: Optional[str], base_period: Optional[int]
|
|
230
|
+
) -> Iterator[tuple[Optional[datetime], Optional[datetime]]]:
|
|
231
|
+
if start is None or end is None:
|
|
232
|
+
# A single window based on the `sample_data` input - see `_handler`.
|
|
233
|
+
yield None, None
|
|
234
|
+
return
|
|
235
|
+
|
|
236
|
+
start_dt = datetime.fromisoformat(start)
|
|
237
|
+
end_dt = datetime.fromisoformat(end)
|
|
238
|
+
|
|
239
|
+
if base_period is None:
|
|
240
|
+
yield start_dt, end_dt
|
|
241
|
+
return
|
|
242
|
+
|
|
243
|
+
if not isinstance(base_period, int) or base_period <= 0:
|
|
244
|
+
raise mlrun.errors.MLRunValueError(
|
|
245
|
+
"`base_period` must be a nonnegative integer - the number of minutes in a monitoring window"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
window_length = timedelta(minutes=base_period)
|
|
249
|
+
current_start_time = start_dt
|
|
250
|
+
while current_start_time < end_dt:
|
|
251
|
+
current_end_time = min(current_start_time + window_length, end_dt)
|
|
252
|
+
yield current_start_time, current_end_time
|
|
253
|
+
current_start_time = current_end_time
|
|
254
|
+
|
|
255
|
+
@classmethod
|
|
256
|
+
def deploy(
|
|
257
|
+
cls,
|
|
258
|
+
func_name: str,
|
|
259
|
+
func_path: Optional[str] = None,
|
|
260
|
+
image: Optional[str] = None,
|
|
261
|
+
handler: Optional[str] = None,
|
|
262
|
+
with_repo: Optional[bool] = False,
|
|
263
|
+
tag: Optional[str] = None,
|
|
264
|
+
requirements: Optional[Union[str, list[str]]] = None,
|
|
265
|
+
requirements_file: str = "",
|
|
266
|
+
**application_kwargs,
|
|
267
|
+
) -> None:
|
|
268
|
+
"""
|
|
269
|
+
Set the application to the current project and deploy it as a Nuclio serving function.
|
|
270
|
+
Required for your model monitoring application to work as a part of the model monitoring framework.
|
|
271
|
+
|
|
272
|
+
:param func_name: The name of the function.
|
|
273
|
+
:param func_path: The path of the function, :code:`None` refers to the current Jupyter notebook.
|
|
274
|
+
|
|
275
|
+
For the other arguments, refer to
|
|
276
|
+
:py:meth:`~mlrun.projects.MlrunProject.set_model_monitoring_function`.
|
|
277
|
+
"""
|
|
278
|
+
project = cast("mlrun.MlrunProject", mlrun.get_current_project())
|
|
279
|
+
function = project.set_model_monitoring_function(
|
|
280
|
+
name=func_name,
|
|
281
|
+
func=func_path,
|
|
282
|
+
application_class=cls.__name__,
|
|
283
|
+
handler=handler,
|
|
284
|
+
image=image,
|
|
285
|
+
with_repo=with_repo,
|
|
286
|
+
requirements=requirements,
|
|
287
|
+
requirements_file=requirements_file,
|
|
288
|
+
tag=tag,
|
|
289
|
+
**application_kwargs,
|
|
290
|
+
)
|
|
291
|
+
function.deploy()
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def get_job_handler(cls, handler_to_class: str) -> str:
|
|
295
|
+
"""
|
|
296
|
+
A helper function to get the handler to the application job ``_handler``.
|
|
297
|
+
|
|
298
|
+
:param handler_to_class: The handler to the application class, e.g. ``my_package.sub_module1.MonitoringApp1``.
|
|
299
|
+
:returns: The handler to the job of the application class.
|
|
300
|
+
"""
|
|
301
|
+
return f"{handler_to_class}::{cls._handler.__name__}"
|
|
302
|
+
|
|
303
|
+
@classmethod
|
|
304
|
+
def to_job(
|
|
305
|
+
cls,
|
|
306
|
+
*,
|
|
307
|
+
class_handler: Optional[str] = None,
|
|
308
|
+
func_path: Optional[str] = None,
|
|
309
|
+
func_name: Optional[str] = None,
|
|
310
|
+
tag: Optional[str] = None,
|
|
311
|
+
image: Optional[str] = None,
|
|
312
|
+
with_repo: Optional[bool] = False,
|
|
313
|
+
requirements: Optional[Union[str, list[str]]] = None,
|
|
314
|
+
requirements_file: str = "",
|
|
315
|
+
project: Optional["mlrun.MlrunProject"] = None,
|
|
316
|
+
) -> mlrun.runtimes.KubejobRuntime:
|
|
317
|
+
"""
|
|
318
|
+
Get the application's :py:meth:`~mlrun.model_monitoring.applications.ModelMonitoringApplicationBase.do_tracking`
|
|
319
|
+
model monitoring logic as a :py:class:`~mlrun.runtimes.KubejobRuntime`.
|
|
320
|
+
|
|
321
|
+
The returned job can be run as any MLRun job with the relevant inputs and params to your application:
|
|
322
|
+
|
|
323
|
+
.. code-block:: python
|
|
324
|
+
|
|
325
|
+
job = ModelMonitoringApplicationBase.to_job(
|
|
326
|
+
class_handler="package.module.AppClass"
|
|
327
|
+
)
|
|
328
|
+
job.run(inputs={}, params={}, local=False) # Add the relevant inputs and params
|
|
329
|
+
|
|
330
|
+
Optional inputs:
|
|
331
|
+
|
|
332
|
+
* ``sample_data``, ``pd.DataFrame``
|
|
333
|
+
* ``reference_data``, ``pd.DataFrame``
|
|
334
|
+
|
|
335
|
+
Optional params:
|
|
336
|
+
|
|
337
|
+
* ``endpoints``, ``list[tuple[str, str]]``
|
|
338
|
+
* ``start``, ``datetime``
|
|
339
|
+
* ``end``, ``datetime``
|
|
340
|
+
* ``base_period``, ``int``
|
|
341
|
+
|
|
342
|
+
For Git sources, add the source archive to the returned job and change the handler:
|
|
343
|
+
|
|
344
|
+
.. code-block:: python
|
|
345
|
+
|
|
346
|
+
handler = ModelMonitoringApplicationBase.get_job_handler("module.AppClass")
|
|
347
|
+
job.with_source_archive(
|
|
348
|
+
"git://github.com/owner/repo.git#branch-category/specific-task",
|
|
349
|
+
workdir="path/to/application/folder",
|
|
350
|
+
handler=handler,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
:param class_handler: The handler to the class, e.g. ``path.to.module::MonitoringApplication``,
|
|
354
|
+
useful when using Git sources or code from images.
|
|
355
|
+
If ``None``, the current class, deriving from
|
|
356
|
+
:py:class:`~mlrun.model_monitoring.applications.ModelMonitoringApplicationBase`,
|
|
357
|
+
is used.
|
|
358
|
+
:param func_path: The path to the function. If ``None``, the current notebook is used.
|
|
359
|
+
:param func_name: The name of the function. If not ``None``, the class name is used.
|
|
360
|
+
:param tag: Tag for the function.
|
|
361
|
+
:param image: Docker image to run the job on (when running remotely).
|
|
362
|
+
:param with_repo: Whether to clone the current repo to the build source.
|
|
363
|
+
:param requirements: List of Python requirements to be installed in the image.
|
|
364
|
+
:param requirements_file: Path to a Python requirements file to be installed in the image.
|
|
365
|
+
:param project: The current project to set the function to. If not set, the current project is used.
|
|
366
|
+
|
|
367
|
+
:returns: The :py:class:`~mlrun.runtimes.KubejobRuntime` job that wraps the model monitoring application's
|
|
368
|
+
logic.
|
|
369
|
+
"""
|
|
370
|
+
project = project or cast("mlrun.MlrunProject", mlrun.get_current_project())
|
|
371
|
+
|
|
372
|
+
if not class_handler and cls == ModelMonitoringApplicationBase:
|
|
373
|
+
raise ValueError(
|
|
374
|
+
"You must provide a handler to the model monitoring application class"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
handler_to_class = class_handler or cls.__name__
|
|
378
|
+
handler = cls.get_job_handler(handler_to_class)
|
|
379
|
+
|
|
380
|
+
if not class_handler:
|
|
381
|
+
class_name = cls.__name__
|
|
382
|
+
else:
|
|
383
|
+
class_name = handler_to_class.split(".")[-1].split("::")[-1]
|
|
384
|
+
|
|
385
|
+
job_name = func_name if func_name else class_name
|
|
386
|
+
|
|
387
|
+
job = cast(
|
|
388
|
+
mlrun.runtimes.KubejobRuntime,
|
|
389
|
+
project.set_function(
|
|
390
|
+
func=func_path,
|
|
391
|
+
name=job_name,
|
|
392
|
+
kind=mlrun.runtimes.KubejobRuntime.kind,
|
|
393
|
+
handler=handler,
|
|
394
|
+
tag=tag,
|
|
395
|
+
image=image,
|
|
396
|
+
with_repo=with_repo,
|
|
397
|
+
requirements=requirements,
|
|
398
|
+
requirements_file=requirements_file,
|
|
399
|
+
),
|
|
400
|
+
)
|
|
401
|
+
return job
|
|
402
|
+
|
|
403
|
+
@classmethod
|
|
404
|
+
def evaluate(
|
|
405
|
+
cls,
|
|
406
|
+
func_path: Optional[str] = None,
|
|
407
|
+
func_name: Optional[str] = None,
|
|
408
|
+
*,
|
|
409
|
+
tag: Optional[str] = None,
|
|
410
|
+
run_local: bool = True,
|
|
411
|
+
auto_build: bool = True,
|
|
412
|
+
sample_data: Optional[pd.DataFrame] = None,
|
|
413
|
+
reference_data: Optional[pd.DataFrame] = None,
|
|
414
|
+
image: Optional[str] = None,
|
|
415
|
+
with_repo: Optional[bool] = False,
|
|
416
|
+
class_handler: Optional[str] = None,
|
|
417
|
+
requirements: Optional[Union[str, list[str]]] = None,
|
|
418
|
+
requirements_file: str = "",
|
|
419
|
+
endpoints: Optional[Union[list[tuple[str, str]], list[str], str]] = None,
|
|
420
|
+
start: Optional[datetime] = None,
|
|
421
|
+
end: Optional[datetime] = None,
|
|
422
|
+
base_period: Optional[int] = None,
|
|
423
|
+
) -> "mlrun.RunObject":
|
|
424
|
+
"""
|
|
425
|
+
Call this function to run the application's
|
|
426
|
+
:py:meth:`~mlrun.model_monitoring.applications.ModelMonitoringApplicationBase.do_tracking`
|
|
427
|
+
model monitoring logic as a :py:class:`~mlrun.runtimes.KubejobRuntime`, which is an MLRun function.
|
|
428
|
+
|
|
429
|
+
This function has default values for all of its arguments. You should be change them when you want to pass
|
|
430
|
+
data to the application.
|
|
431
|
+
|
|
432
|
+
:param func_path: The path to the function. If ``None``, the current notebook is used.
|
|
433
|
+
:param func_name: The name of the function. If not ``None``, the class name is used.
|
|
434
|
+
:param tag: Tag for the function.
|
|
435
|
+
:param run_local: Whether to run the function locally or remotely.
|
|
436
|
+
:param auto_build: Whether to auto build the function.
|
|
437
|
+
:param sample_data: Pandas data-frame as the current dataset.
|
|
438
|
+
When set, it replaces the data read from the model endpoint's offline source.
|
|
439
|
+
:param reference_data: Pandas data-frame of the reference dataset.
|
|
440
|
+
When set, its statistics override the model endpoint's feature statistics.
|
|
441
|
+
:param image: Docker image to run the job on (when running remotely).
|
|
442
|
+
:param with_repo: Whether to clone the current repo to the build source.
|
|
443
|
+
:param class_handler: The relative path to the class, useful when using Git sources or code from images.
|
|
444
|
+
:param requirements: List of Python requirements to be installed in the image.
|
|
445
|
+
:param requirements_file: Path to a Python requirements file to be installed in the image.
|
|
446
|
+
:param endpoints: A list of tuples of the model endpoint (name, uid) to get the data from.
|
|
447
|
+
allow providing a list of model_endpoint names or name for a single model_endpoint.
|
|
448
|
+
Note: provide names retrieves the model all the active model endpoints using those
|
|
449
|
+
names (cross function model endpoints)
|
|
450
|
+
If provided, and ``sample_data`` is not ``None``, you have to provide also the
|
|
451
|
+
``start`` and ``end`` times of the data to analyze from the model endpoints.
|
|
452
|
+
:param start: The start time of the endpoint's data, not included.
|
|
453
|
+
If you want the model endpoint's data at ``start`` included, you need to subtract a
|
|
454
|
+
small ``datetime.timedelta`` from it.
|
|
455
|
+
:param end: The end time of the endpoint's data, included.
|
|
456
|
+
Please note: when ``start`` and ``end`` are set, they create a left-open time interval
|
|
457
|
+
("window") :math:`(\\operatorname{start}, \\operatorname{end}]` that excludes the
|
|
458
|
+
endpoint's data at ``start`` and includes the data at ``end``:
|
|
459
|
+
:math:`\\operatorname{start} < t \\leq \\operatorname{end}`, :math:`t` is the time
|
|
460
|
+
taken in the window's data.
|
|
461
|
+
:param base_period: The window length in minutes. If ``None``, the whole window from ``start`` to ``end``
|
|
462
|
+
is taken. If an integer is specified, the application is run from ``start`` to ``end``
|
|
463
|
+
in ``base_period`` length windows, except for the last window that ends at ``end`` and
|
|
464
|
+
therefore may be shorter:
|
|
465
|
+
:math:`(\\operatorname{start}, \\operatorname{start} + \\operatorname{base\\_period}],
|
|
466
|
+
(\\operatorname{start} + \\operatorname{base\\_period},
|
|
467
|
+
\\operatorname{start} + 2\\cdot\\operatorname{base\\_period}],
|
|
468
|
+
..., (\\operatorname{start} +
|
|
469
|
+
m\\cdot\\operatorname{base\\_period}, \\operatorname{end}]`,
|
|
470
|
+
where :math:`m` is some positive integer.
|
|
471
|
+
|
|
472
|
+
:returns: The output of the
|
|
473
|
+
:py:meth:`~mlrun.model_monitoring.applications.ModelMonitoringApplicationBase.do_tracking`
|
|
474
|
+
method with the given parameters and inputs, wrapped in a :py:class:`~mlrun.model.RunObject`.
|
|
475
|
+
"""
|
|
476
|
+
project = cast("mlrun.MlrunProject", mlrun.get_current_project())
|
|
477
|
+
|
|
478
|
+
job = cls.to_job(
|
|
479
|
+
func_path=func_path,
|
|
480
|
+
func_name=func_name,
|
|
481
|
+
class_handler=class_handler,
|
|
482
|
+
tag=tag,
|
|
483
|
+
image=image,
|
|
484
|
+
with_repo=with_repo,
|
|
485
|
+
requirements=requirements,
|
|
486
|
+
requirements_file=requirements_file,
|
|
487
|
+
project=project,
|
|
488
|
+
)
|
|
489
|
+
|
|
490
|
+
params: dict[str, Union[list[tuple[str, str]], str, int, None]] = {}
|
|
491
|
+
if endpoints:
|
|
492
|
+
endpoints = cls._handle_endpoints_type_evaluate(
|
|
493
|
+
project=project.name,
|
|
494
|
+
endpoints=endpoints,
|
|
495
|
+
)
|
|
496
|
+
params["endpoints"] = endpoints
|
|
497
|
+
if sample_data is None:
|
|
498
|
+
if start is None or end is None:
|
|
499
|
+
raise mlrun.errors.MLRunValueError(
|
|
500
|
+
"`start` and `end` times must be provided when `endpoints` "
|
|
501
|
+
"is provided without `sample_data`"
|
|
502
|
+
)
|
|
503
|
+
params["start"] = (
|
|
504
|
+
start.isoformat() if isinstance(start, datetime) else start
|
|
505
|
+
)
|
|
506
|
+
params["end"] = end.isoformat() if isinstance(end, datetime) else end
|
|
507
|
+
params["base_period"] = base_period
|
|
508
|
+
elif start or end or base_period:
|
|
509
|
+
raise mlrun.errors.MLRunValueError(
|
|
510
|
+
"Custom `start` and `end` times or base_period are supported only with endpoints data"
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
inputs: dict[str, str] = {}
|
|
514
|
+
for data, identifier in [
|
|
515
|
+
(sample_data, "sample_data"),
|
|
516
|
+
(reference_data, "reference_data"),
|
|
517
|
+
]:
|
|
518
|
+
if data is not None:
|
|
519
|
+
key = f"{job.metadata.name}_{identifier}"
|
|
520
|
+
inputs[identifier] = project.log_dataset(
|
|
521
|
+
key,
|
|
522
|
+
data,
|
|
523
|
+
labels={
|
|
524
|
+
mlrun_constants.MLRunInternalLabels.runner_pod: socket.gethostname(),
|
|
525
|
+
mlrun_constants.MLRunInternalLabels.producer_type: "model-monitoring-job",
|
|
526
|
+
mlrun_constants.MLRunInternalLabels.app_name: func_name
|
|
527
|
+
or cls.__name__,
|
|
528
|
+
},
|
|
529
|
+
).uri
|
|
530
|
+
|
|
531
|
+
run_result = job.run(
|
|
532
|
+
local=run_local, auto_build=auto_build, params=params, inputs=inputs
|
|
533
|
+
)
|
|
534
|
+
return run_result
|
|
535
|
+
|
|
83
536
|
@abstractmethod
|
|
84
537
|
def do_tracking(
|
|
85
538
|
self,
|