mlrun 1.7.0rc48__py3-none-any.whl → 1.7.0rc52__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/common/formatters/run.py +3 -0
- mlrun/common/schemas/auth.py +3 -0
- mlrun/common/schemas/model_monitoring/constants.py +0 -7
- mlrun/common/schemas/workflow.py +9 -2
- mlrun/data_types/data_types.py +1 -1
- mlrun/db/httpdb.py +11 -4
- mlrun/execution.py +37 -6
- mlrun/feature_store/retrieval/spark_merger.py +0 -4
- mlrun/model.py +17 -0
- mlrun/model_monitoring/api.py +1 -12
- mlrun/model_monitoring/applications/__init__.py +1 -2
- mlrun/model_monitoring/applications/base.py +2 -182
- mlrun/model_monitoring/applications/context.py +2 -9
- mlrun/model_monitoring/applications/evidently_base.py +0 -74
- mlrun/model_monitoring/applications/histogram_data_drift.py +2 -2
- mlrun/model_monitoring/controller.py +45 -208
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +10 -9
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +38 -29
- mlrun/projects/operations.py +11 -8
- mlrun/projects/pipelines.py +16 -11
- mlrun/projects/project.py +1 -4
- mlrun/runtimes/nuclio/api_gateway.py +6 -0
- mlrun/utils/helpers.py +40 -0
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.7.0rc48.dist-info → mlrun-1.7.0rc52.dist-info}/METADATA +107 -25
- {mlrun-1.7.0rc48.dist-info → mlrun-1.7.0rc52.dist-info}/RECORD +30 -32
- mlrun/model_monitoring/application.py +0 -19
- mlrun/model_monitoring/evidently_application.py +0 -20
- {mlrun-1.7.0rc48.dist-info → mlrun-1.7.0rc52.dist-info}/LICENSE +0 -0
- {mlrun-1.7.0rc48.dist-info → mlrun-1.7.0rc52.dist-info}/WHEEL +0 -0
- {mlrun-1.7.0rc48.dist-info → mlrun-1.7.0rc52.dist-info}/entry_points.txt +0 -0
- {mlrun-1.7.0rc48.dist-info → mlrun-1.7.0rc52.dist-info}/top_level.txt +0 -0
mlrun/common/formatters/run.py
CHANGED
|
@@ -22,5 +22,8 @@ class RunFormat(ObjectFormat, mlrun.common.types.StrEnum):
|
|
|
22
22
|
# No enrichment, data is pulled as-is from the database.
|
|
23
23
|
standard = "standard"
|
|
24
24
|
|
|
25
|
+
# Enrich run with full notifications since the notification params are subtracted from the run body.
|
|
26
|
+
notifications = "notifications"
|
|
27
|
+
|
|
25
28
|
# Performs run enrichment, including the run's artifacts. Only available for the `get` run API.
|
|
26
29
|
full = "full"
|
mlrun/common/schemas/auth.py
CHANGED
|
@@ -141,6 +141,9 @@ class AuthInfo(pydantic.BaseModel):
|
|
|
141
141
|
member_ids.extend(self.user_group_ids)
|
|
142
142
|
return member_ids
|
|
143
143
|
|
|
144
|
+
def get_session(self) -> str:
|
|
145
|
+
return self.data_session or self.session
|
|
146
|
+
|
|
144
147
|
|
|
145
148
|
class Credentials(pydantic.BaseModel):
|
|
146
149
|
access_key: typing.Optional[str]
|
|
@@ -105,15 +105,8 @@ class ApplicationEvent:
|
|
|
105
105
|
APPLICATION_NAME = "application_name"
|
|
106
106
|
START_INFER_TIME = "start_infer_time"
|
|
107
107
|
END_INFER_TIME = "end_infer_time"
|
|
108
|
-
LAST_REQUEST = "last_request"
|
|
109
108
|
ENDPOINT_ID = "endpoint_id"
|
|
110
109
|
OUTPUT_STREAM_URI = "output_stream_uri"
|
|
111
|
-
MLRUN_CONTEXT = "mlrun_context"
|
|
112
|
-
|
|
113
|
-
# Deprecated fields - TODO : delete in 1.9.0 (V1 app deprecation)
|
|
114
|
-
SAMPLE_PARQUET_PATH = "sample_parquet_path"
|
|
115
|
-
CURRENT_STATS = "current_stats"
|
|
116
|
-
FEATURE_STATS = "feature_stats"
|
|
117
110
|
|
|
118
111
|
|
|
119
112
|
class WriterEvent(MonitoringStrEnum):
|
mlrun/common/schemas/workflow.py
CHANGED
|
@@ -16,8 +16,9 @@ import typing
|
|
|
16
16
|
|
|
17
17
|
import pydantic
|
|
18
18
|
|
|
19
|
-
from .notification import Notification
|
|
20
|
-
from .schedule import ScheduleCronTrigger
|
|
19
|
+
from mlrun.common.schemas.notification import Notification
|
|
20
|
+
from mlrun.common.schemas.schedule import ScheduleCronTrigger
|
|
21
|
+
from mlrun.common.types import StrEnum
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class WorkflowSpec(pydantic.BaseModel):
|
|
@@ -55,3 +56,9 @@ class WorkflowResponse(pydantic.BaseModel):
|
|
|
55
56
|
|
|
56
57
|
class GetWorkflowResponse(pydantic.BaseModel):
|
|
57
58
|
workflow_id: str = None
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class EngineType(StrEnum):
|
|
62
|
+
LOCAL = "local"
|
|
63
|
+
REMOTE = "remote"
|
|
64
|
+
KFP = "kfp"
|
mlrun/data_types/data_types.py
CHANGED
mlrun/db/httpdb.py
CHANGED
|
@@ -2754,7 +2754,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
2754
2754
|
deletion_strategy: Union[
|
|
2755
2755
|
str, mlrun.common.schemas.DeletionStrategy
|
|
2756
2756
|
] = mlrun.common.schemas.DeletionStrategy.default(),
|
|
2757
|
-
):
|
|
2757
|
+
) -> None:
|
|
2758
2758
|
"""Delete a project.
|
|
2759
2759
|
|
|
2760
2760
|
:param name: Name of the project to delete.
|
|
@@ -2773,7 +2773,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
2773
2773
|
"DELETE", f"projects/{name}", error_message, headers=headers, version="v2"
|
|
2774
2774
|
)
|
|
2775
2775
|
if response.status_code == http.HTTPStatus.ACCEPTED:
|
|
2776
|
-
logger.info("
|
|
2776
|
+
logger.info("Waiting for project to be deleted", project_name=name)
|
|
2777
2777
|
background_task = mlrun.common.schemas.BackgroundTask(**response.json())
|
|
2778
2778
|
background_task = self._wait_for_background_task_to_reach_terminal_state(
|
|
2779
2779
|
background_task.metadata.name
|
|
@@ -2783,10 +2783,17 @@ class HTTPRunDB(RunDBInterface):
|
|
|
2783
2783
|
== mlrun.common.schemas.BackgroundTaskState.succeeded
|
|
2784
2784
|
):
|
|
2785
2785
|
logger.info("Project deleted", project_name=name)
|
|
2786
|
-
|
|
2786
|
+
elif (
|
|
2787
|
+
background_task.status.state
|
|
2788
|
+
== mlrun.common.schemas.BackgroundTaskState.failed
|
|
2789
|
+
):
|
|
2790
|
+
logger.error(
|
|
2791
|
+
"Project deletion failed",
|
|
2792
|
+
project_name=name,
|
|
2793
|
+
error=background_task.status.error,
|
|
2794
|
+
)
|
|
2787
2795
|
elif response.status_code == http.HTTPStatus.NO_CONTENT:
|
|
2788
2796
|
logger.info("Project deleted", project_name=name)
|
|
2789
|
-
return
|
|
2790
2797
|
|
|
2791
2798
|
def store_project(
|
|
2792
2799
|
self,
|
mlrun/execution.py
CHANGED
|
@@ -24,6 +24,7 @@ from dateutil import parser
|
|
|
24
24
|
|
|
25
25
|
import mlrun
|
|
26
26
|
import mlrun.common.constants as mlrun_constants
|
|
27
|
+
import mlrun.common.formatters
|
|
27
28
|
from mlrun.artifacts import ModelArtifact
|
|
28
29
|
from mlrun.datastore.store_resources import get_store_resource
|
|
29
30
|
from mlrun.errors import MLRunInvalidArgumentError
|
|
@@ -926,12 +927,42 @@ class MLClientCtx:
|
|
|
926
927
|
updates, self._uid, self.project, iter=self._iteration
|
|
927
928
|
)
|
|
928
929
|
|
|
929
|
-
def get_notifications(self):
|
|
930
|
-
"""
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
930
|
+
def get_notifications(self, unmask_secret_params=False):
|
|
931
|
+
"""
|
|
932
|
+
Get the list of notifications
|
|
933
|
+
|
|
934
|
+
:param unmask_secret_params: Used as a workaround for sending notification from workflow-runner.
|
|
935
|
+
When used, if the notification will be saved again a new secret will be created.
|
|
936
|
+
"""
|
|
937
|
+
|
|
938
|
+
# Get the full notifications from the DB since the run context does not contain the params due to bloating
|
|
939
|
+
run = self._rundb.read_run(
|
|
940
|
+
self.uid, format_=mlrun.common.formatters.RunFormat.notifications
|
|
941
|
+
)
|
|
942
|
+
|
|
943
|
+
notifications = []
|
|
944
|
+
for notification in run["spec"]["notifications"]:
|
|
945
|
+
notification: mlrun.model.Notification = mlrun.model.Notification.from_dict(
|
|
946
|
+
notification
|
|
947
|
+
)
|
|
948
|
+
# Fill the secret params from the project secret. We cannot use the server side internal secret mechanism
|
|
949
|
+
# here as it is the client side.
|
|
950
|
+
# TODO: This is a workaround to allow the notification to get the secret params from project secret
|
|
951
|
+
# instead of getting them from the internal project secret that should be mounted.
|
|
952
|
+
# We should mount the internal project secret that was created to the workflow-runner
|
|
953
|
+
# and get the secret from there.
|
|
954
|
+
if unmask_secret_params:
|
|
955
|
+
try:
|
|
956
|
+
notification.enrich_unmasked_secret_params_from_project_secret()
|
|
957
|
+
notifications.append(notification)
|
|
958
|
+
except mlrun.errors.MLRunValueError:
|
|
959
|
+
logger.warning(
|
|
960
|
+
"Failed to fill secret params from project secret for notification."
|
|
961
|
+
"Skip this notification.",
|
|
962
|
+
notification=notification.name,
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
return notifications
|
|
935
966
|
|
|
936
967
|
def to_dict(self):
|
|
937
968
|
"""Convert the run context to a dictionary"""
|
|
@@ -206,10 +206,6 @@ class SparkFeatureMerger(BaseMerger):
|
|
|
206
206
|
time_column=None,
|
|
207
207
|
additional_filters=None,
|
|
208
208
|
):
|
|
209
|
-
mlrun.utils.helpers.additional_filters_warning(
|
|
210
|
-
additional_filters, self.__class__
|
|
211
|
-
)
|
|
212
|
-
|
|
213
209
|
source_kwargs = {}
|
|
214
210
|
if feature_set.spec.passthrough:
|
|
215
211
|
if not feature_set.spec.source:
|
mlrun/model.py
CHANGED
|
@@ -774,6 +774,23 @@ class Notification(ModelObj):
|
|
|
774
774
|
|
|
775
775
|
notification_class.validate_params(secret_params | params)
|
|
776
776
|
|
|
777
|
+
def enrich_unmasked_secret_params_from_project_secret(self):
|
|
778
|
+
"""
|
|
779
|
+
Fill the notification secret params from the project secret.
|
|
780
|
+
We are using this function instead of unmask_secret_params_from_project_secret when we run inside the
|
|
781
|
+
workflow runner pod that doesn't have access to the k8s secrets (but have access to the project secret)
|
|
782
|
+
"""
|
|
783
|
+
secret = self.secret_params.get("secret")
|
|
784
|
+
if secret:
|
|
785
|
+
secret_value = mlrun.get_secret_or_env(secret)
|
|
786
|
+
if secret_value:
|
|
787
|
+
try:
|
|
788
|
+
self.secret_params = json.loads(secret_value)
|
|
789
|
+
except ValueError as exc:
|
|
790
|
+
raise mlrun.errors.MLRunValueError(
|
|
791
|
+
"Failed to parse secret value"
|
|
792
|
+
) from exc
|
|
793
|
+
|
|
777
794
|
@staticmethod
|
|
778
795
|
def validate_notification_uniqueness(notifications: list["Notification"]):
|
|
779
796
|
"""Validate that all notifications in the list are unique by name"""
|
mlrun/model_monitoring/api.py
CHANGED
|
@@ -24,7 +24,6 @@ import mlrun.artifacts
|
|
|
24
24
|
import mlrun.common.helpers
|
|
25
25
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
26
26
|
import mlrun.feature_store
|
|
27
|
-
import mlrun.model_monitoring.application
|
|
28
27
|
import mlrun.model_monitoring.applications as mm_app
|
|
29
28
|
import mlrun.serving
|
|
30
29
|
from mlrun.data_types.infer import InferOptions, get_df_stats
|
|
@@ -561,8 +560,7 @@ def _create_model_monitoring_function_base(
|
|
|
561
560
|
func: typing.Union[str, None] = None,
|
|
562
561
|
application_class: typing.Union[
|
|
563
562
|
str,
|
|
564
|
-
|
|
565
|
-
mm_app.ModelMonitoringApplicationBaseV2,
|
|
563
|
+
mm_app.ModelMonitoringApplicationBase,
|
|
566
564
|
None,
|
|
567
565
|
] = None,
|
|
568
566
|
name: typing.Optional[str] = None,
|
|
@@ -576,15 +574,6 @@ def _create_model_monitoring_function_base(
|
|
|
576
574
|
Note: this is an internal API only.
|
|
577
575
|
This function does not set the labels or mounts v3io.
|
|
578
576
|
"""
|
|
579
|
-
if isinstance(
|
|
580
|
-
application_class,
|
|
581
|
-
mlrun.model_monitoring.application.ModelMonitoringApplicationBase,
|
|
582
|
-
):
|
|
583
|
-
warnings.warn(
|
|
584
|
-
"The `ModelMonitoringApplicationBase` class is deprecated from version 1.7.0, "
|
|
585
|
-
"please use `ModelMonitoringApplicationBaseV2`. It will be removed in 1.9.0.",
|
|
586
|
-
FutureWarning,
|
|
587
|
-
)
|
|
588
577
|
if name in mm_constants._RESERVED_FUNCTION_NAMES:
|
|
589
578
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
590
579
|
"An application cannot have the following names: "
|
|
@@ -13,12 +13,11 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
#
|
|
15
15
|
|
|
16
|
-
from .base import ModelMonitoringApplicationBase
|
|
16
|
+
from .base import ModelMonitoringApplicationBase
|
|
17
17
|
from .context import MonitoringApplicationContext
|
|
18
18
|
from .evidently_base import (
|
|
19
19
|
_HAS_EVIDENTLY,
|
|
20
20
|
SUPPORTED_EVIDENTLY_VERSION,
|
|
21
21
|
EvidentlyModelMonitoringApplicationBase,
|
|
22
|
-
EvidentlyModelMonitoringApplicationBaseV2,
|
|
23
22
|
)
|
|
24
23
|
from .results import ModelMonitoringApplicationMetric, ModelMonitoringApplicationResult
|
|
@@ -13,19 +13,14 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
15
|
from abc import ABC, abstractmethod
|
|
16
|
-
from typing import Any, Union
|
|
16
|
+
from typing import Any, Union
|
|
17
17
|
|
|
18
|
-
import numpy as np
|
|
19
|
-
import pandas as pd
|
|
20
|
-
from deprecated import deprecated
|
|
21
|
-
|
|
22
|
-
import mlrun
|
|
23
18
|
import mlrun.model_monitoring.applications.context as mm_context
|
|
24
19
|
import mlrun.model_monitoring.applications.results as mm_results
|
|
25
20
|
from mlrun.serving.utils import MonitoringApplicationToDict
|
|
26
21
|
|
|
27
22
|
|
|
28
|
-
class
|
|
23
|
+
class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
29
24
|
"""
|
|
30
25
|
A base class for a model monitoring application.
|
|
31
26
|
Inherit from this class to create a custom model monitoring application.
|
|
@@ -111,178 +106,3 @@ class ModelMonitoringApplicationBaseV2(MonitoringApplicationToDict, ABC):
|
|
|
111
106
|
each metric name is the key and the metric value is the corresponding value).
|
|
112
107
|
"""
|
|
113
108
|
raise NotImplementedError
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
# TODO: Remove in 1.9.0
|
|
117
|
-
@deprecated(
|
|
118
|
-
version="1.7.0",
|
|
119
|
-
reason="The `ModelMonitoringApplicationBase` class is deprecated from "
|
|
120
|
-
"version 1.7.0 and will be removed in version 1.9.0. "
|
|
121
|
-
"Use `ModelMonitoringApplicationBaseV2` as your application's base class.",
|
|
122
|
-
)
|
|
123
|
-
class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
124
|
-
"""
|
|
125
|
-
A base class for a model monitoring application.
|
|
126
|
-
Inherit from this class to create a custom model monitoring application.
|
|
127
|
-
|
|
128
|
-
example for very simple custom application::
|
|
129
|
-
|
|
130
|
-
class MyApp(ApplicationBase):
|
|
131
|
-
def do_tracking(
|
|
132
|
-
self,
|
|
133
|
-
sample_df_stats: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
134
|
-
feature_stats: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
135
|
-
start_infer_time: pd.Timestamp,
|
|
136
|
-
end_infer_time: pd.Timestamp,
|
|
137
|
-
schedule_time: pd.Timestamp,
|
|
138
|
-
latest_request: pd.Timestamp,
|
|
139
|
-
endpoint_id: str,
|
|
140
|
-
output_stream_uri: str,
|
|
141
|
-
) -> ModelMonitoringApplicationResult:
|
|
142
|
-
self.context.log_artifact(
|
|
143
|
-
TableArtifact(
|
|
144
|
-
"sample_df_stats", df=self.dict_to_histogram(sample_df_stats)
|
|
145
|
-
)
|
|
146
|
-
)
|
|
147
|
-
return ModelMonitoringApplicationResult(
|
|
148
|
-
name="data_drift_test",
|
|
149
|
-
value=0.5,
|
|
150
|
-
kind=mm_constant.ResultKindApp.data_drift,
|
|
151
|
-
status=mm_constant.ResultStatusApp.detected,
|
|
152
|
-
)
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
"""
|
|
156
|
-
|
|
157
|
-
kind = "monitoring_application"
|
|
158
|
-
|
|
159
|
-
def do(
|
|
160
|
-
self, monitoring_context: mm_context.MonitoringApplicationContext
|
|
161
|
-
) -> tuple[
|
|
162
|
-
list[mm_results.ModelMonitoringApplicationResult],
|
|
163
|
-
mm_context.MonitoringApplicationContext,
|
|
164
|
-
]:
|
|
165
|
-
"""
|
|
166
|
-
Process the monitoring event and return application results.
|
|
167
|
-
|
|
168
|
-
:param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
|
|
169
|
-
:returns: A tuple of:
|
|
170
|
-
[0] = list of application results that can be either from type
|
|
171
|
-
`ModelMonitoringApplicationResult` or from type
|
|
172
|
-
`ModelMonitoringApplicationResult`.
|
|
173
|
-
[1] = the original application event, wrapped in `MonitoringApplicationContext`
|
|
174
|
-
object
|
|
175
|
-
"""
|
|
176
|
-
resolved_event = self._resolve_event(monitoring_context)
|
|
177
|
-
if not (
|
|
178
|
-
hasattr(self, "context") and isinstance(self.context, mlrun.MLClientCtx)
|
|
179
|
-
):
|
|
180
|
-
self._lazy_init(monitoring_context)
|
|
181
|
-
results = self.do_tracking(*resolved_event)
|
|
182
|
-
results = results if isinstance(results, list) else [results]
|
|
183
|
-
return results, monitoring_context
|
|
184
|
-
|
|
185
|
-
def _lazy_init(self, monitoring_context: mm_context.MonitoringApplicationContext):
|
|
186
|
-
self.context = cast(mlrun.MLClientCtx, monitoring_context)
|
|
187
|
-
|
|
188
|
-
@abstractmethod
|
|
189
|
-
def do_tracking(
|
|
190
|
-
self,
|
|
191
|
-
application_name: str,
|
|
192
|
-
sample_df_stats: pd.DataFrame,
|
|
193
|
-
feature_stats: pd.DataFrame,
|
|
194
|
-
sample_df: pd.DataFrame,
|
|
195
|
-
start_infer_time: pd.Timestamp,
|
|
196
|
-
end_infer_time: pd.Timestamp,
|
|
197
|
-
latest_request: pd.Timestamp,
|
|
198
|
-
endpoint_id: str,
|
|
199
|
-
output_stream_uri: str,
|
|
200
|
-
) -> Union[
|
|
201
|
-
mm_results.ModelMonitoringApplicationResult,
|
|
202
|
-
list[mm_results.ModelMonitoringApplicationResult],
|
|
203
|
-
]:
|
|
204
|
-
"""
|
|
205
|
-
Implement this method with your custom monitoring logic.
|
|
206
|
-
|
|
207
|
-
:param application_name: (str) the app name
|
|
208
|
-
:param sample_df_stats: (pd.DataFrame) The new sample distribution.
|
|
209
|
-
:param feature_stats: (pd.DataFrame) The train sample distribution.
|
|
210
|
-
:param sample_df: (pd.DataFrame) The new sample DataFrame.
|
|
211
|
-
:param start_infer_time: (pd.Timestamp) Start time of the monitoring schedule.
|
|
212
|
-
:param end_infer_time: (pd.Timestamp) End time of the monitoring schedule.
|
|
213
|
-
:param latest_request: (pd.Timestamp) Timestamp of the latest request on this endpoint_id.
|
|
214
|
-
:param endpoint_id: (str) ID of the monitored model endpoint
|
|
215
|
-
:param output_stream_uri: (str) URI of the output stream for results
|
|
216
|
-
|
|
217
|
-
:returns: (ModelMonitoringApplicationResult) or
|
|
218
|
-
(list[ModelMonitoringApplicationResult]) of the application results.
|
|
219
|
-
"""
|
|
220
|
-
raise NotImplementedError
|
|
221
|
-
|
|
222
|
-
@classmethod
|
|
223
|
-
def _resolve_event(
|
|
224
|
-
cls,
|
|
225
|
-
monitoring_context: mm_context.MonitoringApplicationContext,
|
|
226
|
-
) -> tuple[
|
|
227
|
-
str,
|
|
228
|
-
pd.DataFrame,
|
|
229
|
-
pd.DataFrame,
|
|
230
|
-
pd.DataFrame,
|
|
231
|
-
pd.Timestamp,
|
|
232
|
-
pd.Timestamp,
|
|
233
|
-
pd.Timestamp,
|
|
234
|
-
str,
|
|
235
|
-
str,
|
|
236
|
-
]:
|
|
237
|
-
"""
|
|
238
|
-
Converting the event into a single tuple that will be used for passing the event arguments to the running
|
|
239
|
-
application
|
|
240
|
-
|
|
241
|
-
:param monitoring_context: (MonitoringApplicationContext) The monitoring context to process.
|
|
242
|
-
|
|
243
|
-
:return: A tuple of:
|
|
244
|
-
[0] = (str) application name
|
|
245
|
-
[1] = (pd.DataFrame) current input statistics
|
|
246
|
-
[2] = (pd.DataFrame) train statistics
|
|
247
|
-
[3] = (pd.DataFrame) current input data
|
|
248
|
-
[4] = (pd.Timestamp) start time of the monitoring schedule
|
|
249
|
-
[5] = (pd.Timestamp) end time of the monitoring schedule
|
|
250
|
-
[6] = (pd.Timestamp) timestamp of the latest request
|
|
251
|
-
[7] = (str) endpoint id
|
|
252
|
-
[8] = (str) output stream uri
|
|
253
|
-
"""
|
|
254
|
-
return (
|
|
255
|
-
monitoring_context.application_name,
|
|
256
|
-
cls.dict_to_histogram(monitoring_context.sample_df_stats),
|
|
257
|
-
cls.dict_to_histogram(monitoring_context.feature_stats),
|
|
258
|
-
monitoring_context.sample_df,
|
|
259
|
-
monitoring_context.start_infer_time,
|
|
260
|
-
monitoring_context.end_infer_time,
|
|
261
|
-
monitoring_context.latest_request,
|
|
262
|
-
monitoring_context.endpoint_id,
|
|
263
|
-
monitoring_context.output_stream_uri,
|
|
264
|
-
)
|
|
265
|
-
|
|
266
|
-
@staticmethod
|
|
267
|
-
def dict_to_histogram(
|
|
268
|
-
histogram_dict: mlrun.common.model_monitoring.helpers.FeatureStats,
|
|
269
|
-
) -> pd.DataFrame:
|
|
270
|
-
"""
|
|
271
|
-
Convert histogram dictionary to pandas DataFrame with feature histograms as columns
|
|
272
|
-
|
|
273
|
-
:param histogram_dict: Histogram dictionary
|
|
274
|
-
|
|
275
|
-
:returns: Histogram dataframe
|
|
276
|
-
"""
|
|
277
|
-
|
|
278
|
-
# Create a dictionary with feature histograms as values
|
|
279
|
-
histograms = {}
|
|
280
|
-
for feature, stats in histogram_dict.items():
|
|
281
|
-
if "hist" in stats:
|
|
282
|
-
# Normalize to probability distribution of each feature
|
|
283
|
-
histograms[feature] = np.array(stats["hist"][0]) / stats["count"]
|
|
284
|
-
|
|
285
|
-
# Convert the dictionary to pandas DataFrame
|
|
286
|
-
histograms = pd.DataFrame(histograms)
|
|
287
|
-
|
|
288
|
-
return histograms
|
|
@@ -98,9 +98,6 @@ class MonitoringApplicationContext:
|
|
|
98
98
|
self.end_infer_time = pd.Timestamp(
|
|
99
99
|
cast(str, event.get(mm_constants.ApplicationEvent.END_INFER_TIME))
|
|
100
100
|
)
|
|
101
|
-
self.latest_request = pd.Timestamp(
|
|
102
|
-
cast(str, event.get(mm_constants.ApplicationEvent.LAST_REQUEST))
|
|
103
|
-
)
|
|
104
101
|
self.endpoint_id = cast(
|
|
105
102
|
str, event.get(mm_constants.ApplicationEvent.ENDPOINT_ID)
|
|
106
103
|
)
|
|
@@ -108,12 +105,8 @@ class MonitoringApplicationContext:
|
|
|
108
105
|
str, event.get(mm_constants.ApplicationEvent.OUTPUT_STREAM_URI)
|
|
109
106
|
)
|
|
110
107
|
|
|
111
|
-
self._feature_stats: Optional[FeatureStats] =
|
|
112
|
-
|
|
113
|
-
)
|
|
114
|
-
self._sample_df_stats: Optional[FeatureStats] = json.loads(
|
|
115
|
-
event.get(mm_constants.ApplicationEvent.CURRENT_STATS, "{}")
|
|
116
|
-
)
|
|
108
|
+
self._feature_stats: Optional[FeatureStats] = None
|
|
109
|
+
self._sample_df_stats: Optional[FeatureStats] = None
|
|
117
110
|
|
|
118
111
|
# Default labels for the artifacts
|
|
119
112
|
self._default_labels = self._get_default_labels()
|
|
@@ -18,7 +18,6 @@ from abc import ABC
|
|
|
18
18
|
|
|
19
19
|
import pandas as pd
|
|
20
20
|
import semver
|
|
21
|
-
from deprecated import deprecated
|
|
22
21
|
|
|
23
22
|
import mlrun.model_monitoring.applications.base as mm_base
|
|
24
23
|
import mlrun.model_monitoring.applications.context as mm_context
|
|
@@ -64,13 +63,6 @@ if _HAS_EVIDENTLY:
|
|
|
64
63
|
from evidently.utils.dashboard import TemplateParams, file_html_template
|
|
65
64
|
|
|
66
65
|
|
|
67
|
-
# TODO: Remove in 1.9.0
|
|
68
|
-
@deprecated(
|
|
69
|
-
version="1.7.0",
|
|
70
|
-
reason="The `EvidentlyModelMonitoringApplicationBase` class is deprecated from "
|
|
71
|
-
"version 1.7.0 and will be removed in version 1.9.0. "
|
|
72
|
-
"Use `EvidentlyModelMonitoringApplicationBaseV2` as your application's base class.",
|
|
73
|
-
)
|
|
74
66
|
class EvidentlyModelMonitoringApplicationBase(
|
|
75
67
|
mm_base.ModelMonitoringApplicationBase, ABC
|
|
76
68
|
):
|
|
@@ -85,72 +77,6 @@ class EvidentlyModelMonitoringApplicationBase(
|
|
|
85
77
|
:param evidently_workspace_path: (str) The path to the Evidently workspace.
|
|
86
78
|
:param evidently_project_id: (str) The ID of the Evidently project.
|
|
87
79
|
|
|
88
|
-
"""
|
|
89
|
-
if not _HAS_EVIDENTLY:
|
|
90
|
-
raise ModuleNotFoundError("Evidently is not installed - the app cannot run")
|
|
91
|
-
self.evidently_workspace = Workspace.create(evidently_workspace_path)
|
|
92
|
-
self.evidently_project_id = evidently_project_id
|
|
93
|
-
self.evidently_project = self.evidently_workspace.get_project(
|
|
94
|
-
evidently_project_id
|
|
95
|
-
)
|
|
96
|
-
|
|
97
|
-
def log_evidently_object(
|
|
98
|
-
self, evidently_object: "Display", artifact_name: str
|
|
99
|
-
) -> None:
|
|
100
|
-
"""
|
|
101
|
-
Logs an Evidently report or suite as an artifact.
|
|
102
|
-
|
|
103
|
-
:param evidently_object: (Display) The Evidently display to log, e.g. a report or a test suite object.
|
|
104
|
-
:param artifact_name: (str) The name for the logged artifact.
|
|
105
|
-
"""
|
|
106
|
-
evidently_object_html = evidently_object.get_html()
|
|
107
|
-
self.context.log_artifact(
|
|
108
|
-
artifact_name, body=evidently_object_html.encode("utf-8"), format="html"
|
|
109
|
-
)
|
|
110
|
-
|
|
111
|
-
def log_project_dashboard(
|
|
112
|
-
self,
|
|
113
|
-
timestamp_start: pd.Timestamp,
|
|
114
|
-
timestamp_end: pd.Timestamp,
|
|
115
|
-
artifact_name: str = "dashboard",
|
|
116
|
-
):
|
|
117
|
-
"""
|
|
118
|
-
Logs an Evidently project dashboard.
|
|
119
|
-
|
|
120
|
-
:param timestamp_start: (pd.Timestamp) The start timestamp for the dashboard data.
|
|
121
|
-
:param timestamp_end: (pd.Timestamp) The end timestamp for the dashboard data.
|
|
122
|
-
:param artifact_name: (str) The name for the logged artifact.
|
|
123
|
-
"""
|
|
124
|
-
|
|
125
|
-
dashboard_info = self.evidently_project.build_dashboard_info(
|
|
126
|
-
timestamp_start, timestamp_end
|
|
127
|
-
)
|
|
128
|
-
template_params = TemplateParams(
|
|
129
|
-
dashboard_id="pd_" + str(uuid.uuid4()).replace("-", ""),
|
|
130
|
-
dashboard_info=dashboard_info,
|
|
131
|
-
additional_graphs={},
|
|
132
|
-
)
|
|
133
|
-
|
|
134
|
-
dashboard_html = file_html_template(params=template_params)
|
|
135
|
-
self.context.log_artifact(
|
|
136
|
-
artifact_name, body=dashboard_html.encode("utf-8"), format="html"
|
|
137
|
-
)
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
class EvidentlyModelMonitoringApplicationBaseV2(
|
|
141
|
-
mm_base.ModelMonitoringApplicationBaseV2, ABC
|
|
142
|
-
):
|
|
143
|
-
def __init__(
|
|
144
|
-
self, evidently_workspace_path: str, evidently_project_id: "STR_UUID"
|
|
145
|
-
) -> None:
|
|
146
|
-
"""
|
|
147
|
-
A class for integrating Evidently for mlrun model monitoring within a monitoring application.
|
|
148
|
-
Note: evidently is not installed by default in the mlrun/mlrun image.
|
|
149
|
-
It must be installed separately to use this class.
|
|
150
|
-
|
|
151
|
-
:param evidently_workspace_path: (str) The path to the Evidently workspace.
|
|
152
|
-
:param evidently_project_id: (str) The ID of the Evidently project.
|
|
153
|
-
|
|
154
80
|
"""
|
|
155
81
|
|
|
156
82
|
# TODO : more then one project (mep -> project)
|
|
@@ -31,7 +31,7 @@ from mlrun.common.schemas.model_monitoring.constants import (
|
|
|
31
31
|
ResultStatusApp,
|
|
32
32
|
)
|
|
33
33
|
from mlrun.model_monitoring.applications import (
|
|
34
|
-
|
|
34
|
+
ModelMonitoringApplicationBase,
|
|
35
35
|
)
|
|
36
36
|
from mlrun.model_monitoring.metrics.histogram_distance import (
|
|
37
37
|
HellingerDistance,
|
|
@@ -87,7 +87,7 @@ class DataDriftClassifier:
|
|
|
87
87
|
return ResultStatusApp.no_detection
|
|
88
88
|
|
|
89
89
|
|
|
90
|
-
class HistogramDataDriftApplication(
|
|
90
|
+
class HistogramDataDriftApplication(ModelMonitoringApplicationBase):
|
|
91
91
|
"""
|
|
92
92
|
MLRun's default data drift application for model monitoring.
|
|
93
93
|
|