From 8eb26ebf615bb87ff86c210f66487f32f2be1d05 Mon Sep 17 00:00:00 2001 From: Alibi Zhenis <92104549+AlibiZhenis@users.noreply.github.com> Date: Thu, 15 Jun 2023 07:53:48 +0800 Subject: [PATCH] adding executeAPI (#165) * adding executeAPI Signed-off-by: Alibi Zhenis * updating documentation Signed-off-by: Alibi Zhenis * changing code and test Signed-off-by: Alibi Zhenis * changing code Signed-off-by: Alibi Zhenis * fixing tests Signed-off-by: Alibi Zhenis * changing tests Signed-off-by: Alibi Zhenis * checking output value in integration tests Signed-off-by: Alibi Zhenis --------- Signed-off-by: Alibi Zhenis --- .../reference/api/ml_commons_execute_api.rst | 6 +++ docs/source/reference/mlcommons.rst | 6 +++ .../ml_commons/ml_commons_client.py | 17 +++++++ opensearch_py_ml/ml_commons/model_execute.py | 48 +++++++++++++++++++ tests/ml_commons/test_ml_commons_client.py | 31 ++++++++++++ 5 files changed, 108 insertions(+) create mode 100644 docs/source/reference/api/ml_commons_execute_api.rst create mode 100644 opensearch_py_ml/ml_commons/model_execute.py diff --git a/docs/source/reference/api/ml_commons_execute_api.rst b/docs/source/reference/api/ml_commons_execute_api.rst new file mode 100644 index 00000000..bea00e05 --- /dev/null +++ b/docs/source/reference/api/ml_commons_execute_api.rst @@ -0,0 +1,6 @@ +Execute +================== + +.. currentmodule:: opensearch_py_ml + +.. autofunction:: opensearch_py_ml.ml_commons.MLCommonClient.execute diff --git a/docs/source/reference/mlcommons.rst b/docs/source/reference/mlcommons.rst index 7834d6cf..cd448cd7 100644 --- a/docs/source/reference/mlcommons.rst +++ b/docs/source/reference/mlcommons.rst @@ -96,3 +96,9 @@ Delete Task api/ml_commons_delete_task_api +Execute +~~~~~~~~~~~~~~~~~ +.. toctree:: + :maxdepth: 2 + + api/ml_commons_execute_api diff --git a/opensearch_py_ml/ml_commons/ml_commons_client.py b/opensearch_py_ml/ml_commons/ml_commons_client.py index 0c3cb83a..1f3e63cb 100644 --- a/opensearch_py_ml/ml_commons/ml_commons_client.py +++ b/opensearch_py_ml/ml_commons/ml_commons_client.py @@ -13,6 +13,7 @@ from opensearchpy import OpenSearch from opensearch_py_ml.ml_commons.ml_common_utils import ML_BASE_URI, TIMEOUT +from opensearch_py_ml.ml_commons.model_execute import ModelExecute from opensearch_py_ml.ml_commons.model_uploader import ModelUploader @@ -25,6 +26,22 @@ class MLCommonClient: def __init__(self, os_client: OpenSearch): self._client = os_client self._model_uploader = ModelUploader(os_client) + self._model_execute = ModelExecute(os_client) + + def execute(self, algorithm_name: str, input_json: dict) -> dict: + """ + This method executes ML algorithms that can be only executed directly (i.e. do not support train and + predict APIs), like anomaly localization and metrics correlation. The algorithm has to be supported by ML Commons. + Refer to https://opensearch.org/docs/2.7/ml-commons-plugin/api/#execute + + :param algorithm_name: Name of the algorithm + :type algorithm_name: string + :param input_json: Dictionary of parameters + :type input_json: dict + :return: returns the API response + :rtype: dict + """ + return self._model_execute._execute(algorithm_name, input_json) @deprecated( reason="Since OpenSearch 2.7.0, you can use register_model instead", diff --git a/opensearch_py_ml/ml_commons/model_execute.py b/opensearch_py_ml/ml_commons/model_execute.py new file mode 100644 index 00000000..36bda99d --- /dev/null +++ b/opensearch_py_ml/ml_commons/model_execute.py @@ -0,0 +1,48 @@ +# SPDX-License-Identifier: Apache-2.0 +# The OpenSearch Contributors require contributions made to +# this file be licensed under the Apache-2.0 license or a +# compatible open source license. +# Any modifications Copyright OpenSearch Contributors. See +# GitHub history for details. + +import json + +from opensearchpy import OpenSearch + +from opensearch_py_ml.ml_commons.ml_common_utils import ML_BASE_URI + + +class ModelExecute: + """ + Class for executing algorithms using ML Commons execute API. + """ + + API_ENDPOINT = "models/_execute" + + def __init__(self, os_client: OpenSearch): + self._client = os_client + + def _execute(self, algorithm_name: str, input_json: dict) -> dict: + """ + This method executes ML algorithms that can be only executed directly (i.e. do not support train and + predict APIs), like anomaly localization and metrics correlation. + The input json must be a dictionary or a deserializable Python object. + The algorithm has to be supported by ML Commons. + Refer to https://opensearch.org/docs/2.7/ml-commons-plugin/api/#execute + + :param algorithm_name: Name of the algorithm + :type algorithm_name: string + :param input_json: Dictionary of parameters or a deserializable string, byte, or bytearray + :type input_json: dict + :return: returns the API response + :rtype: dict + """ + + if not isinstance(input_json, dict): + input_json = json.loads(input_json) + + return self._client.transport.perform_request( + method="POST", + url=f"{ML_BASE_URI}/_execute/{algorithm_name}", + body=input_json, + ) diff --git a/tests/ml_commons/test_ml_commons_client.py b/tests/ml_commons/test_ml_commons_client.py index 96687d9b..0dc5bfd2 100644 --- a/tests/ml_commons/test_ml_commons_client.py +++ b/tests/ml_commons/test_ml_commons_client.py @@ -72,6 +72,34 @@ def test_init(): assert type(ml_client._model_uploader) == ModelUploader +def test_execute(): + raised = False + try: + input_json = {"operation": "max", "input_data": [1.0, 2.0, 3.0]} + result = ml_client.execute( + algorithm_name="local_sample_calculator", input_json=input_json + ) + assert result["output"]["result"] == 3 + except: # noqa: E722 + raised = True + assert ( + raised == False + ), "Raised Exception during execute API testing with dictionary" + + raised = False + try: + input_json = '{"operation": "max", "input_data": [1.0, 2.0, 3.0]}' + result = ml_client.execute( + algorithm_name="local_sample_calculator", input_json=input_json + ) + assert result["output"]["result"] == 3 + except: # noqa: E722 + raised = True + assert ( + raised == False + ), "Raised Exception during execute API testing with JSON string" + + def test_DEPRECATED_integration_pretrained_model_upload_unload_delete(): raised = False try: @@ -381,3 +409,6 @@ def test_integration_model_train_register_full_cycle(): except: # noqa: E722 raised = True assert raised == False, "Raised Exception in deleting model" + + +test_integration_model_train_register_full_cycle()