Source code for mlrun.model_monitoring.applications.evidently.base

# Copyright 2023 Iguazio
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import warnings
from abc import ABC
from tempfile import NamedTemporaryFile
from typing import Optional

import semver

import mlrun.model_monitoring.applications.base as mm_base
import mlrun.model_monitoring.applications.context as mm_context
from mlrun.errors import MLRunIncompatibleVersionError, MLRunValueError

SUPPORTED_EVIDENTLY_VERSION = semver.Version.parse("0.7.5")


def _check_evidently_version(*, cur: semver.Version, ref: semver.Version) -> None:
    if ref.is_compatible(cur) or (
        cur.major == ref.major == 0 and cur.minor == ref.minor and cur.patch > ref.patch
    ):
        return
    if cur.major == ref.major == 0 and cur.minor > ref.minor:
        warnings.warn(
            f"Evidently version {cur} is not compatible with the tested "
            f"version {ref}, use at your own risk."
        )
    else:
        raise MLRunIncompatibleVersionError(
            f"Evidently version {cur} is not supported, please change to "
            f"{ref} (or another compatible version)."
        )


_HAS_EVIDENTLY = False
try:
    import evidently  # noqa: F401

    _check_evidently_version(
        cur=semver.Version.parse(evidently.__version__),
        ref=SUPPORTED_EVIDENTLY_VERSION,
    )
    _HAS_EVIDENTLY = True
except ModuleNotFoundError:
    pass


if _HAS_EVIDENTLY:
    from evidently.core.report import Snapshot
    from evidently.ui.workspace import (
        STR_UUID,
        CloudWorkspace,
        Project,
        Workspace,
        WorkspaceBase,
    )


[docs] class EvidentlyModelMonitoringApplicationBase( mm_base.ModelMonitoringApplicationBase, ABC ): def __init__( self, evidently_project_id: "STR_UUID", evidently_workspace_path: Optional[str] = None, cloud_workspace: bool = False, ) -> None: """ A class for integrating Evidently for MLRun model monitoring within a monitoring application. .. note:: The ``evidently`` package is not installed by default in the mlrun/mlrun image. It must be installed separately to use this class. :param evidently_project_id: (str) The ID of the Evidently project. :param evidently_workspace_path: (str) The path to the Evidently workspace. :param cloud_workspace: (bool) Whether the workspace is an Evidently Cloud workspace. """ if not _HAS_EVIDENTLY: raise ModuleNotFoundError("Evidently is not installed - the app cannot run") self.evidently_workspace_path = evidently_workspace_path if cloud_workspace: self.get_workspace = self.get_cloud_workspace self.evidently_workspace = self.get_workspace() self.evidently_project_id = evidently_project_id self.evidently_project = self.load_project()
[docs] def load_project(self) -> "Project": """Load the Evidently project.""" return self.evidently_workspace.get_project(self.evidently_project_id)
[docs] def get_workspace(self) -> "WorkspaceBase": """Get the Evidently workspace. Override this method for customize access to the workspace.""" if self.evidently_workspace_path: return Workspace.create(self.evidently_workspace_path) else: raise MLRunValueError( "A local workspace could not be created as `evidently_workspace_path` is not set.\n" "If you intend to use a cloud workspace, please use `cloud_workspace=True` and set the " "`EVIDENTLY_API_KEY` environment variable. In other cases, override this method." )
[docs] def get_cloud_workspace(self) -> "CloudWorkspace": """Load the Evidently cloud workspace according to the `EVIDENTLY_API_KEY` environment variable.""" return CloudWorkspace()
[docs] @staticmethod def log_evidently_object( monitoring_context: mm_context.MonitoringApplicationContext, evidently_object: "Snapshot", artifact_name: str, unique_per_endpoint: bool = True, ) -> None: """ Logs an Evidently report or suite as an artifact. .. caution:: Logging Evidently objects in every model monitoring window may cause scale issues. This method should be called on special occasions only. :param monitoring_context: (MonitoringApplicationContext) The monitoring context to process. :param evidently_object: (Snapshot) The Evidently run to log, e.g. a report run. :param artifact_name: (str) The name for the logged artifact. :param unique_per_endpoint: by default ``True``, we will log different artifact for each model endpoint, set to ``False`` without changing item key will cause artifact override. """ with NamedTemporaryFile(suffix=".html") as file: evidently_object.save_html(filename=file.name) monitoring_context.log_artifact( artifact_name, local_path=file.name, unique_per_endpoint=unique_per_endpoint, )