mlrun 1.10.0rc3__py3-none-any.whl → 1.10.0rc5__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/__init__.py +1 -0
- mlrun/artifacts/base.py +14 -2
- mlrun/artifacts/helpers.py +40 -0
- mlrun/artifacts/llm_prompt.py +165 -0
- mlrun/artifacts/manager.py +13 -1
- mlrun/artifacts/model.py +91 -11
- mlrun/common/formatters/artifact.py +1 -0
- mlrun/common/runtimes/constants.py +0 -14
- mlrun/common/schemas/artifact.py +12 -12
- mlrun/common/schemas/pipeline.py +0 -16
- mlrun/common/schemas/project.py +0 -17
- mlrun/common/schemas/runs.py +0 -17
- mlrun/config.py +5 -2
- mlrun/datastore/base.py +2 -2
- mlrun/datastore/datastore.py +1 -1
- mlrun/datastore/datastore_profile.py +1 -9
- mlrun/datastore/redis.py +2 -3
- mlrun/datastore/sources.py +0 -9
- mlrun/datastore/storeytargets.py +2 -5
- mlrun/datastore/targets.py +6 -56
- mlrun/datastore/utils.py +1 -11
- mlrun/db/base.py +1 -0
- mlrun/db/httpdb.py +6 -0
- mlrun/db/nopdb.py +1 -0
- mlrun/execution.py +87 -1
- mlrun/model.py +0 -5
- mlrun/model_monitoring/applications/base.py +9 -5
- mlrun/projects/project.py +241 -4
- mlrun/run.py +0 -18
- mlrun/runtimes/daskjob.py +8 -1
- mlrun/runtimes/remotesparkjob.py +6 -0
- mlrun/runtimes/sparkjob/spark3job.py +6 -0
- mlrun/serving/states.py +67 -3
- mlrun/serving/v2_serving.py +1 -1
- mlrun/utils/helpers.py +60 -8
- mlrun/utils/notifications/notification/slack.py +5 -1
- mlrun/utils/notifications/notification_pusher.py +2 -1
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc5.dist-info}/METADATA +5 -5
- {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc5.dist-info}/RECORD +44 -42
- {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc5.dist-info}/WHEEL +1 -1
- {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc5.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc5.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc5.dist-info}/top_level.txt +0 -0
mlrun/projects/project.py
CHANGED
|
@@ -82,6 +82,7 @@ from ..artifacts import (
|
|
|
82
82
|
DatasetArtifact,
|
|
83
83
|
DocumentArtifact,
|
|
84
84
|
DocumentLoaderSpec,
|
|
85
|
+
LLMPromptArtifact,
|
|
85
86
|
ModelArtifact,
|
|
86
87
|
)
|
|
87
88
|
from ..artifacts.manager import ArtifactManager, dict_to_artifact, extend_artifact_path
|
|
@@ -1799,6 +1800,8 @@ class MlrunProject(ModelObj):
|
|
|
1799
1800
|
training_set=None,
|
|
1800
1801
|
label_column=None,
|
|
1801
1802
|
extra_data=None,
|
|
1803
|
+
model_url: Optional[str] = None,
|
|
1804
|
+
default_config=None,
|
|
1802
1805
|
**kwargs,
|
|
1803
1806
|
) -> ModelArtifact:
|
|
1804
1807
|
"""Log a model artifact and optionally upload it to datastore
|
|
@@ -1841,7 +1844,9 @@ class MlrunProject(ModelObj):
|
|
|
1841
1844
|
:param label_column: which columns in the training set are the label (target) columns
|
|
1842
1845
|
:param extra_data: key/value list of extra files/charts to link with this dataset
|
|
1843
1846
|
value can be absolute path | relative path (to model dir) | bytes | artifact object
|
|
1844
|
-
|
|
1847
|
+
:param model_url: Remote model url.
|
|
1848
|
+
:param default_config: Default configuration for client building
|
|
1849
|
+
Saved as a sub-dictionary under the parameter.
|
|
1845
1850
|
:returns: model artifact object
|
|
1846
1851
|
"""
|
|
1847
1852
|
|
|
@@ -1864,6 +1869,8 @@ class MlrunProject(ModelObj):
|
|
|
1864
1869
|
feature_vector=feature_vector,
|
|
1865
1870
|
feature_weights=feature_weights,
|
|
1866
1871
|
extra_data=extra_data,
|
|
1872
|
+
model_url=model_url,
|
|
1873
|
+
default_config=default_config,
|
|
1867
1874
|
**kwargs,
|
|
1868
1875
|
)
|
|
1869
1876
|
if training_set is not None:
|
|
@@ -1881,6 +1888,87 @@ class MlrunProject(ModelObj):
|
|
|
1881
1888
|
)
|
|
1882
1889
|
return item
|
|
1883
1890
|
|
|
1891
|
+
def log_llm_prompt(
|
|
1892
|
+
self,
|
|
1893
|
+
key,
|
|
1894
|
+
prompt_string: Optional[str] = None,
|
|
1895
|
+
prompt_path: Optional[str] = None,
|
|
1896
|
+
prompt_legend: Optional[dict] = None,
|
|
1897
|
+
model_artifact: Union[ModelArtifact, str] = None,
|
|
1898
|
+
model_configuration: Optional[dict] = None,
|
|
1899
|
+
description: Optional[str] = None,
|
|
1900
|
+
target_path: Optional[str] = None,
|
|
1901
|
+
artifact_path: Optional[str] = None,
|
|
1902
|
+
tag: Optional[str] = None,
|
|
1903
|
+
labels: Optional[Union[list[str], str]] = None,
|
|
1904
|
+
upload: Optional[bool] = None,
|
|
1905
|
+
**kwargs,
|
|
1906
|
+
) -> LLMPromptArtifact:
|
|
1907
|
+
"""
|
|
1908
|
+
Log an LLM prompt artifact to the project.
|
|
1909
|
+
|
|
1910
|
+
This method creates and logs an `LLMPromptArtifact` which captures a prompt definition for large language model
|
|
1911
|
+
(LLM) interactions. The prompt can be provided as a string or a file, and may include metadata like generation
|
|
1912
|
+
parameters, a legend for variable injection, and references to a parent model artifact.
|
|
1913
|
+
|
|
1914
|
+
If the prompt content exceeds a certain length, it may be stored in a temporary file and logged accordingly.
|
|
1915
|
+
|
|
1916
|
+
Examples::
|
|
1917
|
+
|
|
1918
|
+
# Log a prompt from file
|
|
1919
|
+
project.log_llm_prompt(
|
|
1920
|
+
key="qa-prompt",
|
|
1921
|
+
prompt_path="prompts/qa_template.txt",
|
|
1922
|
+
prompt_legend={"question": "user_question"},
|
|
1923
|
+
model_artifact=model,
|
|
1924
|
+
tag="v2",
|
|
1925
|
+
)
|
|
1926
|
+
|
|
1927
|
+
:param key: Unique key for the prompt artifact.
|
|
1928
|
+
:param prompt_string: Raw prompt text. Mutually exclusive with `prompt_path`.
|
|
1929
|
+
:param prompt_path: Path to a file containing the prompt. Mutually exclusive with `prompt_string`.
|
|
1930
|
+
:param prompt_legend: A dictionary where each key is a placeholder in the prompt (e.g., ``{user_name}``)
|
|
1931
|
+
and the value is a description or explanation of what that placeholder represents.
|
|
1932
|
+
Useful for documenting and clarifying dynamic parts of the prompt.
|
|
1933
|
+
:param model_artifact: Reference to the parent model (either `ModelArtifact` or model URI string).
|
|
1934
|
+
:param model_configuration: Configuration dictionary for model generation parameters
|
|
1935
|
+
(e.g., temperature, max tokens).
|
|
1936
|
+
:param description: Optional description of the prompt.
|
|
1937
|
+
:param target_path: Optional local target path for saving prompt content.
|
|
1938
|
+
:param artifact_path: Storage path for the logged artifact.
|
|
1939
|
+
:param tag: Version tag for the artifact (e.g., "v1", "latest").
|
|
1940
|
+
:param labels: Labels to tag the artifact for filtering and organization.
|
|
1941
|
+
:param upload: Whether to upload the artifact to a remote datastore. Defaults to True.
|
|
1942
|
+
:param kwargs: Additional attributes to pass into the `LLMPromptArtifact`.
|
|
1943
|
+
|
|
1944
|
+
:returns: The logged `LLMPromptArtifact` object.
|
|
1945
|
+
"""
|
|
1946
|
+
|
|
1947
|
+
llm_prompt = LLMPromptArtifact(
|
|
1948
|
+
key=key,
|
|
1949
|
+
project=self.name,
|
|
1950
|
+
prompt_string=prompt_string,
|
|
1951
|
+
prompt_path=prompt_path,
|
|
1952
|
+
prompt_legend=prompt_legend,
|
|
1953
|
+
model_artifact=model_artifact,
|
|
1954
|
+
model_configuration=model_configuration,
|
|
1955
|
+
target_path=target_path,
|
|
1956
|
+
description=description,
|
|
1957
|
+
**kwargs,
|
|
1958
|
+
)
|
|
1959
|
+
|
|
1960
|
+
item = cast(
|
|
1961
|
+
LLMPromptArtifact,
|
|
1962
|
+
self.log_artifact(
|
|
1963
|
+
llm_prompt,
|
|
1964
|
+
artifact_path=artifact_path,
|
|
1965
|
+
tag=tag,
|
|
1966
|
+
upload=upload,
|
|
1967
|
+
labels=labels,
|
|
1968
|
+
),
|
|
1969
|
+
)
|
|
1970
|
+
return item
|
|
1971
|
+
|
|
1884
1972
|
def get_vector_store_collection(
|
|
1885
1973
|
self,
|
|
1886
1974
|
vector_store: "VectorStore", # noqa: F821
|
|
@@ -4474,8 +4562,8 @@ class MlrunProject(ModelObj):
|
|
|
4474
4562
|
|
|
4475
4563
|
def list_models(
|
|
4476
4564
|
self,
|
|
4477
|
-
name=None,
|
|
4478
|
-
tag=None,
|
|
4565
|
+
name: Optional[str] = None,
|
|
4566
|
+
tag: Optional[str] = None,
|
|
4479
4567
|
labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
|
|
4480
4568
|
since=None,
|
|
4481
4569
|
until=None,
|
|
@@ -4486,7 +4574,7 @@ class MlrunProject(ModelObj):
|
|
|
4486
4574
|
format_: Optional[
|
|
4487
4575
|
mlrun.common.formatters.ArtifactFormat
|
|
4488
4576
|
] = mlrun.common.formatters.ArtifactFormat.full,
|
|
4489
|
-
):
|
|
4577
|
+
) -> list[ModelArtifact]:
|
|
4490
4578
|
"""List models in project, filtered by various parameters.
|
|
4491
4579
|
|
|
4492
4580
|
Examples::
|
|
@@ -4595,6 +4683,155 @@ class MlrunProject(ModelObj):
|
|
|
4595
4683
|
**kwargs,
|
|
4596
4684
|
)
|
|
4597
4685
|
|
|
4686
|
+
def list_llm_prompts(
|
|
4687
|
+
self,
|
|
4688
|
+
name: Optional[str] = None,
|
|
4689
|
+
tag: Optional[str] = None,
|
|
4690
|
+
labels: Optional[Union[str, dict[str, Optional[str]], list[str]]] = None,
|
|
4691
|
+
since: Optional[datetime.datetime] = None,
|
|
4692
|
+
until: Optional[datetime.datetime] = None,
|
|
4693
|
+
iter: Optional[int] = None,
|
|
4694
|
+
best_iteration: bool = False,
|
|
4695
|
+
tree: Optional[str] = None,
|
|
4696
|
+
model: Optional[Union[str, Artifact]] = None,
|
|
4697
|
+
format_: Optional[
|
|
4698
|
+
mlrun.common.formatters.ArtifactFormat
|
|
4699
|
+
] = mlrun.common.formatters.ArtifactFormat.full,
|
|
4700
|
+
partition_by: Optional[
|
|
4701
|
+
Union[mlrun.common.schemas.ArtifactPartitionByField, str]
|
|
4702
|
+
] = None,
|
|
4703
|
+
rows_per_partition: int = 1,
|
|
4704
|
+
partition_sort_by: Optional[
|
|
4705
|
+
Union[mlrun.common.schemas.SortField, str]
|
|
4706
|
+
] = mlrun.common.schemas.SortField.updated,
|
|
4707
|
+
partition_order: Union[
|
|
4708
|
+
mlrun.common.schemas.OrderType, str
|
|
4709
|
+
] = mlrun.common.schemas.OrderType.desc,
|
|
4710
|
+
) -> list[mlrun.artifacts.llm_prompt.LLMPromptArtifact]:
|
|
4711
|
+
"""List LLM prompt artifacts in the project with support for filtering.
|
|
4712
|
+
|
|
4713
|
+
This method returns a list of LLM prompt artifacts, filtered by parameters such as name, tag, labels,
|
|
4714
|
+
model association, iteration, and more. It can be used to retrieve the latest, best, or specific versions
|
|
4715
|
+
of prompts tied to a model or general project context.
|
|
4716
|
+
|
|
4717
|
+
Examples::
|
|
4718
|
+
|
|
4719
|
+
# Get all latest tagged prompts
|
|
4720
|
+
prompts = project.list_llm_prompts(tag="latest")
|
|
4721
|
+
|
|
4722
|
+
# Get prompts associated with a specific model
|
|
4723
|
+
prompts = project.list_llm_prompts(model=ModelArtifact("m1"))
|
|
4724
|
+
|
|
4725
|
+
# Get prompts filtered by label
|
|
4726
|
+
prompts = project.list_llm_prompts(labels={"use_case": "chatbot"})
|
|
4727
|
+
|
|
4728
|
+
# Get prompts using a name wildcard
|
|
4729
|
+
prompts = project.list_llm_prompts(name="~chat")
|
|
4730
|
+
|
|
4731
|
+
:param name: Name of the prompt artifact. Prefix with '~' for wildcard search (case-insensitive).
|
|
4732
|
+
:param tag: Filter artifacts by this tag (e.g., 'latest', 'prod').
|
|
4733
|
+
:param labels: Filter llm-prompt artifacts by label key-value pairs or key existence. This can be provided as:
|
|
4734
|
+
|
|
4735
|
+
- A dictionary in the format `{"label": "value"}` to match specific label key-value pairs,
|
|
4736
|
+
or `{"label": None}` to check for key existence.
|
|
4737
|
+
- A list of strings formatted as `"label=value"` to match specific label key-value pairs,
|
|
4738
|
+
or just `"label"` for key existence.
|
|
4739
|
+
- A comma-separated string formatted as `"label1=value1,label2"` to match entities with
|
|
4740
|
+
the specified key-value pairs or key existence.
|
|
4741
|
+
|
|
4742
|
+
:param since: Return artifacts updated after this date (as datetime object).
|
|
4743
|
+
:param until: Return artifacts updated before this date (as datetime object).
|
|
4744
|
+
:param iter: Retrieve a specific iteration. Use `0` for root; `None` for all.
|
|
4745
|
+
:param best_iteration: Returns the llm-prompt artifact which belongs to the best iteration of a given run,
|
|
4746
|
+
in the case of artifacts generated from a hyper-param run. If only a single iteration exists, will return
|
|
4747
|
+
the artifact from that iteration. If using ``best_iter``, the ``iter`` parameter must not be used.
|
|
4748
|
+
:param tree: Filter by artifact tree ID (e.g., for lineage filtering).
|
|
4749
|
+
:param model: Return prompts associated with this model (can be `Artifact` URI or `Artifact` object).
|
|
4750
|
+
:param format_: The format in which to return the artifacts. Default is 'full'.
|
|
4751
|
+
:param partition_by: Field to group results by. When `partition_by` is specified, the `partition_sort_by`
|
|
4752
|
+
parameter must be provided as well.
|
|
4753
|
+
:param rows_per_partition: How many top rows (per sorting defined by `partition_sort_by` and `partition_order`)
|
|
4754
|
+
to return per group. Default value is 1.
|
|
4755
|
+
:param partition_sort_by: What field to sort the results by, within each partition defined by `partition_by`.
|
|
4756
|
+
Currently the only allowed values are `created` and `updated`.
|
|
4757
|
+
:param partition_order: Order of sorting within partitions - `asc` or `desc`. Default is `desc`.
|
|
4758
|
+
|
|
4759
|
+
:returns: A list of filtered `LLMPromptArtifact` objects matching the given parameters.
|
|
4760
|
+
"""
|
|
4761
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4762
|
+
return db.list_artifacts(
|
|
4763
|
+
name=name,
|
|
4764
|
+
project=self.metadata.name,
|
|
4765
|
+
tag=tag,
|
|
4766
|
+
labels=labels,
|
|
4767
|
+
since=since,
|
|
4768
|
+
until=until,
|
|
4769
|
+
iter=iter,
|
|
4770
|
+
best_iteration=best_iteration,
|
|
4771
|
+
kind=mlrun.artifacts.llm_prompt.LLMPromptArtifact.kind,
|
|
4772
|
+
tree=tree,
|
|
4773
|
+
parent=model.uri if isinstance(model, Artifact) else model,
|
|
4774
|
+
format_=format_,
|
|
4775
|
+
partition_by=partition_by,
|
|
4776
|
+
rows_per_partition=rows_per_partition,
|
|
4777
|
+
partition_sort_by=partition_sort_by,
|
|
4778
|
+
partition_order=partition_order,
|
|
4779
|
+
).to_objects()
|
|
4780
|
+
|
|
4781
|
+
def paginated_list_llm_prompts(
|
|
4782
|
+
self,
|
|
4783
|
+
*args,
|
|
4784
|
+
page: Optional[int] = None,
|
|
4785
|
+
page_size: Optional[int] = None,
|
|
4786
|
+
page_token: Optional[str] = None,
|
|
4787
|
+
**kwargs,
|
|
4788
|
+
) -> tuple[mlrun.lists.ArtifactList, Optional[str]]:
|
|
4789
|
+
"""Retrieve a paginated list of LLM prompt artifacts for the current project.
|
|
4790
|
+
|
|
4791
|
+
This method returns a list of LLM prompt artifacts, supporting both token-based and page-number-based
|
|
4792
|
+
pagination. You can filter and navigate through the results using the optional `page`, `page_size`, and
|
|
4793
|
+
`page_token` parameters.
|
|
4794
|
+
|
|
4795
|
+
Examples::
|
|
4796
|
+
|
|
4797
|
+
# Fetch the first page with up to 5 prompt artifacts
|
|
4798
|
+
prompts, token = project.paginated_list_llm_prompts(page_size=5)
|
|
4799
|
+
|
|
4800
|
+
# Fetch the next page using the page token
|
|
4801
|
+
prompts, token = project.paginated_list_llm_prompts(page_token=token)
|
|
4802
|
+
|
|
4803
|
+
# Fetch a specific page (e.g., page 3)
|
|
4804
|
+
prompts, token = project.paginated_list_llm_prompts(page=3, page_size=5)
|
|
4805
|
+
|
|
4806
|
+
# Retrieve all prompt artifacts across pages
|
|
4807
|
+
all_prompts = []
|
|
4808
|
+
token = None
|
|
4809
|
+
while True:
|
|
4810
|
+
page_prompts, token = project.paginated_list_llm_prompts(
|
|
4811
|
+
page_token=token, page_size=5
|
|
4812
|
+
)
|
|
4813
|
+
all_prompts.extend(page_prompts)
|
|
4814
|
+
if not token:
|
|
4815
|
+
break
|
|
4816
|
+
print(f"Total retrieved prompts: {len(all_prompts)}")
|
|
4817
|
+
|
|
4818
|
+
:param page: Page number to retrieve (alternative to page_token).
|
|
4819
|
+
:param page_size: Number of items per page. Defaults to `mlrun.mlconf.httpdb.pagination.default_page_size`.
|
|
4820
|
+
:param page_token: Token for retrieving the next page of results (used for continuous iteration).
|
|
4821
|
+
|
|
4822
|
+
:returns: A tuple of (ArtifactList of LLM prompts, next page_token or None if no more pages).
|
|
4823
|
+
"""
|
|
4824
|
+
db = mlrun.db.get_run_db(secrets=self._secrets)
|
|
4825
|
+
return db.paginated_list_artifacts(
|
|
4826
|
+
*args,
|
|
4827
|
+
project=self.metadata.name,
|
|
4828
|
+
kind=mlrun.artifacts.llm_prompt.LLMPromptArtifact.kind,
|
|
4829
|
+
page=page,
|
|
4830
|
+
page_size=page_size,
|
|
4831
|
+
page_token=page_token,
|
|
4832
|
+
**kwargs,
|
|
4833
|
+
)
|
|
4834
|
+
|
|
4598
4835
|
def list_functions(
|
|
4599
4836
|
self,
|
|
4600
4837
|
name: Optional[str] = None,
|
mlrun/run.py
CHANGED
|
@@ -21,7 +21,6 @@ import tempfile
|
|
|
21
21
|
import time
|
|
22
22
|
import typing
|
|
23
23
|
import uuid
|
|
24
|
-
import warnings
|
|
25
24
|
from base64 import b64decode
|
|
26
25
|
from copy import deepcopy
|
|
27
26
|
from os import environ, makedirs, path
|
|
@@ -206,7 +205,6 @@ def get_or_create_ctx(
|
|
|
206
205
|
rundb: Union[str, "mlrun.db.RunDBInterface"] = "",
|
|
207
206
|
project: str = "",
|
|
208
207
|
upload_artifacts: bool = False,
|
|
209
|
-
labels: Optional[dict] = None,
|
|
210
208
|
) -> MLClientCtx:
|
|
211
209
|
"""
|
|
212
210
|
Called from within the user program to obtain a run context.
|
|
@@ -226,7 +224,6 @@ def get_or_create_ctx(
|
|
|
226
224
|
:param project: project to initiate the context in (by default `mlrun.mlconf.active_project`)
|
|
227
225
|
:param upload_artifacts: when using local context (not as part of a job/run), upload artifacts to the
|
|
228
226
|
system default artifact path location
|
|
229
|
-
:param labels: (deprecated - use spec instead) dict of the context labels.
|
|
230
227
|
:return: execution context
|
|
231
228
|
|
|
232
229
|
Examples::
|
|
@@ -259,21 +256,6 @@ def get_or_create_ctx(
|
|
|
259
256
|
context.log_artifact("results.html", body=b"<b> Some HTML <b>", viewer="web-app")
|
|
260
257
|
|
|
261
258
|
"""
|
|
262
|
-
if labels:
|
|
263
|
-
warnings.warn(
|
|
264
|
-
"The `labels` argument is deprecated in 1.7.0 and will be removed in 1.10.0. "
|
|
265
|
-
"Please use `spec` instead, e.g.:\n"
|
|
266
|
-
"spec={'metadata': {'labels': {'key': 'value'}}}",
|
|
267
|
-
FutureWarning,
|
|
268
|
-
)
|
|
269
|
-
if spec is None:
|
|
270
|
-
spec = {}
|
|
271
|
-
if "metadata" not in spec:
|
|
272
|
-
spec["metadata"] = {}
|
|
273
|
-
if "labels" not in spec["metadata"]:
|
|
274
|
-
spec["metadata"]["labels"] = {}
|
|
275
|
-
spec["metadata"]["labels"].update(labels)
|
|
276
|
-
|
|
277
259
|
if global_context.get() and not spec and not event:
|
|
278
260
|
return global_context.get()
|
|
279
261
|
|
mlrun/runtimes/daskjob.py
CHANGED
|
@@ -255,7 +255,11 @@ class DaskCluster(KubejobRuntime):
|
|
|
255
255
|
background_task = db.start_function(func_url=self._function_uri())
|
|
256
256
|
if watch:
|
|
257
257
|
now = datetime.datetime.utcnow()
|
|
258
|
-
timeout = now + datetime.timedelta(
|
|
258
|
+
timeout = now + datetime.timedelta(
|
|
259
|
+
seconds=int(
|
|
260
|
+
mlrun.mlconf.background_tasks.default_timeouts.runtimes.dask_cluster_start
|
|
261
|
+
)
|
|
262
|
+
)
|
|
259
263
|
while now < timeout:
|
|
260
264
|
background_task = db.get_project_background_task(
|
|
261
265
|
background_task.metadata.project, background_task.metadata.name
|
|
@@ -282,6 +286,9 @@ class DaskCluster(KubejobRuntime):
|
|
|
282
286
|
return
|
|
283
287
|
time.sleep(5)
|
|
284
288
|
now = datetime.datetime.utcnow()
|
|
289
|
+
raise mlrun.errors.MLRunTimeoutError(
|
|
290
|
+
"Timeout waiting for Dask cluster to start"
|
|
291
|
+
)
|
|
285
292
|
|
|
286
293
|
def close(self, running=True):
|
|
287
294
|
from dask.distributed import default_client
|
mlrun/runtimes/remotesparkjob.py
CHANGED
|
@@ -103,6 +103,12 @@ class RemoteSparkRuntime(KubejobRuntime):
|
|
|
103
103
|
|
|
104
104
|
@classmethod
|
|
105
105
|
def deploy_default_image(cls):
|
|
106
|
+
if not mlrun.get_current_project(silent=True):
|
|
107
|
+
raise mlrun.errors.MLRunMissingProjectError(
|
|
108
|
+
"An active project is required to run deploy_default_image(). "
|
|
109
|
+
"This can be set by calling get_or_create_project(), load_project(), or new_project()."
|
|
110
|
+
)
|
|
111
|
+
|
|
106
112
|
sj = mlrun.new_function(
|
|
107
113
|
kind="remote-spark", name="remote-spark-default-image-deploy-temp"
|
|
108
114
|
)
|
|
@@ -804,6 +804,12 @@ class Spark3Runtime(KubejobRuntime):
|
|
|
804
804
|
|
|
805
805
|
@classmethod
|
|
806
806
|
def deploy_default_image(cls, with_gpu=False):
|
|
807
|
+
if not mlrun.get_current_project(silent=True):
|
|
808
|
+
raise mlrun.errors.MLRunMissingProjectError(
|
|
809
|
+
"An active project is required to run deploy_default_image(). "
|
|
810
|
+
"This can be set by calling get_or_create_project()."
|
|
811
|
+
)
|
|
812
|
+
|
|
807
813
|
sj = mlrun.new_function(kind=cls.kind, name="spark-default-image-deploy-temp")
|
|
808
814
|
sj.spec.build.image = cls._get_default_deployed_mlrun_image_name(with_gpu)
|
|
809
815
|
|
mlrun/serving/states.py
CHANGED
|
@@ -32,12 +32,14 @@ import storey.utils
|
|
|
32
32
|
import mlrun
|
|
33
33
|
import mlrun.artifacts
|
|
34
34
|
import mlrun.common.schemas as schemas
|
|
35
|
+
from mlrun.artifacts.model import ModelArtifact
|
|
35
36
|
from mlrun.datastore.datastore_profile import (
|
|
36
37
|
DatastoreProfileKafkaSource,
|
|
37
38
|
DatastoreProfileKafkaTarget,
|
|
38
39
|
DatastoreProfileV3io,
|
|
39
40
|
datastore_profile_read,
|
|
40
41
|
)
|
|
42
|
+
from mlrun.datastore.store_resources import get_store_resource
|
|
41
43
|
from mlrun.datastore.storeytargets import KafkaStoreyTarget, StreamStoreyTarget
|
|
42
44
|
from mlrun.utils import logger
|
|
43
45
|
|
|
@@ -955,10 +957,33 @@ class RouterStep(TaskStep):
|
|
|
955
957
|
|
|
956
958
|
|
|
957
959
|
class Model(storey.ParallelExecutionRunnable):
|
|
960
|
+
def __init__(
|
|
961
|
+
self,
|
|
962
|
+
name: str,
|
|
963
|
+
raise_exception: bool = True,
|
|
964
|
+
artifact_uri: Optional[str] = None,
|
|
965
|
+
**kwargs,
|
|
966
|
+
):
|
|
967
|
+
super().__init__(name=name, raise_exception=raise_exception, **kwargs)
|
|
968
|
+
if artifact_uri is not None and not isinstance(artifact_uri, str):
|
|
969
|
+
raise MLRunInvalidArgumentError("artifact_uri argument must be a string")
|
|
970
|
+
self.artifact_uri = artifact_uri
|
|
971
|
+
|
|
958
972
|
def load(self) -> None:
|
|
959
973
|
"""Override to load model if needed."""
|
|
960
974
|
pass
|
|
961
975
|
|
|
976
|
+
def _get_artifact_object(self) -> Union[ModelArtifact, None]:
|
|
977
|
+
if self.artifact_uri:
|
|
978
|
+
if mlrun.datastore.is_store_uri(self.artifact_uri):
|
|
979
|
+
return get_store_resource(self.artifact_uri)
|
|
980
|
+
else:
|
|
981
|
+
raise ValueError(
|
|
982
|
+
"Could not get artifact, artifact_uri must be a valid artifact store URI"
|
|
983
|
+
)
|
|
984
|
+
else:
|
|
985
|
+
return None
|
|
986
|
+
|
|
962
987
|
def init(self):
|
|
963
988
|
self.load()
|
|
964
989
|
|
|
@@ -976,6 +1001,39 @@ class Model(storey.ParallelExecutionRunnable):
|
|
|
976
1001
|
async def run_async(self, body: Any, path: str) -> Any:
|
|
977
1002
|
return self.predict(body)
|
|
978
1003
|
|
|
1004
|
+
def get_local_model_path(self, suffix="") -> (str, dict):
|
|
1005
|
+
"""get local model file(s) and extra data items by using artifact
|
|
1006
|
+
If the model file is stored in remote cloud storage, download it to the local file system
|
|
1007
|
+
|
|
1008
|
+
Examples
|
|
1009
|
+
--------
|
|
1010
|
+
::
|
|
1011
|
+
|
|
1012
|
+
def load(self):
|
|
1013
|
+
model_file, extra_data = self.get_local_model_path(suffix=".pkl")
|
|
1014
|
+
self.model = load(open(model_file, "rb"))
|
|
1015
|
+
categories = extra_data["categories"].as_df()
|
|
1016
|
+
|
|
1017
|
+
Parameters
|
|
1018
|
+
----------
|
|
1019
|
+
suffix : str
|
|
1020
|
+
optional, model file suffix (when the model_path is a directory)
|
|
1021
|
+
|
|
1022
|
+
Returns
|
|
1023
|
+
-------
|
|
1024
|
+
str
|
|
1025
|
+
(local) model file
|
|
1026
|
+
dict
|
|
1027
|
+
extra dataitems dictionary
|
|
1028
|
+
"""
|
|
1029
|
+
artifact = self._get_artifact_object()
|
|
1030
|
+
if artifact:
|
|
1031
|
+
model_file, _, extra_dataitems = mlrun.artifacts.get_model(
|
|
1032
|
+
suffix=suffix, model_dir=artifact
|
|
1033
|
+
)
|
|
1034
|
+
return model_file, extra_dataitems
|
|
1035
|
+
return None, None
|
|
1036
|
+
|
|
979
1037
|
|
|
980
1038
|
class ModelSelector:
|
|
981
1039
|
"""Used to select which models to run on each event."""
|
|
@@ -1089,6 +1147,14 @@ class ModelRunnerStep(TaskStep, StepToDict):
|
|
|
1089
1147
|
"""
|
|
1090
1148
|
# TODO allow model_class as Model object as part of ML-9924
|
|
1091
1149
|
model_parameters = model_parameters or {}
|
|
1150
|
+
model_artifact = (
|
|
1151
|
+
model_artifact.uri
|
|
1152
|
+
if isinstance(model_artifact, mlrun.artifacts.Artifact)
|
|
1153
|
+
else model_artifact
|
|
1154
|
+
)
|
|
1155
|
+
model_parameters["artifact_uri"] = model_parameters.get(
|
|
1156
|
+
"artifact_uri", model_artifact
|
|
1157
|
+
)
|
|
1092
1158
|
if model_parameters.get("name", endpoint_name) != endpoint_name:
|
|
1093
1159
|
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
1094
1160
|
"Inconsistent name for model added to ModelRunnerStep."
|
|
@@ -1111,9 +1177,7 @@ class ModelRunnerStep(TaskStep, StepToDict):
|
|
|
1111
1177
|
schemas.MonitoringData.INPUT_PATH: input_path,
|
|
1112
1178
|
schemas.MonitoringData.CREATION_STRATEGY: creation_strategy,
|
|
1113
1179
|
schemas.MonitoringData.LABELS: labels,
|
|
1114
|
-
schemas.MonitoringData.MODEL_PATH: model_artifact
|
|
1115
|
-
if isinstance(model_artifact, mlrun.artifacts.Artifact)
|
|
1116
|
-
else model_artifact,
|
|
1180
|
+
schemas.MonitoringData.MODEL_PATH: model_artifact,
|
|
1117
1181
|
}
|
|
1118
1182
|
self.class_args[schemas.ModelRunnerStepData.MODELS] = models
|
|
1119
1183
|
self.class_args[schemas.ModelRunnerStepData.MONITORING_DATA] = monitoring_data
|
mlrun/serving/v2_serving.py
CHANGED
|
@@ -177,7 +177,7 @@ class V2ModelServer(StepToDict):
|
|
|
177
177
|
"""set real time metric (for model monitoring)"""
|
|
178
178
|
self.metrics[name] = value
|
|
179
179
|
|
|
180
|
-
def get_model(self, suffix=""):
|
|
180
|
+
def get_model(self, suffix="") -> (str, dict):
|
|
181
181
|
"""get the model file(s) and metadata from model store
|
|
182
182
|
|
|
183
183
|
the method returns a path to the model file and the extra data (dict of dataitem objects)
|
mlrun/utils/helpers.py
CHANGED
|
@@ -60,6 +60,7 @@ import mlrun_pipelines.common.constants
|
|
|
60
60
|
import mlrun_pipelines.models
|
|
61
61
|
import mlrun_pipelines.utils
|
|
62
62
|
from mlrun.common.constants import MYSQL_MEDIUMBLOB_SIZE_BYTES
|
|
63
|
+
from mlrun.common.schemas import ArtifactCategories
|
|
63
64
|
from mlrun.config import config
|
|
64
65
|
from mlrun_pipelines.models import PipelineRun
|
|
65
66
|
|
|
@@ -96,6 +97,7 @@ class StorePrefix:
|
|
|
96
97
|
Model = "models"
|
|
97
98
|
Dataset = "datasets"
|
|
98
99
|
Document = "documents"
|
|
100
|
+
LLMPrompt = "llm-prompts"
|
|
99
101
|
|
|
100
102
|
@classmethod
|
|
101
103
|
def is_artifact(cls, prefix):
|
|
@@ -107,6 +109,7 @@ class StorePrefix:
|
|
|
107
109
|
"model": cls.Model,
|
|
108
110
|
"dataset": cls.Dataset,
|
|
109
111
|
"document": cls.Document,
|
|
112
|
+
"llm-prompt": cls.LLMPrompt,
|
|
110
113
|
}
|
|
111
114
|
return kind_map.get(kind, cls.Artifact)
|
|
112
115
|
|
|
@@ -119,6 +122,7 @@ class StorePrefix:
|
|
|
119
122
|
cls.FeatureSet,
|
|
120
123
|
cls.FeatureVector,
|
|
121
124
|
cls.Document,
|
|
125
|
+
cls.LLMPrompt,
|
|
122
126
|
]
|
|
123
127
|
|
|
124
128
|
|
|
@@ -131,7 +135,16 @@ def get_artifact_target(item: dict, project=None):
|
|
|
131
135
|
kind = item.get("kind")
|
|
132
136
|
uid = item["metadata"].get("uid")
|
|
133
137
|
|
|
134
|
-
if
|
|
138
|
+
if (
|
|
139
|
+
kind
|
|
140
|
+
in {
|
|
141
|
+
ArtifactCategories.dataset,
|
|
142
|
+
ArtifactCategories.model,
|
|
143
|
+
ArtifactCategories.llm_prompt,
|
|
144
|
+
"artifact",
|
|
145
|
+
}
|
|
146
|
+
and db_key
|
|
147
|
+
):
|
|
135
148
|
target = (
|
|
136
149
|
f"{DB_SCHEMA}://{StorePrefix.kind_to_prefix(kind)}/{project_str}/{db_key}"
|
|
137
150
|
)
|
|
@@ -880,7 +893,8 @@ def enrich_image_url(
|
|
|
880
893
|
|
|
881
894
|
# Add python version tag if needed
|
|
882
895
|
if image_url == "python" and client_python_version:
|
|
883
|
-
|
|
896
|
+
image_tag = ".".join(client_python_version.split(".")[:2])
|
|
897
|
+
image_url = f"python:{image_tag}"
|
|
884
898
|
|
|
885
899
|
client_version = _convert_python_package_version_to_image_tag(client_version)
|
|
886
900
|
server_version = _convert_python_package_version_to_image_tag(
|
|
@@ -2098,22 +2112,60 @@ def join_urls(base_url: Optional[str], path: Optional[str]) -> str:
|
|
|
2098
2112
|
|
|
2099
2113
|
class Workflow:
|
|
2100
2114
|
@staticmethod
|
|
2101
|
-
def get_workflow_steps(
|
|
2115
|
+
def get_workflow_steps(
|
|
2116
|
+
db: "mlrun.db.RunDBInterface", workflow_id: str, project: str
|
|
2117
|
+
) -> list:
|
|
2102
2118
|
steps = []
|
|
2103
|
-
db = mlrun.get_run_db()
|
|
2104
2119
|
|
|
2105
2120
|
def _add_run_step(_step: mlrun_pipelines.models.PipelineStep):
|
|
2121
|
+
# on kfp 1.8 argo sets the pod hostname differently than what we have with kfp 2.5
|
|
2122
|
+
# therefore, the heuristic needs to change. what we do here is first trying against 1.8 conventions
|
|
2123
|
+
# and if we can't find it then falling back to 2.5
|
|
2106
2124
|
try:
|
|
2107
|
-
|
|
2125
|
+
# runner_pod = x-y-N
|
|
2126
|
+
_runs = db.list_runs(
|
|
2108
2127
|
project=project,
|
|
2109
2128
|
labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={_step.node_name}",
|
|
2110
|
-
)
|
|
2129
|
+
)
|
|
2130
|
+
if not _runs:
|
|
2131
|
+
try:
|
|
2132
|
+
# x-y-N -> x-y, N
|
|
2133
|
+
node_name_initials, node_name_generated_id = (
|
|
2134
|
+
_step.node_name.rsplit("-", 1)
|
|
2135
|
+
)
|
|
2136
|
+
|
|
2137
|
+
except ValueError:
|
|
2138
|
+
# defensive programming, if the node name is not in the expected format
|
|
2139
|
+
node_name_initials = _step.node_name
|
|
2140
|
+
node_name_generated_id = ""
|
|
2141
|
+
|
|
2142
|
+
# compile the expected runner pod hostname as per kfp >= 2.4
|
|
2143
|
+
# x-y, Z, N -> runner_pod = x-y-Z-N
|
|
2144
|
+
runner_pod_value = "-".join(
|
|
2145
|
+
[
|
|
2146
|
+
node_name_initials,
|
|
2147
|
+
_step.display_name,
|
|
2148
|
+
node_name_generated_id,
|
|
2149
|
+
]
|
|
2150
|
+
).rstrip("-")
|
|
2151
|
+
logger.debug(
|
|
2152
|
+
"No run found for step, trying with different node name",
|
|
2153
|
+
step_node_name=runner_pod_value,
|
|
2154
|
+
)
|
|
2155
|
+
_runs = db.list_runs(
|
|
2156
|
+
project=project,
|
|
2157
|
+
labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={runner_pod_value}",
|
|
2158
|
+
)
|
|
2159
|
+
|
|
2160
|
+
_run = _runs[0]
|
|
2111
2161
|
except IndexError:
|
|
2162
|
+
logger.warning("No run found for step", step=_step.to_dict())
|
|
2112
2163
|
_run = {
|
|
2113
2164
|
"metadata": {
|
|
2114
2165
|
"name": _step.display_name,
|
|
2115
2166
|
"project": project,
|
|
2116
2167
|
},
|
|
2168
|
+
"status": {},
|
|
2117
2169
|
}
|
|
2118
2170
|
_run["step_kind"] = _step.step_type
|
|
2119
2171
|
if _step.skipped:
|
|
@@ -2231,9 +2283,9 @@ class Workflow:
|
|
|
2231
2283
|
namespace=mlrun.mlconf.namespace,
|
|
2232
2284
|
)
|
|
2233
2285
|
|
|
2234
|
-
# arbitrary timeout of
|
|
2286
|
+
# arbitrary timeout of 30 seconds, the workflow should be done by now, however sometimes kfp takes a few
|
|
2235
2287
|
# seconds to update the workflow status
|
|
2236
|
-
kfp_run = kfp_client.wait_for_run_completion(workflow_id,
|
|
2288
|
+
kfp_run = kfp_client.wait_for_run_completion(workflow_id, 30)
|
|
2237
2289
|
if not kfp_run:
|
|
2238
2290
|
return None
|
|
2239
2291
|
|
|
@@ -16,6 +16,7 @@ import typing
|
|
|
16
16
|
|
|
17
17
|
import aiohttp
|
|
18
18
|
|
|
19
|
+
import mlrun.common.runtimes.constants as runtimes_constants
|
|
19
20
|
import mlrun.common.schemas
|
|
20
21
|
import mlrun.lists
|
|
21
22
|
import mlrun.utils.helpers
|
|
@@ -177,7 +178,10 @@ class SlackNotification(NotificationBase):
|
|
|
177
178
|
# Only show the URL if the run is not a function (serving or mlrun function)
|
|
178
179
|
kind = run.get("step_kind")
|
|
179
180
|
state = run["status"].get("state", "")
|
|
180
|
-
|
|
181
|
+
|
|
182
|
+
if state != runtimes_constants.RunStates.skipped and (
|
|
183
|
+
url and not kind or kind == "run"
|
|
184
|
+
):
|
|
181
185
|
line = f'<{url}|*{meta.get("name")}*>'
|
|
182
186
|
else:
|
|
183
187
|
line = meta.get("name")
|