mlrun 1.10.0rc18__py3-none-any.whl → 1.10.0rc20__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 +21 -2
- mlrun/common/constants.py +1 -0
- mlrun/common/schemas/function.py +10 -0
- mlrun/common/schemas/model_monitoring/constants.py +4 -11
- mlrun/common/schemas/model_monitoring/model_endpoints.py +2 -0
- mlrun/datastore/__init__.py +9 -1
- mlrun/datastore/model_provider/huggingface_provider.py +114 -26
- mlrun/datastore/model_provider/model_provider.py +144 -70
- mlrun/datastore/model_provider/openai_provider.py +95 -37
- mlrun/db/base.py +0 -19
- mlrun/db/httpdb.py +10 -46
- mlrun/db/nopdb.py +0 -10
- mlrun/launcher/base.py +13 -6
- mlrun/model_monitoring/api.py +43 -22
- mlrun/model_monitoring/applications/base.py +1 -1
- mlrun/model_monitoring/controller.py +112 -38
- mlrun/model_monitoring/db/_schedules.py +13 -9
- mlrun/model_monitoring/stream_processing.py +16 -12
- mlrun/platforms/__init__.py +3 -2
- mlrun/projects/project.py +2 -2
- mlrun/run.py +1 -1
- mlrun/runtimes/base.py +5 -2
- mlrun/runtimes/daskjob.py +1 -0
- mlrun/runtimes/nuclio/application/application.py +84 -5
- mlrun/runtimes/nuclio/function.py +3 -1
- mlrun/serving/server.py +24 -0
- mlrun/serving/states.py +80 -30
- mlrun/serving/system_steps.py +60 -36
- mlrun/utils/helpers.py +37 -13
- mlrun/utils/notifications/notification_pusher.py +1 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc18.dist-info → mlrun-1.10.0rc20.dist-info}/METADATA +4 -4
- {mlrun-1.10.0rc18.dist-info → mlrun-1.10.0rc20.dist-info}/RECORD +37 -38
- mlrun/api/schemas/__init__.py +0 -259
- {mlrun-1.10.0rc18.dist-info → mlrun-1.10.0rc20.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.10.0rc20.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.10.0rc20.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.10.0rc20.dist-info}/top_level.txt +0 -0
|
@@ -13,13 +13,19 @@
|
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
import inspect
|
|
15
15
|
from collections.abc import Awaitable
|
|
16
|
-
from typing import Callable, Optional,
|
|
16
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional, Union
|
|
17
17
|
|
|
18
18
|
import mlrun
|
|
19
|
-
from mlrun.datastore.model_provider.model_provider import
|
|
19
|
+
from mlrun.datastore.model_provider.model_provider import (
|
|
20
|
+
InvokeResponseFormat,
|
|
21
|
+
ModelProvider,
|
|
22
|
+
UsageResponseKeys,
|
|
23
|
+
)
|
|
20
24
|
from mlrun.datastore.utils import accepts_param
|
|
21
25
|
|
|
22
|
-
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from openai._models import BaseModel # noqa
|
|
28
|
+
from openai.types.chat.chat_completion import ChatCompletion
|
|
23
29
|
|
|
24
30
|
|
|
25
31
|
class OpenAIProvider(ModelProvider):
|
|
@@ -36,6 +42,7 @@ class OpenAIProvider(ModelProvider):
|
|
|
36
42
|
"""
|
|
37
43
|
|
|
38
44
|
support_async = True
|
|
45
|
+
response_class = None
|
|
39
46
|
|
|
40
47
|
def __init__(
|
|
41
48
|
self,
|
|
@@ -62,6 +69,27 @@ class OpenAIProvider(ModelProvider):
|
|
|
62
69
|
self.options = self.get_client_options()
|
|
63
70
|
self.load_client()
|
|
64
71
|
|
|
72
|
+
@classmethod
|
|
73
|
+
def _import_response_class(cls) -> None:
|
|
74
|
+
if not cls.response_class:
|
|
75
|
+
try:
|
|
76
|
+
from openai.types.chat.chat_completion import ChatCompletion
|
|
77
|
+
except ImportError as exc:
|
|
78
|
+
raise ImportError("openai package is not installed") from exc
|
|
79
|
+
cls.response_class = ChatCompletion
|
|
80
|
+
|
|
81
|
+
@staticmethod
|
|
82
|
+
def _extract_string_output(response: "ChatCompletion") -> str:
|
|
83
|
+
"""
|
|
84
|
+
Extracts the first generated string from Hugging Face pipeline output,
|
|
85
|
+
regardless of whether it's plain text-generation or chat-style output.
|
|
86
|
+
"""
|
|
87
|
+
if len(response.choices) != 1:
|
|
88
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
89
|
+
"OpenAIProvider: extracting string from response is only supported for single-response outputs"
|
|
90
|
+
)
|
|
91
|
+
return response.choices[0].message.content
|
|
92
|
+
|
|
65
93
|
@classmethod
|
|
66
94
|
def parse_endpoint_and_path(cls, endpoint, subpath) -> (str, str):
|
|
67
95
|
if endpoint and subpath:
|
|
@@ -101,8 +129,8 @@ class OpenAIProvider(ModelProvider):
|
|
|
101
129
|
return self._sanitize_options(res)
|
|
102
130
|
|
|
103
131
|
def custom_invoke(
|
|
104
|
-
self, operation: Optional[Callable
|
|
105
|
-
) ->
|
|
132
|
+
self, operation: Optional[Callable] = None, **invoke_kwargs
|
|
133
|
+
) -> Union["ChatCompletion", "BaseModel"]:
|
|
106
134
|
"""
|
|
107
135
|
OpenAI-specific implementation of `ModelProvider.custom_invoke`.
|
|
108
136
|
|
|
@@ -139,9 +167,9 @@ class OpenAIProvider(ModelProvider):
|
|
|
139
167
|
|
|
140
168
|
async def async_custom_invoke(
|
|
141
169
|
self,
|
|
142
|
-
operation: Optional[Callable[..., Awaitable[
|
|
170
|
+
operation: Optional[Callable[..., Awaitable[Any]]] = None,
|
|
143
171
|
**invoke_kwargs,
|
|
144
|
-
) ->
|
|
172
|
+
) -> Union["ChatCompletion", "BaseModel"]:
|
|
145
173
|
"""
|
|
146
174
|
OpenAI-specific implementation of `ModelProvider.async_custom_invoke`.
|
|
147
175
|
|
|
@@ -178,60 +206,90 @@ class OpenAIProvider(ModelProvider):
|
|
|
178
206
|
**invoke_kwargs, **model_kwargs
|
|
179
207
|
)
|
|
180
208
|
|
|
209
|
+
def _response_handler(
|
|
210
|
+
self,
|
|
211
|
+
response: "ChatCompletion",
|
|
212
|
+
invoke_response_format: InvokeResponseFormat = InvokeResponseFormat.FULL,
|
|
213
|
+
**kwargs,
|
|
214
|
+
) -> ["ChatCompletion", str, dict[str, Any]]:
|
|
215
|
+
if InvokeResponseFormat.is_str_response(invoke_response_format.value):
|
|
216
|
+
str_response = self._extract_string_output(response)
|
|
217
|
+
if invoke_response_format == InvokeResponseFormat.STRING:
|
|
218
|
+
return str_response
|
|
219
|
+
if invoke_response_format == InvokeResponseFormat.USAGE:
|
|
220
|
+
stats = response.to_dict()["usage"]
|
|
221
|
+
response = {
|
|
222
|
+
UsageResponseKeys.ANSWER: str_response,
|
|
223
|
+
UsageResponseKeys.USAGE: stats,
|
|
224
|
+
}
|
|
225
|
+
return response
|
|
226
|
+
|
|
181
227
|
def invoke(
|
|
182
228
|
self,
|
|
183
|
-
messages:
|
|
184
|
-
|
|
229
|
+
messages: list[dict],
|
|
230
|
+
invoke_response_format: InvokeResponseFormat = InvokeResponseFormat.FULL,
|
|
185
231
|
**invoke_kwargs,
|
|
186
|
-
) ->
|
|
232
|
+
) -> Union[dict[str, Any], str, "ChatCompletion"]:
|
|
187
233
|
"""
|
|
188
234
|
OpenAI-specific implementation of `ModelProvider.invoke`.
|
|
189
|
-
Invokes an OpenAI model operation using the
|
|
235
|
+
Invokes an OpenAI model operation using the synchronous client.
|
|
190
236
|
For full details, see `ModelProvider.invoke`.
|
|
191
237
|
|
|
192
|
-
:param messages:
|
|
238
|
+
:param messages:
|
|
239
|
+
Same as `ModelProvider.invoke`.
|
|
193
240
|
|
|
194
|
-
:param
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
241
|
+
:param invoke_response_format: InvokeResponseFormat
|
|
242
|
+
Specifies the format of the returned response. Options:
|
|
243
|
+
|
|
244
|
+
- "string": Returns only the generated text content, taken from a single response.
|
|
245
|
+
- "stats": Combines the generated text with metadata (e.g., token usage), returning a dictionary:
|
|
246
|
+
|
|
247
|
+
.. code-block:: json
|
|
248
|
+
{
|
|
249
|
+
"answer": "<generated_text>",
|
|
250
|
+
"stats": <ChatCompletion>.to_dict()["usage"]
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
- "full": Returns the full OpenAI `ChatCompletion` object.
|
|
199
254
|
|
|
200
255
|
:param invoke_kwargs:
|
|
201
|
-
|
|
202
|
-
:return: Same as ModelProvider.invoke.
|
|
256
|
+
Additional keyword arguments passed to the OpenAI client. Same as in `ModelProvider.invoke`.
|
|
203
257
|
|
|
258
|
+
:return:
|
|
259
|
+
A string, dictionary, or `ChatCompletion` object, depending on `invoke_response_format`.
|
|
204
260
|
"""
|
|
261
|
+
|
|
205
262
|
response = self.custom_invoke(messages=messages, **invoke_kwargs)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
263
|
+
return self._response_handler(
|
|
264
|
+
messages=messages,
|
|
265
|
+
invoke_response_format=invoke_response_format,
|
|
266
|
+
response=response,
|
|
267
|
+
)
|
|
209
268
|
|
|
210
269
|
async def async_invoke(
|
|
211
270
|
self,
|
|
212
|
-
messages:
|
|
213
|
-
|
|
271
|
+
messages: list[dict],
|
|
272
|
+
invoke_response_format=InvokeResponseFormat.FULL,
|
|
214
273
|
**invoke_kwargs,
|
|
215
|
-
) -> str:
|
|
274
|
+
) -> Union[str, "ChatCompletion", dict]:
|
|
216
275
|
"""
|
|
217
276
|
OpenAI-specific implementation of `ModelProvider.async_invoke`.
|
|
218
277
|
Invokes an OpenAI model operation using the async client.
|
|
219
|
-
For full details, see `ModelProvider.async_invoke`.
|
|
278
|
+
For full details, see `ModelProvider.async_invoke` and `OpenAIProvider.invoke`.
|
|
220
279
|
|
|
221
|
-
:param messages: Same as
|
|
280
|
+
:param messages: Same as `OpenAIProvider.invoke`.
|
|
222
281
|
|
|
223
|
-
:param
|
|
224
|
-
|
|
225
|
-
(`response.choices[0].message.content`).
|
|
226
|
-
If `False`, returns the full awaited response object, whose type depends on
|
|
227
|
-
the specific OpenAI SDK operation used (e.g., chat completion, completion, etc.).
|
|
282
|
+
:param invoke_response_format: InvokeResponseFormat
|
|
283
|
+
Same as `OpenAIProvider.invoke`.
|
|
228
284
|
|
|
229
285
|
:param invoke_kwargs:
|
|
230
|
-
Same as
|
|
231
|
-
:returns Same as ModelProvider.async_invoke
|
|
286
|
+
Same as `OpenAIProvider.invoke`.
|
|
287
|
+
:returns Same as `ModelProvider.async_invoke`.
|
|
232
288
|
|
|
233
289
|
"""
|
|
234
290
|
response = await self.async_custom_invoke(messages=messages, **invoke_kwargs)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
291
|
+
return self._response_handler(
|
|
292
|
+
messages=messages,
|
|
293
|
+
invoke_response_format=invoke_response_format,
|
|
294
|
+
response=response,
|
|
295
|
+
)
|
mlrun/db/base.py
CHANGED
|
@@ -16,8 +16,6 @@ import datetime
|
|
|
16
16
|
from abc import ABC, abstractmethod
|
|
17
17
|
from typing import Literal, Optional, Union
|
|
18
18
|
|
|
19
|
-
from deprecated import deprecated
|
|
20
|
-
|
|
21
19
|
import mlrun.alerts
|
|
22
20
|
import mlrun.common
|
|
23
21
|
import mlrun.common.formatters
|
|
@@ -445,23 +443,6 @@ class RunDBInterface(ABC):
|
|
|
445
443
|
) -> dict:
|
|
446
444
|
pass
|
|
447
445
|
|
|
448
|
-
# TODO: remove in 1.10.0
|
|
449
|
-
@deprecated(
|
|
450
|
-
version="1.7.0",
|
|
451
|
-
reason="'list_features' will be removed in 1.10.0, use 'list_features_v2' instead",
|
|
452
|
-
category=FutureWarning,
|
|
453
|
-
)
|
|
454
|
-
@abstractmethod
|
|
455
|
-
def list_features(
|
|
456
|
-
self,
|
|
457
|
-
project: str,
|
|
458
|
-
name: Optional[str] = None,
|
|
459
|
-
tag: Optional[str] = None,
|
|
460
|
-
entities: Optional[list[str]] = None,
|
|
461
|
-
labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
|
|
462
|
-
) -> mlrun.common.schemas.FeaturesOutput:
|
|
463
|
-
pass
|
|
464
|
-
|
|
465
446
|
@abstractmethod
|
|
466
447
|
def list_features_v2(
|
|
467
448
|
self,
|
mlrun/db/httpdb.py
CHANGED
|
@@ -24,6 +24,7 @@ from datetime import datetime, timedelta
|
|
|
24
24
|
from os import environ, path, remove
|
|
25
25
|
from typing import Literal, Optional, Union
|
|
26
26
|
from urllib.parse import urlparse
|
|
27
|
+
from uuid import UUID
|
|
27
28
|
|
|
28
29
|
import pydantic.v1
|
|
29
30
|
import requests
|
|
@@ -2554,50 +2555,6 @@ class HTTPRunDB(RunDBInterface):
|
|
|
2554
2555
|
resp = self.api_call("GET", path, error_message)
|
|
2555
2556
|
return FeatureSet.from_dict(resp.json())
|
|
2556
2557
|
|
|
2557
|
-
def list_features(
|
|
2558
|
-
self,
|
|
2559
|
-
project: Optional[str] = None,
|
|
2560
|
-
name: Optional[str] = None,
|
|
2561
|
-
tag: Optional[str] = None,
|
|
2562
|
-
entities: Optional[list[str]] = None,
|
|
2563
|
-
labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
|
|
2564
|
-
) -> list[dict]:
|
|
2565
|
-
"""List feature-sets which contain specific features. This function may return multiple versions of the same
|
|
2566
|
-
feature-set if a specific tag is not requested. Note that the various filters of this function actually
|
|
2567
|
-
refer to the feature-set object containing the features, not to the features themselves.
|
|
2568
|
-
|
|
2569
|
-
:param project: Project which contains these features.
|
|
2570
|
-
:param name: Name of the feature to look for. The name is used in a like query, and is not case-sensitive. For
|
|
2571
|
-
example, looking for ``feat`` will return features which are named ``MyFeature`` as well as ``defeat``.
|
|
2572
|
-
:param tag: Return feature-sets which contain the features looked for, and are tagged with the specific tag.
|
|
2573
|
-
:param entities: Return only feature-sets which contain an entity whose name is contained in this list.
|
|
2574
|
-
:param labels: Filter feature-sets by label key-value pairs or key existence. This can be provided as:
|
|
2575
|
-
- A dictionary in the format `{"label": "value"}` to match specific label key-value pairs,
|
|
2576
|
-
or `{"label": None}` to check for key existence.
|
|
2577
|
-
- A list of strings formatted as `"label=value"` to match specific label key-value pairs,
|
|
2578
|
-
or just `"label"` for key existence.
|
|
2579
|
-
- A comma-separated string formatted as `"label1=value1,label2"` to match entities with
|
|
2580
|
-
the specified key-value pairs or key existence.
|
|
2581
|
-
:returns: A list of mapping from feature to a digest of the feature-set, which contains the feature-set
|
|
2582
|
-
meta-data. Multiple entries may be returned for any specific feature due to multiple tags or versions
|
|
2583
|
-
of the feature-set.
|
|
2584
|
-
"""
|
|
2585
|
-
|
|
2586
|
-
project = project or config.active_project
|
|
2587
|
-
labels = self._parse_labels(labels)
|
|
2588
|
-
params = {
|
|
2589
|
-
"name": name,
|
|
2590
|
-
"tag": tag,
|
|
2591
|
-
"entity": entities or [],
|
|
2592
|
-
"label": labels,
|
|
2593
|
-
}
|
|
2594
|
-
|
|
2595
|
-
path = f"projects/{project}/features"
|
|
2596
|
-
|
|
2597
|
-
error_message = f"Failed listing features, project: {project}, query: {params}"
|
|
2598
|
-
resp = self.api_call("GET", path, error_message, params=params)
|
|
2599
|
-
return resp.json()["features"]
|
|
2600
|
-
|
|
2601
2558
|
def list_features_v2(
|
|
2602
2559
|
self,
|
|
2603
2560
|
project: Optional[str] = None,
|
|
@@ -3834,8 +3791,8 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3834
3791
|
If tsdb_metrics=False, this parameter will be ignored and no tsdb metrics
|
|
3835
3792
|
will be included.
|
|
3836
3793
|
:param top_level: Whether to return only top level model endpoints.
|
|
3837
|
-
:param mode: Specifies the mode of the model endpoint. Can be "real-time", "batch", or
|
|
3838
|
-
to None.
|
|
3794
|
+
:param mode: Specifies the mode of the model endpoint. Can be "real-time" (0), "batch" (1), or
|
|
3795
|
+
both if set to None.
|
|
3839
3796
|
:param uids: A list of unique ids to filter by.
|
|
3840
3797
|
:param latest_only: Whether to return only the latest model endpoint version.
|
|
3841
3798
|
:return: A list of model endpoints.
|
|
@@ -3968,6 +3925,13 @@ class HTTPRunDB(RunDBInterface):
|
|
|
3968
3925
|
raise MLRunInvalidArgumentError(
|
|
3969
3926
|
"Either endpoint_uid or function_name and function_tag must be provided"
|
|
3970
3927
|
)
|
|
3928
|
+
if uid:
|
|
3929
|
+
try:
|
|
3930
|
+
UUID(uid)
|
|
3931
|
+
except (ValueError, TypeError):
|
|
3932
|
+
raise MLRunInvalidArgumentError(
|
|
3933
|
+
"endpoint_id must be a valid UUID string"
|
|
3934
|
+
)
|
|
3971
3935
|
|
|
3972
3936
|
def update_model_monitoring_controller(
|
|
3973
3937
|
self,
|
mlrun/db/nopdb.py
CHANGED
|
@@ -376,16 +376,6 @@ class NopDB(RunDBInterface):
|
|
|
376
376
|
) -> dict:
|
|
377
377
|
pass
|
|
378
378
|
|
|
379
|
-
def list_features(
|
|
380
|
-
self,
|
|
381
|
-
project: str,
|
|
382
|
-
name: Optional[str] = None,
|
|
383
|
-
tag: Optional[str] = None,
|
|
384
|
-
entities: Optional[list[str]] = None,
|
|
385
|
-
labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
|
|
386
|
-
) -> mlrun.common.schemas.FeaturesOutput:
|
|
387
|
-
pass
|
|
388
|
-
|
|
389
379
|
def list_features_v2(
|
|
390
380
|
self,
|
|
391
381
|
project: str,
|
mlrun/launcher/base.py
CHANGED
|
@@ -157,6 +157,19 @@ class BaseLauncher(abc.ABC):
|
|
|
157
157
|
]:
|
|
158
158
|
mlrun.utils.helpers.warn_on_deprecated_image(image)
|
|
159
159
|
|
|
160
|
+
# Raise an error if retry is configured for a runtime that doesn't support retries.
|
|
161
|
+
# For local runs, we intentionally skip this validation and allow the run to proceed, since they are typically
|
|
162
|
+
# used for debugging purposes, and in such cases we avoid blocking their execution.
|
|
163
|
+
if (
|
|
164
|
+
not mlrun.runtimes.RuntimeKinds.is_local_runtime(runtime.kind)
|
|
165
|
+
and run.spec.retry.count
|
|
166
|
+
and runtime.kind not in mlrun.runtimes.RuntimeKinds.retriable_runtimes()
|
|
167
|
+
):
|
|
168
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
169
|
+
f"Retry is not supported for {runtime.kind} runtime, supported runtimes are: "
|
|
170
|
+
f"{mlrun.runtimes.RuntimeKinds.retriable_runtimes()}"
|
|
171
|
+
)
|
|
172
|
+
|
|
160
173
|
@staticmethod
|
|
161
174
|
def _validate_output_path(
|
|
162
175
|
runtime: "mlrun.runtimes.BaseRuntime",
|
|
@@ -268,12 +281,6 @@ class BaseLauncher(abc.ABC):
|
|
|
268
281
|
|
|
269
282
|
run.metadata.name = mlrun.utils.normalize_name(
|
|
270
283
|
name=name or run.metadata.name or def_name,
|
|
271
|
-
# if name or runspec.metadata.name are set then it means that is user defined name and we want to warn the
|
|
272
|
-
# user that the passed name needs to be set without underscore, if its not user defined but rather enriched
|
|
273
|
-
# from the handler(function) name then we replace the underscore without warning the user.
|
|
274
|
-
# most of the time handlers will have `_` in the handler name (python convention is to separate function
|
|
275
|
-
# words with `_`), therefore we don't want to be noisy when normalizing the run name
|
|
276
|
-
verbose=bool(name or run.metadata.name),
|
|
277
284
|
)
|
|
278
285
|
mlrun.utils.verify_field_regex(
|
|
279
286
|
"run.metadata.name", run.metadata.name, mlrun.utils.regex.run_name
|
mlrun/model_monitoring/api.py
CHANGED
|
@@ -18,6 +18,7 @@ from datetime import datetime
|
|
|
18
18
|
|
|
19
19
|
import numpy as np
|
|
20
20
|
import pandas as pd
|
|
21
|
+
from deprecated import deprecated
|
|
21
22
|
|
|
22
23
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
23
24
|
import mlrun.datastore.base
|
|
@@ -45,6 +46,14 @@ DatasetType = typing.Union[
|
|
|
45
46
|
]
|
|
46
47
|
|
|
47
48
|
|
|
49
|
+
# TODO: Remove this in 1.12.0
|
|
50
|
+
@deprecated(
|
|
51
|
+
version="1.10.0",
|
|
52
|
+
reason="This function is deprecated and will be removed in 1.12. You can generate a model endpoint by either "
|
|
53
|
+
"deploying a monitored serving function as a real-time service or running it as an offline job. "
|
|
54
|
+
"To retrieve model endpoints, use `project.list_model_endpoints()`",
|
|
55
|
+
category=FutureWarning,
|
|
56
|
+
)
|
|
48
57
|
def get_or_create_model_endpoint(
|
|
49
58
|
project: str,
|
|
50
59
|
model_endpoint_name: str,
|
|
@@ -67,8 +76,8 @@ def get_or_create_model_endpoint(
|
|
|
67
76
|
:param model_endpoint_name: If a new model endpoint is created, the model endpoint name will be presented
|
|
68
77
|
under this endpoint (applicable only to new endpoint_id).
|
|
69
78
|
:param model_path: The model store path (applicable only to new endpoint_id).
|
|
70
|
-
:param endpoint_id: Model endpoint unique ID. If not exist in DB, will generate a new record
|
|
71
|
-
|
|
79
|
+
:param endpoint_id: Model endpoint unique ID. If not exist in DB, will generate a new record with a
|
|
80
|
+
newly generated ID.
|
|
72
81
|
:param function_name: If a new model endpoint is created, use this function name.
|
|
73
82
|
:param function_tag: If a new model endpoint is created, use this function tag.
|
|
74
83
|
:param context: MLRun context. If `function_name` not provided, use the context to generate the
|
|
@@ -91,25 +100,26 @@ def get_or_create_model_endpoint(
|
|
|
91
100
|
function_name = FunctionURI.from_string(
|
|
92
101
|
context.to_dict()["spec"]["function"]
|
|
93
102
|
).function
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
103
|
+
if endpoint_id or function_name:
|
|
104
|
+
try:
|
|
105
|
+
model_endpoint = db_session.get_model_endpoint(
|
|
106
|
+
project=project,
|
|
107
|
+
name=model_endpoint_name,
|
|
108
|
+
endpoint_id=endpoint_id,
|
|
109
|
+
function_name=function_name,
|
|
110
|
+
function_tag=function_tag or "latest",
|
|
111
|
+
feature_analysis=feature_analysis,
|
|
112
|
+
)
|
|
113
|
+
# If other fields provided, validate that they are correspond to the existing model endpoint data
|
|
114
|
+
_model_endpoint_validations(
|
|
115
|
+
model_endpoint=model_endpoint,
|
|
116
|
+
model_path=model_path,
|
|
117
|
+
sample_set_statistics=sample_set_statistics,
|
|
118
|
+
)
|
|
109
119
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
120
|
+
except mlrun.errors.MLRunNotFoundError:
|
|
121
|
+
# Create a new model endpoint with the provided details
|
|
122
|
+
pass
|
|
113
123
|
if not model_endpoint:
|
|
114
124
|
model_endpoint = _generate_model_endpoint(
|
|
115
125
|
project=project,
|
|
@@ -123,6 +133,13 @@ def get_or_create_model_endpoint(
|
|
|
123
133
|
return model_endpoint
|
|
124
134
|
|
|
125
135
|
|
|
136
|
+
# TODO: Remove this in 1.12.0
|
|
137
|
+
@deprecated(
|
|
138
|
+
version="1.10.0",
|
|
139
|
+
reason="This function is deprecated and will be removed in 1.12. "
|
|
140
|
+
"Instead, run a monitored serving function as a job",
|
|
141
|
+
category=FutureWarning,
|
|
142
|
+
)
|
|
126
143
|
def record_results(
|
|
127
144
|
project: str,
|
|
128
145
|
model_path: str,
|
|
@@ -144,8 +161,8 @@ def record_results(
|
|
|
144
161
|
:param model_path: The model Store path.
|
|
145
162
|
:param model_endpoint_name: If a new model endpoint is generated, the model endpoint name will be presented
|
|
146
163
|
under this endpoint.
|
|
147
|
-
:param endpoint_id: Model endpoint unique ID. If not exist in DB, will generate a new record
|
|
148
|
-
|
|
164
|
+
:param endpoint_id: Model endpoint unique ID. If not exist in DB, will generate a new record with a
|
|
165
|
+
newly generated ID.
|
|
149
166
|
:param function_name: If a new model endpoint is created, use this function name for generating the
|
|
150
167
|
function URI.
|
|
151
168
|
:param context: MLRun context. Note that the context is required generating the model endpoint.
|
|
@@ -236,6 +253,7 @@ def _model_endpoint_validations(
|
|
|
236
253
|
key=model_obj.key,
|
|
237
254
|
iter=model_obj.iter,
|
|
238
255
|
tree=model_obj.tree,
|
|
256
|
+
uid=model_obj.uid,
|
|
239
257
|
)
|
|
240
258
|
|
|
241
259
|
# Enrich the uri schema with the store prefix
|
|
@@ -325,12 +343,15 @@ def _generate_model_endpoint(
|
|
|
325
343
|
|
|
326
344
|
:return `mlrun.common.schemas.ModelEndpoint` object.
|
|
327
345
|
"""
|
|
346
|
+
|
|
328
347
|
current_time = datetime_now()
|
|
329
348
|
model_endpoint = mlrun.common.schemas.ModelEndpoint(
|
|
330
349
|
metadata=mlrun.common.schemas.ModelEndpointMetadata(
|
|
331
350
|
project=project,
|
|
332
351
|
name=model_endpoint_name,
|
|
333
352
|
endpoint_type=mlrun.common.schemas.model_monitoring.EndpointType.BATCH_EP,
|
|
353
|
+
# Due to backwards compatibility, old batch model endpoint will be analyzed as real time endpoint
|
|
354
|
+
mode=mlrun.common.schemas.model_monitoring.EndpointMode.REAL_TIME,
|
|
334
355
|
),
|
|
335
356
|
spec=mlrun.common.schemas.ModelEndpointSpec(
|
|
336
357
|
function_name=function_name or "function",
|
|
@@ -647,7 +647,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
647
647
|
else:
|
|
648
648
|
class_name = handler_to_class.split(".")[-1].split("::")[0]
|
|
649
649
|
|
|
650
|
-
job_name = mlrun.utils.normalize_name(class_name
|
|
650
|
+
job_name = mlrun.utils.normalize_name(class_name)
|
|
651
651
|
|
|
652
652
|
if not mm_constants.APP_NAME_REGEX.fullmatch(job_name):
|
|
653
653
|
raise mlrun.errors.MLRunValueError(
|