mlrun 1.10.0rc3__py3-none-any.whl → 1.10.0rc4__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.

Files changed (42) hide show
  1. mlrun/artifacts/__init__.py +1 -0
  2. mlrun/artifacts/base.py +14 -2
  3. mlrun/artifacts/helpers.py +40 -0
  4. mlrun/artifacts/llm_prompt.py +165 -0
  5. mlrun/artifacts/manager.py +13 -1
  6. mlrun/artifacts/model.py +91 -11
  7. mlrun/common/formatters/artifact.py +1 -0
  8. mlrun/common/runtimes/constants.py +0 -14
  9. mlrun/common/schemas/artifact.py +12 -12
  10. mlrun/common/schemas/pipeline.py +0 -16
  11. mlrun/common/schemas/project.py +0 -17
  12. mlrun/common/schemas/runs.py +0 -17
  13. mlrun/config.py +1 -1
  14. mlrun/datastore/base.py +2 -2
  15. mlrun/datastore/datastore.py +1 -1
  16. mlrun/datastore/datastore_profile.py +1 -9
  17. mlrun/datastore/redis.py +2 -3
  18. mlrun/datastore/sources.py +0 -9
  19. mlrun/datastore/storeytargets.py +2 -5
  20. mlrun/datastore/targets.py +6 -56
  21. mlrun/datastore/utils.py +1 -11
  22. mlrun/db/base.py +1 -0
  23. mlrun/db/httpdb.py +6 -0
  24. mlrun/db/nopdb.py +1 -0
  25. mlrun/execution.py +87 -1
  26. mlrun/model.py +0 -5
  27. mlrun/projects/project.py +241 -4
  28. mlrun/run.py +0 -18
  29. mlrun/runtimes/remotesparkjob.py +6 -0
  30. mlrun/runtimes/sparkjob/spark3job.py +6 -0
  31. mlrun/serving/states.py +67 -3
  32. mlrun/serving/v2_serving.py +1 -1
  33. mlrun/utils/helpers.py +58 -7
  34. mlrun/utils/notifications/notification/slack.py +5 -1
  35. mlrun/utils/notifications/notification_pusher.py +2 -1
  36. mlrun/utils/version/version.json +2 -2
  37. {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc4.dist-info}/METADATA +5 -5
  38. {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc4.dist-info}/RECORD +42 -40
  39. {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc4.dist-info}/WHEEL +1 -1
  40. {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc4.dist-info}/entry_points.txt +0 -0
  41. {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc4.dist-info}/licenses/LICENSE +0 -0
  42. {mlrun-1.10.0rc3.dist-info → mlrun-1.10.0rc4.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
 
@@ -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.uri
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
@@ -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 kind in {"dataset", "model", "artifact"} and db_key:
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
  )
@@ -2098,22 +2111,60 @@ def join_urls(base_url: Optional[str], path: Optional[str]) -> str:
2098
2111
 
2099
2112
  class Workflow:
2100
2113
  @staticmethod
2101
- def get_workflow_steps(workflow_id: str, project: str) -> list:
2114
+ def get_workflow_steps(
2115
+ db: "mlrun.db.RunDBInterface", workflow_id: str, project: str
2116
+ ) -> list:
2102
2117
  steps = []
2103
- db = mlrun.get_run_db()
2104
2118
 
2105
2119
  def _add_run_step(_step: mlrun_pipelines.models.PipelineStep):
2120
+ # on kfp 1.8 argo sets the pod hostname differently than what we have with kfp 2.5
2121
+ # therefore, the heuristic needs to change. what we do here is first trying against 1.8 conventions
2122
+ # and if we can't find it then falling back to 2.5
2106
2123
  try:
2107
- _run = db.list_runs(
2124
+ # runner_pod = x-y-N
2125
+ _runs = db.list_runs(
2108
2126
  project=project,
2109
2127
  labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={_step.node_name}",
2110
- )[0]
2128
+ )
2129
+ if not _runs:
2130
+ try:
2131
+ # x-y-N -> x-y, N
2132
+ node_name_initials, node_name_generated_id = (
2133
+ _step.node_name.rsplit("-", 1)
2134
+ )
2135
+
2136
+ except ValueError:
2137
+ # defensive programming, if the node name is not in the expected format
2138
+ node_name_initials = _step.node_name
2139
+ node_name_generated_id = ""
2140
+
2141
+ # compile the expected runner pod hostname as per kfp >= 2.4
2142
+ # x-y, Z, N -> runner_pod = x-y-Z-N
2143
+ runner_pod_value = "-".join(
2144
+ [
2145
+ node_name_initials,
2146
+ _step.display_name,
2147
+ node_name_generated_id,
2148
+ ]
2149
+ ).rstrip("-")
2150
+ logger.debug(
2151
+ "No run found for step, trying with different node name",
2152
+ step_node_name=runner_pod_value,
2153
+ )
2154
+ _runs = db.list_runs(
2155
+ project=project,
2156
+ labels=f"{mlrun_constants.MLRunInternalLabels.runner_pod}={runner_pod_value}",
2157
+ )
2158
+
2159
+ _run = _runs[0]
2111
2160
  except IndexError:
2161
+ logger.warning("No run found for step", step=_step.to_dict())
2112
2162
  _run = {
2113
2163
  "metadata": {
2114
2164
  "name": _step.display_name,
2115
2165
  "project": project,
2116
2166
  },
2167
+ "status": {},
2117
2168
  }
2118
2169
  _run["step_kind"] = _step.step_type
2119
2170
  if _step.skipped:
@@ -2231,9 +2282,9 @@ class Workflow:
2231
2282
  namespace=mlrun.mlconf.namespace,
2232
2283
  )
2233
2284
 
2234
- # arbitrary timeout of 60 seconds, the workflow should be done by now, however sometimes kfp takes a few
2285
+ # arbitrary timeout of 30 seconds, the workflow should be done by now, however sometimes kfp takes a few
2235
2286
  # seconds to update the workflow status
2236
- kfp_run = kfp_client.wait_for_run_completion(workflow_id, 60)
2287
+ kfp_run = kfp_client.wait_for_run_completion(workflow_id, 30)
2237
2288
  if not kfp_run:
2238
2289
  return None
2239
2290
 
@@ -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
- if state != "skipped" and (url and not kind or kind == "run"):
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")
@@ -287,7 +287,8 @@ class NotificationPusher(_NotificationPusherBase):
287
287
  )
288
288
  project = run.metadata.project
289
289
  workflow_id = run.status.results.get("workflow_id", None)
290
- runs.extend(Workflow.get_workflow_steps(workflow_id, project))
290
+ db = mlrun.get_run_db()
291
+ runs.extend(Workflow.get_workflow_steps(db, workflow_id, project))
291
292
 
292
293
  message = (
293
294
  self.messages.get(run.state(), "").format(resource=resource)
@@ -1,4 +1,4 @@
1
1
  {
2
- "git_commit": "210c516a3ed5c2f2c7223f31fdfd9e99b73d56b6",
3
- "version": "1.10.0-rc3"
2
+ "git_commit": "aca543927ff594b8db166e423cb47001dfdf7bcc",
3
+ "version": "1.10.0-rc4"
4
4
  }