mlrun 1.10.0rc21__py3-none-any.whl → 1.10.0rc23__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/artifacts/llm_prompt.py +11 -10
- mlrun/artifacts/model.py +3 -3
- mlrun/common/schemas/auth.py +2 -0
- mlrun/common/schemas/model_monitoring/functions.py +13 -4
- mlrun/datastore/datastore.py +6 -1
- mlrun/datastore/model_provider/mock_model_provider.py +87 -0
- mlrun/db/base.py +9 -0
- mlrun/db/httpdb.py +21 -1
- mlrun/db/nopdb.py +8 -0
- mlrun/execution.py +52 -10
- mlrun/k8s_utils.py +105 -2
- mlrun/model_monitoring/applications/__init__.py +1 -1
- mlrun/model_monitoring/applications/base.py +86 -33
- mlrun/model_monitoring/controller.py +1 -1
- mlrun/model_monitoring/db/_schedules.py +21 -0
- mlrun/model_monitoring/db/tsdb/base.py +14 -5
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +4 -5
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +53 -20
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +39 -1
- mlrun/projects/project.py +50 -7
- mlrun/serving/server.py +24 -7
- mlrun/serving/states.py +358 -75
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc21.dist-info → mlrun-1.10.0rc23.dist-info}/METADATA +3 -3
- {mlrun-1.10.0rc21.dist-info → mlrun-1.10.0rc23.dist-info}/RECORD +29 -28
- {mlrun-1.10.0rc21.dist-info → mlrun-1.10.0rc23.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc21.dist-info → mlrun-1.10.0rc23.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc21.dist-info → mlrun-1.10.0rc23.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc21.dist-info → mlrun-1.10.0rc23.dist-info}/top_level.txt +0 -0
|
@@ -27,6 +27,7 @@ import mlrun
|
|
|
27
27
|
import mlrun.common.constants as mlrun_constants
|
|
28
28
|
import mlrun.common.helpers
|
|
29
29
|
import mlrun.common.schemas.model_monitoring.constants as mm_constants
|
|
30
|
+
import mlrun.common.types
|
|
30
31
|
import mlrun.datastore.datastore_profile as ds_profile
|
|
31
32
|
import mlrun.errors
|
|
32
33
|
import mlrun.model_monitoring.api as mm_api
|
|
@@ -39,6 +40,12 @@ from mlrun.serving.utils import MonitoringApplicationToDict
|
|
|
39
40
|
from mlrun.utils import logger
|
|
40
41
|
|
|
41
42
|
|
|
43
|
+
class ExistingDataHandling(mlrun.common.types.StrEnum):
|
|
44
|
+
fail_on_overlap = "fail_on_overlap"
|
|
45
|
+
skip_overlap = "skip_overlap"
|
|
46
|
+
delete_all = "delete_all"
|
|
47
|
+
|
|
48
|
+
|
|
42
49
|
def _serialize_context_and_result(
|
|
43
50
|
*,
|
|
44
51
|
context: mm_context.MonitoringApplicationContext,
|
|
@@ -288,7 +295,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
288
295
|
end: Optional[str] = None,
|
|
289
296
|
base_period: Optional[int] = None,
|
|
290
297
|
write_output: bool = False,
|
|
291
|
-
|
|
298
|
+
existing_data_handling: ExistingDataHandling = ExistingDataHandling.fail_on_overlap,
|
|
292
299
|
stream_profile: Optional[ds_profile.DatastoreProfile] = None,
|
|
293
300
|
):
|
|
294
301
|
"""
|
|
@@ -350,6 +357,24 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
350
357
|
resolved_endpoints = self._handle_endpoints_type_evaluate(
|
|
351
358
|
project=project, endpoints=endpoints
|
|
352
359
|
)
|
|
360
|
+
if (
|
|
361
|
+
write_output
|
|
362
|
+
and existing_data_handling == ExistingDataHandling.delete_all
|
|
363
|
+
):
|
|
364
|
+
endpoint_ids = [
|
|
365
|
+
endpoint_id for _, endpoint_id in resolved_endpoints
|
|
366
|
+
]
|
|
367
|
+
context.logger.info(
|
|
368
|
+
"Deleting all the application data before running the application",
|
|
369
|
+
application_name=application_name,
|
|
370
|
+
endpoint_ids=endpoint_ids,
|
|
371
|
+
)
|
|
372
|
+
self._delete_application_data(
|
|
373
|
+
project_name=project.name,
|
|
374
|
+
application_name=application_name,
|
|
375
|
+
endpoint_ids=endpoint_ids,
|
|
376
|
+
application_schedules=application_schedules,
|
|
377
|
+
)
|
|
353
378
|
for endpoint_name, endpoint_id in resolved_endpoints:
|
|
354
379
|
for window_start, window_end in self._window_generator(
|
|
355
380
|
start=start,
|
|
@@ -358,7 +383,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
358
383
|
application_schedules=application_schedules,
|
|
359
384
|
endpoint_id=endpoint_id,
|
|
360
385
|
application_name=application_name,
|
|
361
|
-
|
|
386
|
+
existing_data_handling=existing_data_handling,
|
|
362
387
|
):
|
|
363
388
|
result = call_do_tracking(
|
|
364
389
|
event={
|
|
@@ -481,7 +506,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
481
506
|
end_dt: datetime,
|
|
482
507
|
base_period: Optional[int],
|
|
483
508
|
application_name: str,
|
|
484
|
-
|
|
509
|
+
existing_data_handling: ExistingDataHandling,
|
|
485
510
|
) -> datetime:
|
|
486
511
|
"""Make sure that the (app, endpoint) pair doesn't write output before the last analyzed window"""
|
|
487
512
|
if application_schedules:
|
|
@@ -490,7 +515,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
490
515
|
)
|
|
491
516
|
if last_analyzed:
|
|
492
517
|
if start_dt < last_analyzed:
|
|
493
|
-
if
|
|
518
|
+
if existing_data_handling == ExistingDataHandling.skip_overlap:
|
|
494
519
|
if last_analyzed < end_dt and base_period is None:
|
|
495
520
|
logger.warn(
|
|
496
521
|
"Setting the start time to last_analyzed since the original start time precedes "
|
|
@@ -525,6 +550,25 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
525
550
|
)
|
|
526
551
|
return start_dt
|
|
527
552
|
|
|
553
|
+
@staticmethod
|
|
554
|
+
def _delete_application_data(
|
|
555
|
+
project_name: str,
|
|
556
|
+
application_name: str,
|
|
557
|
+
endpoint_ids: list[str],
|
|
558
|
+
application_schedules: Optional[
|
|
559
|
+
mm_schedules.ModelMonitoringSchedulesFileApplication
|
|
560
|
+
],
|
|
561
|
+
) -> None:
|
|
562
|
+
mlrun.get_run_db().delete_model_monitoring_metrics(
|
|
563
|
+
project=project_name,
|
|
564
|
+
application_name=application_name,
|
|
565
|
+
endpoint_ids=endpoint_ids,
|
|
566
|
+
)
|
|
567
|
+
if application_schedules:
|
|
568
|
+
application_schedules.delete_endpoints_last_analyzed(
|
|
569
|
+
endpoint_uids=endpoint_ids
|
|
570
|
+
)
|
|
571
|
+
|
|
528
572
|
@classmethod
|
|
529
573
|
def _window_generator(
|
|
530
574
|
cls,
|
|
@@ -537,7 +581,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
537
581
|
],
|
|
538
582
|
endpoint_id: str,
|
|
539
583
|
application_name: str,
|
|
540
|
-
|
|
584
|
+
existing_data_handling: ExistingDataHandling,
|
|
541
585
|
) -> Iterator[tuple[Optional[datetime], Optional[datetime]]]:
|
|
542
586
|
if start is None or end is None:
|
|
543
587
|
# A single window based on the `sample_data` input - see `_handler`.
|
|
@@ -547,15 +591,16 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
547
591
|
start_dt = datetime.fromisoformat(start)
|
|
548
592
|
end_dt = datetime.fromisoformat(end)
|
|
549
593
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
594
|
+
if existing_data_handling != ExistingDataHandling.delete_all:
|
|
595
|
+
start_dt = cls._validate_monotonically_increasing_data(
|
|
596
|
+
application_schedules=application_schedules,
|
|
597
|
+
endpoint_id=endpoint_id,
|
|
598
|
+
start_dt=start_dt,
|
|
599
|
+
end_dt=end_dt,
|
|
600
|
+
base_period=base_period,
|
|
601
|
+
application_name=application_name,
|
|
602
|
+
existing_data_handling=existing_data_handling,
|
|
603
|
+
)
|
|
559
604
|
|
|
560
605
|
if base_period is None:
|
|
561
606
|
yield start_dt, end_dt
|
|
@@ -702,7 +747,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
702
747
|
* ``end``, ``datetime``
|
|
703
748
|
* ``base_period``, ``int``
|
|
704
749
|
* ``write_output``, ``bool``
|
|
705
|
-
* ``
|
|
750
|
+
* ``existing_data_handling``, ``str``
|
|
706
751
|
|
|
707
752
|
For Git sources, add the source archive to the returned job and change the handler:
|
|
708
753
|
|
|
@@ -788,7 +833,7 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
788
833
|
end: Optional[datetime] = None,
|
|
789
834
|
base_period: Optional[int] = None,
|
|
790
835
|
write_output: bool = False,
|
|
791
|
-
|
|
836
|
+
existing_data_handling: ExistingDataHandling = ExistingDataHandling.fail_on_overlap,
|
|
792
837
|
stream_profile: Optional[ds_profile.DatastoreProfile] = None,
|
|
793
838
|
) -> "mlrun.RunObject":
|
|
794
839
|
"""
|
|
@@ -856,11 +901,18 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
856
901
|
:param write_output: Whether to write the results and metrics to the time-series DB. Can be ``True`` only
|
|
857
902
|
if ``endpoints`` are passed.
|
|
858
903
|
Note: the model monitoring infrastructure must be up for the writing to work.
|
|
859
|
-
:param
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
904
|
+
:param existing_data_handling:
|
|
905
|
+
How to handle the existing application data for the model endpoints when writing the
|
|
906
|
+
new data. Relevant only when ``write_output=True``. The default is
|
|
907
|
+
``"fail_on_overlap"``. The options are:
|
|
908
|
+
|
|
909
|
+
- ``"fail_on_overlap"``: when the requested ``start`` time precedes the
|
|
910
|
+
``end`` time of a previous run that also wrote to the database - an error is raised.
|
|
911
|
+
- ``"skip_overlap"``: when the previously described situation occurs, the relevant
|
|
912
|
+
time window is cut so that it starts at the earliest possible time after ``start``.
|
|
913
|
+
- ``"delete_all"``: delete all the data that was written by the application to the
|
|
914
|
+
model endpoints, regardless of the time window, and write the new data.
|
|
915
|
+
|
|
864
916
|
:param stream_profile: The stream datastore profile. It should be provided only when running locally and
|
|
865
917
|
writing the outputs to the database (i.e., when both ``run_local`` and
|
|
866
918
|
``write_output`` are set to ``True``).
|
|
@@ -899,18 +951,6 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
899
951
|
)
|
|
900
952
|
params["end"] = end.isoformat() if isinstance(end, datetime) else end
|
|
901
953
|
params["base_period"] = base_period
|
|
902
|
-
params["write_output"] = write_output
|
|
903
|
-
params["fail_on_overlap"] = fail_on_overlap
|
|
904
|
-
if stream_profile:
|
|
905
|
-
if not run_local:
|
|
906
|
-
raise mlrun.errors.MLRunValueError(
|
|
907
|
-
"Passing a `stream_profile` is relevant only when running locally"
|
|
908
|
-
)
|
|
909
|
-
if not write_output:
|
|
910
|
-
raise mlrun.errors.MLRunValueError(
|
|
911
|
-
"Passing a `stream_profile` is relevant only when writing the outputs"
|
|
912
|
-
)
|
|
913
|
-
params["stream_profile"] = stream_profile
|
|
914
954
|
elif start or end or base_period:
|
|
915
955
|
raise mlrun.errors.MLRunValueError(
|
|
916
956
|
"Custom `start` and `end` times or base_period are supported only with endpoints data"
|
|
@@ -920,6 +960,19 @@ class ModelMonitoringApplicationBase(MonitoringApplicationToDict, ABC):
|
|
|
920
960
|
"Writing the application output or passing `stream_profile` are supported only with endpoints data"
|
|
921
961
|
)
|
|
922
962
|
|
|
963
|
+
params["write_output"] = write_output
|
|
964
|
+
params["existing_data_handling"] = existing_data_handling
|
|
965
|
+
if stream_profile:
|
|
966
|
+
if not run_local:
|
|
967
|
+
raise mlrun.errors.MLRunValueError(
|
|
968
|
+
"Passing a `stream_profile` is relevant only when running locally"
|
|
969
|
+
)
|
|
970
|
+
if not write_output:
|
|
971
|
+
raise mlrun.errors.MLRunValueError(
|
|
972
|
+
"Passing a `stream_profile` is relevant only when writing the outputs"
|
|
973
|
+
)
|
|
974
|
+
params["stream_profile"] = stream_profile
|
|
975
|
+
|
|
923
976
|
inputs: dict[str, str] = {}
|
|
924
977
|
for data, identifier in [
|
|
925
978
|
(sample_data, "sample_data"),
|
|
@@ -859,7 +859,7 @@ class MonitoringApplicationController:
|
|
|
859
859
|
for endpoint in endpoints:
|
|
860
860
|
last_request = last_request_dict.get(endpoint.metadata.uid, None)
|
|
861
861
|
if isinstance(last_request, float):
|
|
862
|
-
last_request = pd.to_datetime(last_request, unit="
|
|
862
|
+
last_request = pd.to_datetime(last_request, unit="ms", utc=True)
|
|
863
863
|
endpoint.status.last_request = (
|
|
864
864
|
last_request or endpoint.status.last_request
|
|
865
865
|
)
|
|
@@ -170,6 +170,16 @@ class ModelMonitoringSchedulesFileEndpoint(ModelMonitoringSchedulesFileBase):
|
|
|
170
170
|
self._check_open_schedules()
|
|
171
171
|
self._schedules[application] = float(timestamp)
|
|
172
172
|
|
|
173
|
+
def delete_application_time(self, application: str) -> None:
|
|
174
|
+
self._check_open_schedules()
|
|
175
|
+
if application in self._schedules:
|
|
176
|
+
logger.debug(
|
|
177
|
+
"Deleting application time from schedules",
|
|
178
|
+
application=application,
|
|
179
|
+
endpoint_id=self._endpoint_id,
|
|
180
|
+
)
|
|
181
|
+
del self._schedules[application]
|
|
182
|
+
|
|
173
183
|
def get_application_list(self) -> set[str]:
|
|
174
184
|
self._check_open_schedules()
|
|
175
185
|
return set(self._schedules.keys())
|
|
@@ -275,6 +285,17 @@ class ModelMonitoringSchedulesFileApplication(ModelMonitoringSchedulesFileBase):
|
|
|
275
285
|
timezone.utc
|
|
276
286
|
).isoformat()
|
|
277
287
|
|
|
288
|
+
def delete_endpoints_last_analyzed(self, endpoint_uids: list[str]) -> None:
|
|
289
|
+
self._check_open_schedules()
|
|
290
|
+
for endpoint_uid in endpoint_uids:
|
|
291
|
+
if endpoint_uid in self._schedules:
|
|
292
|
+
logger.debug(
|
|
293
|
+
"Deleting endpoint last analyzed from schedules",
|
|
294
|
+
endpoint_uid=endpoint_uid,
|
|
295
|
+
application=self._application,
|
|
296
|
+
)
|
|
297
|
+
del self._schedules[endpoint_uid]
|
|
298
|
+
|
|
278
299
|
|
|
279
300
|
def _delete_folder(folder: str) -> None:
|
|
280
301
|
fs = mlrun.datastore.store_manager.object(folder).store.filesystem
|
|
@@ -96,14 +96,23 @@ class TSDBConnector(ABC):
|
|
|
96
96
|
"""
|
|
97
97
|
|
|
98
98
|
@abstractmethod
|
|
99
|
-
def delete_tsdb_records(
|
|
100
|
-
self,
|
|
101
|
-
endpoint_ids: list[str],
|
|
102
|
-
) -> None:
|
|
99
|
+
def delete_tsdb_records(self, endpoint_ids: list[str]) -> None:
|
|
103
100
|
"""
|
|
104
101
|
Delete model endpoint records from the TSDB connector.
|
|
102
|
+
|
|
105
103
|
:param endpoint_ids: List of model endpoint unique identifiers.
|
|
106
|
-
|
|
104
|
+
"""
|
|
105
|
+
pass
|
|
106
|
+
|
|
107
|
+
@abstractmethod
|
|
108
|
+
def delete_application_records(
|
|
109
|
+
self, application_name: str, endpoint_ids: Optional[list[str]] = None
|
|
110
|
+
) -> None:
|
|
111
|
+
"""
|
|
112
|
+
Delete application records from the TSDB for the given model endpoints or all if ``None``.
|
|
113
|
+
|
|
114
|
+
:param application_name: The name of the application to delete records for.
|
|
115
|
+
:param endpoint_ids: List of model endpoint unique identifiers.
|
|
107
116
|
"""
|
|
108
117
|
pass
|
|
109
118
|
|
|
@@ -122,10 +122,7 @@ class TDEngineSchema:
|
|
|
122
122
|
)
|
|
123
123
|
return f"DELETE FROM {self.database}.{subtable} WHERE {values};"
|
|
124
124
|
|
|
125
|
-
def drop_subtable_query(
|
|
126
|
-
self,
|
|
127
|
-
subtable: str,
|
|
128
|
-
) -> str:
|
|
125
|
+
def drop_subtable_query(self, subtable: str) -> str:
|
|
129
126
|
return f"DROP TABLE if EXISTS {self.database}.`{subtable}`;"
|
|
130
127
|
|
|
131
128
|
def drop_supertable_query(self) -> str:
|
|
@@ -145,8 +142,10 @@ class TDEngineSchema:
|
|
|
145
142
|
values = f" {operator} ".join(
|
|
146
143
|
f"{filter_tag} LIKE '{val}'" for val in filter_values
|
|
147
144
|
)
|
|
145
|
+
return self._get_tables_query_by_condition(values)
|
|
148
146
|
|
|
149
|
-
|
|
147
|
+
def _get_tables_query_by_condition(self, condition: str) -> str:
|
|
148
|
+
return f"SELECT DISTINCT TBNAME FROM {self.database}.{self.super_table} WHERE {condition};"
|
|
150
149
|
|
|
151
150
|
@staticmethod
|
|
152
151
|
def _get_records_query(
|
|
@@ -22,7 +22,6 @@ import taosws
|
|
|
22
22
|
import mlrun.common.schemas.model_monitoring as mm_schemas
|
|
23
23
|
import mlrun.common.types
|
|
24
24
|
import mlrun.model_monitoring.db.tsdb.tdengine.schemas as tdengine_schemas
|
|
25
|
-
import mlrun.model_monitoring.db.tsdb.tdengine.stream_graph_steps
|
|
26
25
|
from mlrun.datastore.datastore_profile import DatastoreProfile
|
|
27
26
|
from mlrun.model_monitoring.db import TSDBConnector
|
|
28
27
|
from mlrun.model_monitoring.db.tsdb.tdengine.tdengine_connection import (
|
|
@@ -205,7 +204,7 @@ class TDEngineConnector(TSDBConnector):
|
|
|
205
204
|
@staticmethod
|
|
206
205
|
def _generate_filter_query(
|
|
207
206
|
filter_column: str, filter_values: Union[str, list[Union[str, int]]]
|
|
208
|
-
) ->
|
|
207
|
+
) -> str:
|
|
209
208
|
"""
|
|
210
209
|
Generate a filter query for TDEngine based on the provided column and values.
|
|
211
210
|
|
|
@@ -213,15 +212,14 @@ class TDEngineConnector(TSDBConnector):
|
|
|
213
212
|
:param filter_values: A single value or a list of values to filter by.
|
|
214
213
|
|
|
215
214
|
:return: A string representing the filter query.
|
|
216
|
-
:raise:
|
|
215
|
+
:raise: ``MLRunValueError`` if the filter values are not of type string or list.
|
|
217
216
|
"""
|
|
218
|
-
|
|
219
217
|
if isinstance(filter_values, str):
|
|
220
218
|
return f"{filter_column}='{filter_values}'"
|
|
221
219
|
elif isinstance(filter_values, list):
|
|
222
220
|
return f"{filter_column} IN ({', '.join(repr(v) for v in filter_values)}) "
|
|
223
221
|
else:
|
|
224
|
-
raise mlrun.errors.
|
|
222
|
+
raise mlrun.errors.MLRunValueError(
|
|
225
223
|
f"Invalid filter values {filter_values}: must be a string or a list, "
|
|
226
224
|
f"got {type(filter_values).__name__}; filter values: {filter_values}"
|
|
227
225
|
)
|
|
@@ -311,10 +309,7 @@ class TDEngineConnector(TSDBConnector):
|
|
|
311
309
|
flush_after_seconds=tsdb_batching_timeout_secs,
|
|
312
310
|
)
|
|
313
311
|
|
|
314
|
-
def delete_tsdb_records(
|
|
315
|
-
self,
|
|
316
|
-
endpoint_ids: list[str],
|
|
317
|
-
):
|
|
312
|
+
def delete_tsdb_records(self, endpoint_ids: list[str]) -> None:
|
|
318
313
|
"""
|
|
319
314
|
To delete subtables within TDEngine, we first query the subtables names with the provided endpoint_ids.
|
|
320
315
|
Then, we drop each subtable.
|
|
@@ -332,9 +327,7 @@ class TDEngineConnector(TSDBConnector):
|
|
|
332
327
|
get_subtable_query = self.tables[table]._get_subtables_query_by_tag(
|
|
333
328
|
filter_tag="endpoint_id", filter_values=endpoint_ids
|
|
334
329
|
)
|
|
335
|
-
subtables_result = self.connection.run(
|
|
336
|
-
query=get_subtable_query,
|
|
337
|
-
)
|
|
330
|
+
subtables_result = self.connection.run(query=get_subtable_query)
|
|
338
331
|
subtables.extend([subtable[0] for subtable in subtables_result.data])
|
|
339
332
|
except Exception as e:
|
|
340
333
|
logger.warning(
|
|
@@ -346,15 +339,13 @@ class TDEngineConnector(TSDBConnector):
|
|
|
346
339
|
)
|
|
347
340
|
|
|
348
341
|
# Prepare the drop statements
|
|
349
|
-
drop_statements = [
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
)
|
|
342
|
+
drop_statements = [
|
|
343
|
+
self.tables[table].drop_subtable_query(subtable=subtable)
|
|
344
|
+
for subtable in subtables
|
|
345
|
+
]
|
|
354
346
|
try:
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
)
|
|
347
|
+
logger.debug("Dropping subtables", drop_statements=drop_statements)
|
|
348
|
+
self.connection.run(statements=drop_statements)
|
|
358
349
|
except Exception as e:
|
|
359
350
|
logger.warning(
|
|
360
351
|
"Failed to delete model endpoint resources. You may need to delete them manually. "
|
|
@@ -369,6 +360,48 @@ class TDEngineConnector(TSDBConnector):
|
|
|
369
360
|
number_of_endpoints_to_delete=len(endpoint_ids),
|
|
370
361
|
)
|
|
371
362
|
|
|
363
|
+
def delete_application_records(
|
|
364
|
+
self, application_name: str, endpoint_ids: Optional[list[str]] = None
|
|
365
|
+
) -> None:
|
|
366
|
+
"""
|
|
367
|
+
Delete application records from the TSDB for the given model endpoints or all if ``endpoint_ids`` is ``None``.
|
|
368
|
+
"""
|
|
369
|
+
logger.debug(
|
|
370
|
+
"Deleting application records",
|
|
371
|
+
project=self.project,
|
|
372
|
+
application_name=application_name,
|
|
373
|
+
endpoint_ids=endpoint_ids,
|
|
374
|
+
)
|
|
375
|
+
tables = [
|
|
376
|
+
self.tables[mm_schemas.TDEngineSuperTables.APP_RESULTS],
|
|
377
|
+
self.tables[mm_schemas.TDEngineSuperTables.METRICS],
|
|
378
|
+
]
|
|
379
|
+
|
|
380
|
+
filter_query = self._generate_filter_query(
|
|
381
|
+
filter_column=mm_schemas.ApplicationEvent.APPLICATION_NAME,
|
|
382
|
+
filter_values=application_name,
|
|
383
|
+
)
|
|
384
|
+
if endpoint_ids:
|
|
385
|
+
endpoint_ids_filter = self._generate_filter_query(
|
|
386
|
+
filter_column=mm_schemas.EventFieldType.ENDPOINT_ID,
|
|
387
|
+
filter_values=endpoint_ids,
|
|
388
|
+
)
|
|
389
|
+
filter_query += f" AND {endpoint_ids_filter}"
|
|
390
|
+
|
|
391
|
+
drop_statements: list[str] = []
|
|
392
|
+
for table in tables:
|
|
393
|
+
get_subtable_query = table._get_tables_query_by_condition(filter_query)
|
|
394
|
+
subtables_result = self.connection.run(query=get_subtable_query)
|
|
395
|
+
drop_statements.extend(
|
|
396
|
+
[
|
|
397
|
+
table.drop_subtable_query(subtable=subtable[0])
|
|
398
|
+
for subtable in subtables_result.data
|
|
399
|
+
]
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
logger.debug("Dropping application records", drop_statements=drop_statements)
|
|
403
|
+
self.connection.run(statements=drop_statements)
|
|
404
|
+
|
|
372
405
|
def delete_tsdb_resources(self):
|
|
373
406
|
"""
|
|
374
407
|
Delete all project resources in the TSDB connector, such as model endpoints data and drift results.
|
|
@@ -492,7 +492,8 @@ class V3IOTSDBConnector(TSDBConnector):
|
|
|
492
492
|
# Split the endpoint ids into chunks to avoid exceeding the v3io-engine filter-expression limit
|
|
493
493
|
for i in range(0, len(endpoint_ids), V3IO_FRAMESD_MEPS_LIMIT):
|
|
494
494
|
endpoint_id_chunk = endpoint_ids[i : i + V3IO_FRAMESD_MEPS_LIMIT]
|
|
495
|
-
|
|
495
|
+
endpoints_list = "', '".join(endpoint_id_chunk)
|
|
496
|
+
filter_query = f"endpoint_id IN('{endpoints_list}')"
|
|
496
497
|
for table in tables:
|
|
497
498
|
try:
|
|
498
499
|
self.frames_client.delete(
|
|
@@ -532,6 +533,43 @@ class V3IOTSDBConnector(TSDBConnector):
|
|
|
532
533
|
project=self.project,
|
|
533
534
|
)
|
|
534
535
|
|
|
536
|
+
def delete_application_records(
|
|
537
|
+
self, application_name: str, endpoint_ids: Optional[list[str]] = None
|
|
538
|
+
) -> None:
|
|
539
|
+
"""
|
|
540
|
+
Delete application records from the TSDB for the given model endpoints or all if ``endpoint_ids`` is ``None``.
|
|
541
|
+
"""
|
|
542
|
+
base_filter_query = f"application_name=='{application_name}'"
|
|
543
|
+
|
|
544
|
+
filter_queries: list[str] = []
|
|
545
|
+
if endpoint_ids:
|
|
546
|
+
for i in range(0, len(endpoint_ids), V3IO_FRAMESD_MEPS_LIMIT):
|
|
547
|
+
endpoint_id_chunk = endpoint_ids[i : i + V3IO_FRAMESD_MEPS_LIMIT]
|
|
548
|
+
endpoints_list = "', '".join(endpoint_id_chunk)
|
|
549
|
+
filter_queries.append(
|
|
550
|
+
f"{base_filter_query} AND endpoint_id IN ('{endpoints_list}')"
|
|
551
|
+
)
|
|
552
|
+
else:
|
|
553
|
+
filter_queries = [base_filter_query]
|
|
554
|
+
|
|
555
|
+
for table in [
|
|
556
|
+
self.tables[mm_schemas.V3IOTSDBTables.APP_RESULTS],
|
|
557
|
+
self.tables[mm_schemas.V3IOTSDBTables.METRICS],
|
|
558
|
+
]:
|
|
559
|
+
logger.debug(
|
|
560
|
+
"Deleting application records from TSDB",
|
|
561
|
+
table=table,
|
|
562
|
+
filter_queries=filter_queries,
|
|
563
|
+
project=self.project,
|
|
564
|
+
)
|
|
565
|
+
for filter_query in filter_queries:
|
|
566
|
+
self.frames_client.delete(
|
|
567
|
+
backend=_TSDB_BE,
|
|
568
|
+
table=table,
|
|
569
|
+
filter=filter_query,
|
|
570
|
+
start="0",
|
|
571
|
+
)
|
|
572
|
+
|
|
535
573
|
def get_model_endpoint_real_time_metrics(
|
|
536
574
|
self, endpoint_id: str, metrics: list[str], start: str, end: str
|
|
537
575
|
) -> dict[str, list[tuple[str, float]]]:
|
mlrun/projects/project.py
CHANGED
|
@@ -1908,13 +1908,51 @@ class MlrunProject(ModelObj):
|
|
|
1908
1908
|
|
|
1909
1909
|
Examples::
|
|
1910
1910
|
|
|
1911
|
+
# Log directly with an inline prompt template
|
|
1912
|
+
project.log_llm_prompt(
|
|
1913
|
+
key="customer_support_prompt",
|
|
1914
|
+
prompt_template=[
|
|
1915
|
+
{
|
|
1916
|
+
"role": "system",
|
|
1917
|
+
"content": "You are a helpful customer support assistant.",
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
"role": "user",
|
|
1921
|
+
"content": "The customer reports: {issue_description}",
|
|
1922
|
+
},
|
|
1923
|
+
],
|
|
1924
|
+
prompt_legend={
|
|
1925
|
+
"issue_description": {
|
|
1926
|
+
"field": "user_issue",
|
|
1927
|
+
"description": "Detailed description of the customer's issue",
|
|
1928
|
+
},
|
|
1929
|
+
"solution": {
|
|
1930
|
+
"field": "proposed_solution",
|
|
1931
|
+
"description": "Suggested fix for the customer's issue",
|
|
1932
|
+
},
|
|
1933
|
+
},
|
|
1934
|
+
model_artifact=model,
|
|
1935
|
+
model_configuration={"temperature": 0.5, "max_tokens": 200},
|
|
1936
|
+
description="Prompt for handling customer support queries",
|
|
1937
|
+
tag="support-v1",
|
|
1938
|
+
labels={"domain": "support"},
|
|
1939
|
+
)
|
|
1940
|
+
|
|
1911
1941
|
# Log a prompt from file
|
|
1912
1942
|
project.log_llm_prompt(
|
|
1913
|
-
key="
|
|
1914
|
-
prompt_path="prompts/
|
|
1915
|
-
prompt_legend={
|
|
1943
|
+
key="qa_prompt",
|
|
1944
|
+
prompt_path="prompts/template.json",
|
|
1945
|
+
prompt_legend={
|
|
1946
|
+
"question": {
|
|
1947
|
+
"field": "user_question",
|
|
1948
|
+
"description": "The actual question asked by the user",
|
|
1949
|
+
}
|
|
1950
|
+
},
|
|
1916
1951
|
model_artifact=model,
|
|
1952
|
+
model_configuration={"temperature": 0.7, "max_tokens": 256},
|
|
1953
|
+
description="Q&A prompt template with user-provided question",
|
|
1917
1954
|
tag="v2",
|
|
1955
|
+
labels={"task": "qa", "stage": "experiment"},
|
|
1918
1956
|
)
|
|
1919
1957
|
|
|
1920
1958
|
:param key: Unique key for the prompt artifact.
|
|
@@ -1923,7 +1961,10 @@ class MlrunProject(ModelObj):
|
|
|
1923
1961
|
"role": "user", "content": "I need your help with {profession}"]. only "role" and "content" keys allow in any
|
|
1924
1962
|
str format (upper/lower case), keys will be modified to lower case.
|
|
1925
1963
|
Cannot be used with `prompt_path`.
|
|
1926
|
-
:param prompt_path: Path to a file containing the prompt.
|
|
1964
|
+
:param prompt_path: Path to a JSON file containing the prompt template.
|
|
1965
|
+
Cannot be used together with `prompt_template`.
|
|
1966
|
+
The file should define a list of dictionaries in the same format
|
|
1967
|
+
supported by `prompt_template`.
|
|
1927
1968
|
:param prompt_legend: A dictionary where each key is a placeholder in the prompt (e.g., ``{user_name}``)
|
|
1928
1969
|
and the value is a dictionary holding two keys, "field", "description". "field" points to the field in
|
|
1929
1970
|
the event where the value of the place-holder inside the event, if None or not exist will be replaced
|
|
@@ -1932,9 +1973,11 @@ class MlrunProject(ModelObj):
|
|
|
1932
1973
|
:param model_artifact: Reference to the parent model (either `ModelArtifact` or model URI string).
|
|
1933
1974
|
:param model_configuration: Configuration dictionary for model generation parameters
|
|
1934
1975
|
(e.g., temperature, max tokens).
|
|
1935
|
-
:param description:
|
|
1936
|
-
:param target_path:
|
|
1937
|
-
:param artifact_path:
|
|
1976
|
+
:param description: Optional description of the prompt.
|
|
1977
|
+
:param target_path: Absolute target path (instead of using artifact_path + local_path)
|
|
1978
|
+
:param artifact_path: Target artifact path (when not using the default)
|
|
1979
|
+
To define a subpath under the default location use:
|
|
1980
|
+
`artifact_path=context.artifact_subpath('data')`
|
|
1938
1981
|
:param tag: Version tag for the artifact (e.g., "v1", "latest").
|
|
1939
1982
|
:param labels: Labels to tag the artifact for filtering and organization.
|
|
1940
1983
|
:param upload: Whether to upload the artifact to a remote datastore. Defaults to True.
|
mlrun/serving/server.py
CHANGED
|
@@ -17,8 +17,10 @@ __all__ = ["GraphServer", "create_graph_server", "GraphContext", "MockEvent"]
|
|
|
17
17
|
import asyncio
|
|
18
18
|
import base64
|
|
19
19
|
import copy
|
|
20
|
+
import importlib
|
|
20
21
|
import json
|
|
21
22
|
import os
|
|
23
|
+
import pathlib
|
|
22
24
|
import socket
|
|
23
25
|
import traceback
|
|
24
26
|
import uuid
|
|
@@ -572,19 +574,34 @@ async def async_execute_graph(
|
|
|
572
574
|
nest_under_inputs: bool,
|
|
573
575
|
) -> list[Any]:
|
|
574
576
|
spec = mlrun.utils.get_serving_spec()
|
|
575
|
-
|
|
576
|
-
namespace = {}
|
|
577
|
+
modname = None
|
|
577
578
|
code = os.getenv("MLRUN_EXEC_CODE")
|
|
578
579
|
if code:
|
|
579
580
|
code = base64.b64decode(code).decode("utf-8")
|
|
580
|
-
|
|
581
|
+
with open("user_code.py", "w") as fp:
|
|
582
|
+
fp.write(code)
|
|
583
|
+
modname = "user_code"
|
|
581
584
|
else:
|
|
582
585
|
# TODO: find another way to get the local file path, or ensure that MLRUN_EXEC_CODE
|
|
583
586
|
# gets set in local flow and not just in the remote pod
|
|
584
|
-
|
|
585
|
-
if
|
|
586
|
-
|
|
587
|
-
|
|
587
|
+
source_file_path = spec.get("filename", None)
|
|
588
|
+
if source_file_path:
|
|
589
|
+
source_file_path_object = pathlib.Path(source_file_path).resolve()
|
|
590
|
+
current_dir_path_object = pathlib.Path(".").resolve()
|
|
591
|
+
if not source_file_path_object.is_relative_to(current_dir_path_object):
|
|
592
|
+
raise mlrun.errors.MLRunRuntimeError(
|
|
593
|
+
f"Source file path '{source_file_path}' is not under the current working directory "
|
|
594
|
+
f"(which is required when running with local=True)"
|
|
595
|
+
)
|
|
596
|
+
relative_path_to_source_file = source_file_path_object.relative_to(
|
|
597
|
+
current_dir_path_object
|
|
598
|
+
)
|
|
599
|
+
modname = ".".join(relative_path_to_source_file.with_suffix("").parts)
|
|
600
|
+
|
|
601
|
+
namespace = {}
|
|
602
|
+
if modname:
|
|
603
|
+
mod = importlib.import_module(modname)
|
|
604
|
+
namespace = mod.__dict__
|
|
588
605
|
|
|
589
606
|
server = GraphServer.from_dict(spec)
|
|
590
607
|
|