mlrun 1.10.0rc13__py3-none-any.whl → 1.10.0rc14__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.

@@ -11,12 +11,13 @@
11
11
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
+ import json
14
15
  import tempfile
15
16
  from typing import Optional, Union
16
17
 
17
18
  import mlrun
18
19
  import mlrun.artifacts.model as model_art
19
- import mlrun.common
20
+ import mlrun.common.schemas
20
21
  from mlrun.artifacts import Artifact, ArtifactMetadata, ArtifactSpec
21
22
  from mlrun.utils import StorePrefix, logger
22
23
 
@@ -25,16 +26,18 @@ MAX_PROMPT_LENGTH = 1024
25
26
 
26
27
  class LLMPromptArtifactSpec(ArtifactSpec):
27
28
  _dict_fields = ArtifactSpec._dict_fields + [
28
- "prompt_string",
29
+ "prompt_template",
29
30
  "prompt_legend",
30
31
  "model_configuration",
31
32
  "description",
32
33
  ]
34
+ PROMPT_TEMPLATE_KEYS = ("content", "role")
35
+ PROMPT_LEGENDS_KEYS = ("field", "description")
33
36
 
34
37
  def __init__(
35
38
  self,
36
39
  model_artifact: Union[model_art.ModelArtifact, str] = None,
37
- prompt_string: Optional[str] = None,
40
+ prompt_template: Optional[list[dict]] = None,
38
41
  prompt_path: Optional[str] = None,
39
42
  prompt_legend: Optional[dict] = None,
40
43
  model_configuration: Optional[dict] = None,
@@ -42,22 +45,26 @@ class LLMPromptArtifactSpec(ArtifactSpec):
42
45
  target_path: Optional[str] = None,
43
46
  **kwargs,
44
47
  ):
45
- if prompt_string and prompt_path:
48
+ if prompt_template and prompt_path:
46
49
  raise mlrun.errors.MLRunInvalidArgumentError(
47
- "Cannot specify both 'prompt_string' and 'prompt_path'"
50
+ "Cannot specify both 'prompt_template' and 'prompt_path'"
48
51
  )
49
-
52
+ if prompt_legend:
53
+ self._verify_prompt_legend(prompt_legend)
54
+ if prompt_path:
55
+ self._verify_prompt_path(prompt_path)
56
+ if prompt_template:
57
+ self._verify_prompt_template(prompt_template)
50
58
  super().__init__(
51
59
  src_path=prompt_path,
52
60
  target_path=target_path,
53
61
  parent_uri=model_artifact.uri
54
62
  if isinstance(model_artifact, model_art.ModelArtifact)
55
63
  else model_artifact,
56
- body=prompt_string,
57
64
  **kwargs,
58
65
  )
59
66
 
60
- self.prompt_string = prompt_string
67
+ self.prompt_template = prompt_template
61
68
  self.prompt_legend = prompt_legend
62
69
  self.model_configuration = model_configuration
63
70
  self.description = description
@@ -67,10 +74,78 @@ class LLMPromptArtifactSpec(ArtifactSpec):
67
74
  else None
68
75
  )
69
76
 
77
+ def _verify_prompt_template(self, prompt_template):
78
+ if not (
79
+ isinstance(prompt_template, list)
80
+ and all(isinstance(item, dict) for item in prompt_template)
81
+ ):
82
+ raise mlrun.errors.MLRunInvalidArgumentError(
83
+ "Expected prompt_template to be a list of dicts"
84
+ )
85
+ keys_to_pop = []
86
+ for message in prompt_template:
87
+ for key in message.keys():
88
+ if isinstance(key, str):
89
+ if key.lower() not in self.PROMPT_TEMPLATE_KEYS:
90
+ raise mlrun.errors.MLRunInvalidArgumentError(
91
+ f"Expected prompt_template to contain dict that "
92
+ f"only has keys from {self.PROMPT_TEMPLATE_KEYS}"
93
+ )
94
+ else:
95
+ if not key.islower():
96
+ message[key.lower()] = message[key]
97
+ keys_to_pop.append(key)
98
+ else:
99
+ raise mlrun.errors.MLRunInvalidArgumentError(
100
+ f"Expected prompt_template to contain dict that only"
101
+ f" has str keys got {key} of type {type(key)}"
102
+ )
103
+ for key_to_pop in keys_to_pop:
104
+ message.pop(key_to_pop)
105
+
70
106
  @property
71
107
  def model_uri(self):
72
108
  return self.parent_uri
73
109
 
110
+ @staticmethod
111
+ def _verify_prompt_legend(prompt_legend: dict):
112
+ if prompt_legend is None:
113
+ return True
114
+ for place_holder, body_map in prompt_legend.items():
115
+ if isinstance(body_map, dict):
116
+ if body_map.get("field") is None:
117
+ body_map["field"] = place_holder
118
+ body_map["description"] = body_map.get("description")
119
+ if diff := set(body_map.keys()) - set(
120
+ LLMPromptArtifactSpec.PROMPT_LEGENDS_KEYS
121
+ ):
122
+ raise mlrun.errors.MLRunInvalidArgumentError(
123
+ "prompt_legend values must contain only 'field' and "
124
+ f"'description' keys, got extra fields: {diff}"
125
+ )
126
+ else:
127
+ raise mlrun.errors.MLRunInvalidArgumentError(
128
+ f"Wrong prompt_legend format, {place_holder} is not mapped to dict"
129
+ )
130
+
131
+ @staticmethod
132
+ def _verify_prompt_path(prompt_path: str):
133
+ with mlrun.datastore.store_manager.object(prompt_path).open(mode="r") as p_file:
134
+ try:
135
+ json.load(p_file)
136
+ except json.JSONDecodeError:
137
+ raise mlrun.errors.MLRunInvalidArgumentError(
138
+ f"Failed on decoding str in path "
139
+ f"{prompt_path} expected file to contain a "
140
+ f"json format."
141
+ )
142
+
143
+ def get_body(self):
144
+ if self.prompt_template:
145
+ return json.dumps(self.prompt_template)
146
+ else:
147
+ return None
148
+
74
149
 
75
150
  class LLMPromptArtifact(Artifact):
76
151
  """
@@ -90,7 +165,7 @@ class LLMPromptArtifact(Artifact):
90
165
  model_artifact: Union[
91
166
  model_art.ModelArtifact, str
92
167
  ] = None, # TODO support partial model uri
93
- prompt_string: Optional[str] = None,
168
+ prompt_template: Optional[list[dict]] = None,
94
169
  prompt_path: Optional[str] = None,
95
170
  prompt_legend: Optional[dict] = None,
96
171
  model_configuration: Optional[dict] = None,
@@ -99,7 +174,7 @@ class LLMPromptArtifact(Artifact):
99
174
  **kwargs,
100
175
  ):
101
176
  llm_prompt_spec = LLMPromptArtifactSpec(
102
- prompt_string=prompt_string,
177
+ prompt_template=prompt_template,
103
178
  prompt_path=prompt_path,
104
179
  prompt_legend=prompt_legend,
105
180
  model_artifact=model_artifact,
@@ -137,33 +212,44 @@ class LLMPromptArtifact(Artifact):
137
212
  return self.spec._model_artifact
138
213
  return None
139
214
 
140
- def read_prompt(self) -> Optional[str]:
215
+ def read_prompt(self) -> Optional[Union[str, list[dict]]]:
141
216
  """
142
- Read the prompt string from the artifact.
217
+ Read the prompt json from the artifact or if provided prompt template.
218
+ @:param as_str: True to return the prompt string or a list of dicts.
219
+ @:return prompt string or list of dicts
143
220
  """
144
- if self.spec.prompt_string:
145
- return self.spec.prompt_string
221
+ if self.spec.prompt_template:
222
+ return self.spec.prompt_template
146
223
  if self.spec.target_path:
147
224
  with mlrun.datastore.store_manager.object(url=self.spec.target_path).open(
148
225
  mode="r"
149
226
  ) as p_file:
150
- return p_file.read()
227
+ try:
228
+ return json.load(p_file)
229
+ except json.JSONDecodeError:
230
+ raise mlrun.errors.MLRunInvalidArgumentError(
231
+ f"Failed on decoding str in path "
232
+ f"{self.spec.target_path} expected file to contain a "
233
+ f"json format."
234
+ )
151
235
 
152
236
  def before_log(self):
153
237
  """
154
238
  Prepare the artifact before logging.
155
239
  This method is called before the artifact is logged.
156
240
  """
157
- if self.spec.prompt_string and len(self.spec.prompt_string) > MAX_PROMPT_LENGTH:
241
+ if (
242
+ self.spec.prompt_template
243
+ and len(str(self.spec.prompt_template)) > MAX_PROMPT_LENGTH
244
+ ):
158
245
  logger.debug(
159
246
  "Prompt string exceeds maximum length, saving to a temporary file."
160
247
  )
161
248
  with tempfile.NamedTemporaryFile(
162
- delete=False, mode="w", suffix=".txt"
249
+ delete=False, mode="w", suffix=".json"
163
250
  ) as temp_file:
164
- temp_file.write(self.spec.prompt_string)
251
+ temp_file.write(json.dumps(self.spec.prompt_template))
165
252
  self.spec.src_path = temp_file.name
166
- self.spec.prompt_string = None
253
+ self.spec.prompt_template = None
167
254
  self._src_is_temp = True
168
-
169
255
  super().before_log()
mlrun/common/constants.py CHANGED
@@ -81,7 +81,6 @@ class MLRunInternalLabels:
81
81
  kind = "kind"
82
82
  component = "component"
83
83
  mlrun_type = "mlrun__type"
84
- rerun_of = "rerun-of"
85
84
  original_workflow_id = "original-workflow-id"
86
85
  workflow_id = "workflow-id"
87
86
 
@@ -49,7 +49,6 @@ class WorkflowRequest(pydantic.v1.BaseModel):
49
49
  class RerunWorkflowRequest(pydantic.v1.BaseModel):
50
50
  run_name: typing.Optional[str] = None
51
51
  run_id: typing.Optional[str] = None
52
- original_workflow_id: typing.Optional[str] = None
53
52
  notifications: typing.Optional[list[Notification]] = None
54
53
  workflow_runner_node_selector: typing.Optional[dict[str, str]] = None
55
54
 
mlrun/execution.py CHANGED
@@ -94,6 +94,7 @@ class MLClientCtx:
94
94
  self._state_thresholds = {}
95
95
  self._retry_spec = {}
96
96
  self._retry_count = None
97
+ self._retries = []
97
98
 
98
99
  self._labels = {}
99
100
  self._annotations = {}
@@ -468,6 +469,7 @@ class MLClientCtx:
468
469
  for key, uri in status.get("artifact_uris", {}).items():
469
470
  self._artifacts_manager.artifact_uris[key] = uri
470
471
  self._retry_count = status.get("retry_count", self._retry_count)
472
+ self._retries = status.get("retries", self._retries)
471
473
  # if run is a retry, the state needs to move to running
472
474
  if include_status:
473
475
  self._state = status.get("state", self._state)
@@ -911,7 +913,7 @@ class MLClientCtx:
911
913
  def log_llm_prompt(
912
914
  self,
913
915
  key,
914
- prompt_string: Optional[str] = None,
916
+ prompt_template: Optional[list[dict]] = None,
915
917
  prompt_path: Optional[str] = None,
916
918
  prompt_legend: Optional[dict] = None,
917
919
  model_artifact: Union[ModelArtifact, str] = None,
@@ -935,7 +937,7 @@ class MLClientCtx:
935
937
  # Log an inline prompt
936
938
  context.log_llm_prompt(
937
939
  key="qa-prompt",
938
- prompt_string="Q: {question}",
940
+ prompt_template=[{"role: "user", "content": "question with {place_holder}"}],
939
941
  model_artifact=model,
940
942
  prompt_legend={"question": "user_input"},
941
943
  model_configuration={"temperature": 0.7, "max_tokens": 128},
@@ -943,10 +945,16 @@ class MLClientCtx:
943
945
  )
944
946
 
945
947
  :param key: Unique name of the artifact.
946
- :param prompt_string: Raw prompt text as a string. Cannot be used with `prompt_path`.
948
+ :param prompt_template: Raw prompt list of dicts -
949
+ [{"role": "system", "content": "You are a {profession} advisor"},
950
+ "role": "user", "content": "I need your help with {profession}"]. only "role" and "content" keys allow in any
951
+ str format (upper/lower case), keys will be modified to lower case.
952
+ Cannot be used with `prompt_path`.
947
953
  :param prompt_path: Path to a file containing the prompt content. Cannot be used with `prompt_string`.
948
954
  :param prompt_legend: A dictionary where each key is a placeholder in the prompt (e.g., ``{user_name}``)
949
- and the value is a description or explanation of what that placeholder represents.
955
+ and the value is a dictionary holding two keys, "field", "description". "field" points to the field in
956
+ the event where the value of the place-holder inside the event, if None or not exist will be replaced
957
+ with the place-holder name. "description" will point to explanation of what that placeholder represents.
950
958
  Useful for documenting and clarifying dynamic parts of the prompt.
951
959
  :param model_artifact: Reference to the parent model (either `ModelArtifact` or model URI string).
952
960
  :param model_configuration: Dictionary of generation parameters (e.g., temperature, max_tokens).
@@ -961,15 +969,15 @@ class MLClientCtx:
961
969
  :returns: The logged `LLMPromptArtifact` object.
962
970
  """
963
971
 
964
- if not prompt_string and not prompt_path:
972
+ if not prompt_template and not prompt_path:
965
973
  raise mlrun.errors.MLRunInvalidArgumentError(
966
- "Either 'prompt_string' or 'prompt_path' must be provided"
974
+ "Either 'prompt_template' or 'prompt_path' must be provided"
967
975
  )
968
976
 
969
977
  llm_prompt = LLMPromptArtifact(
970
978
  key=key,
971
979
  project=self.project or "",
972
- prompt_string=prompt_string,
980
+ prompt_template=prompt_template,
973
981
  prompt_path=prompt_path,
974
982
  prompt_legend=prompt_legend,
975
983
  model_artifact=model_artifact,
@@ -1267,6 +1275,7 @@ class MLClientCtx:
1267
1275
  "start_time": to_date_str(self._start_time),
1268
1276
  "last_update": to_date_str(self._last_update),
1269
1277
  "retry_count": self._retry_count,
1278
+ "retries": self._retries,
1270
1279
  },
1271
1280
  }
1272
1281
 
mlrun/model.py CHANGED
@@ -1375,6 +1375,7 @@ class RunStatus(ModelObj):
1375
1375
  notifications: Optional[dict[str, Notification]] = None,
1376
1376
  artifact_uris: Optional[dict[str, str]] = None,
1377
1377
  retry_count: Optional[int] = None,
1378
+ retries: Optional[list[dict]] = None,
1378
1379
  ):
1379
1380
  self.state = state or "created"
1380
1381
  self.status_text = status_text
@@ -1393,6 +1394,7 @@ class RunStatus(ModelObj):
1393
1394
  # Artifact key -> URI mapping, since the full artifacts are not stored in the runs DB table
1394
1395
  self._artifact_uris = artifact_uris or {}
1395
1396
  self._retry_count = retry_count or None
1397
+ self._retries = retries or []
1396
1398
 
1397
1399
  @classmethod
1398
1400
  def from_dict(
@@ -1461,6 +1463,19 @@ class RunStatus(ModelObj):
1461
1463
  """
1462
1464
  self._retry_count = retry_count
1463
1465
 
1466
+ @property
1467
+ def retries(self) -> list[dict]:
1468
+ """List of metadata for each retry attempt."""
1469
+ return self._retries
1470
+
1471
+ @retries.setter
1472
+ def retries(self, retries: list[dict]):
1473
+ """
1474
+ Set the list of retry attempt metadata.
1475
+ :param retries: A list of dictionaries, each representing a retry attempt.
1476
+ """
1477
+ self._retries = retries
1478
+
1464
1479
  def is_failed(self) -> Optional[bool]:
1465
1480
  """
1466
1481
  This method returns whether a run has failed.
mlrun/projects/project.py CHANGED
@@ -1889,7 +1889,7 @@ class MlrunProject(ModelObj):
1889
1889
  def log_llm_prompt(
1890
1890
  self,
1891
1891
  key,
1892
- prompt_string: Optional[str] = None,
1892
+ prompt_template: Optional[list[dict]] = None,
1893
1893
  prompt_path: Optional[str] = None,
1894
1894
  prompt_legend: Optional[dict] = None,
1895
1895
  model_artifact: Union[ModelArtifact, str] = None,
@@ -1923,10 +1923,16 @@ class MlrunProject(ModelObj):
1923
1923
  )
1924
1924
 
1925
1925
  :param key: Unique key for the prompt artifact.
1926
- :param prompt_string: Raw prompt text. Mutually exclusive with `prompt_path`.
1926
+ :param prompt_template: Raw prompt list of dicts -
1927
+ [{"role": "system", "content": "You are a {profession} advisor"},
1928
+ "role": "user", "content": "I need your help with {profession}"]. only "role" and "content" keys allow in any
1929
+ str format (upper/lower case), keys will be modified to lower case.
1930
+ Cannot be used with `prompt_path`.
1927
1931
  :param prompt_path: Path to a file containing the prompt. Mutually exclusive with `prompt_string`.
1928
1932
  :param prompt_legend: A dictionary where each key is a placeholder in the prompt (e.g., ``{user_name}``)
1929
- and the value is a description or explanation of what that placeholder represents.
1933
+ and the value is a dictionary holding two keys, "field", "description". "field" points to the field in
1934
+ the event where the value of the place-holder inside the event, if None or not exist will be replaced
1935
+ with the place-holder name. "description" will point to explanation of what that placeholder represents.
1930
1936
  Useful for documenting and clarifying dynamic parts of the prompt.
1931
1937
  :param model_artifact: Reference to the parent model (either `ModelArtifact` or model URI string).
1932
1938
  :param model_configuration: Configuration dictionary for model generation parameters
@@ -1942,15 +1948,15 @@ class MlrunProject(ModelObj):
1942
1948
  :returns: The logged `LLMPromptArtifact` object.
1943
1949
  """
1944
1950
 
1945
- if not prompt_string and not prompt_path:
1951
+ if not prompt_template and not prompt_path:
1946
1952
  raise mlrun.errors.MLRunInvalidArgumentError(
1947
- "Either 'prompt_string' or 'prompt_path' must be provided"
1953
+ "Either 'prompt_template' or 'prompt_path' must be provided"
1948
1954
  )
1949
1955
 
1950
1956
  llm_prompt = LLMPromptArtifact(
1951
1957
  key=key,
1952
1958
  project=self.name,
1953
- prompt_string=prompt_string,
1959
+ prompt_template=prompt_template,
1954
1960
  prompt_path=prompt_path,
1955
1961
  prompt_legend=prompt_legend,
1956
1962
  model_artifact=model_artifact,
mlrun/serving/__init__.py CHANGED
@@ -28,6 +28,7 @@ __all__ = [
28
28
  "Model",
29
29
  "ModelSelector",
30
30
  "MonitoredStep",
31
+ "LLModel",
31
32
  ]
32
33
 
33
34
  from .routers import ModelRouter, VotingEnsemble # noqa
@@ -47,6 +48,7 @@ from .states import (
47
48
  Model,
48
49
  ModelSelector,
49
50
  MonitoredStep,
51
+ LLModel,
50
52
  ) # noqa
51
53
  from .v1_serving import MLModelServer, new_v1_model_server # noqa
52
54
  from .v2_serving import V2ModelServer # noqa
mlrun/serving/states.py CHANGED
@@ -1081,6 +1081,7 @@ class Model(storey.ParallelExecutionRunnable, ModelObj):
1081
1081
  "raise_exception",
1082
1082
  "artifact_uri",
1083
1083
  "shared_runnable_name",
1084
+ "shared_proxy_mapping",
1084
1085
  ]
1085
1086
  kind = "model"
1086
1087
 
@@ -1089,12 +1090,16 @@ class Model(storey.ParallelExecutionRunnable, ModelObj):
1089
1090
  name: str,
1090
1091
  raise_exception: bool = True,
1091
1092
  artifact_uri: Optional[str] = None,
1093
+ shared_proxy_mapping: Optional[dict] = None,
1092
1094
  **kwargs,
1093
1095
  ):
1094
1096
  super().__init__(name=name, raise_exception=raise_exception, **kwargs)
1095
1097
  if artifact_uri is not None and not isinstance(artifact_uri, str):
1096
1098
  raise MLRunInvalidArgumentError("'artifact_uri' argument must be a string")
1097
1099
  self.artifact_uri = artifact_uri
1100
+ self.shared_proxy_mapping: dict[
1101
+ str : Union[str, ModelArtifact, LLMPromptArtifact]
1102
+ ] = shared_proxy_mapping
1098
1103
  self.invocation_artifact: Optional[LLMPromptArtifact] = None
1099
1104
  self.model_artifact: Optional[ModelArtifact] = None
1100
1105
  self.model_provider: Optional[ModelProvider] = None
@@ -1125,10 +1130,13 @@ class Model(storey.ParallelExecutionRunnable, ModelObj):
1125
1130
  else:
1126
1131
  self.model_artifact = artifact
1127
1132
 
1128
- def _get_artifact_object(self) -> Union[ModelArtifact, LLMPromptArtifact, None]:
1129
- if self.artifact_uri:
1130
- if mlrun.datastore.is_store_uri(self.artifact_uri):
1131
- artifact, _ = mlrun.store_manager.get_store_artifact(self.artifact_uri)
1133
+ def _get_artifact_object(
1134
+ self, proxy_uri: Optional[str] = None
1135
+ ) -> Union[ModelArtifact, LLMPromptArtifact, None]:
1136
+ uri = proxy_uri or self.artifact_uri
1137
+ if uri:
1138
+ if mlrun.datastore.is_store_uri(uri):
1139
+ artifact, _ = mlrun.store_manager.get_store_artifact(uri)
1132
1140
  return artifact
1133
1141
  else:
1134
1142
  raise ValueError(
@@ -1148,10 +1156,12 @@ class Model(storey.ParallelExecutionRunnable, ModelObj):
1148
1156
  """Override to implement prediction logic if the logic requires asyncio."""
1149
1157
  return body
1150
1158
 
1151
- def run(self, body: Any, path: str) -> Any:
1159
+ def run(self, body: Any, path: str, origin_name: Optional[str] = None) -> Any:
1152
1160
  return self.predict(body)
1153
1161
 
1154
- async def run_async(self, body: Any, path: str) -> Any:
1162
+ async def run_async(
1163
+ self, body: Any, path: str, origin_name: Optional[str] = None
1164
+ ) -> Any:
1155
1165
  return await self.predict_async(body)
1156
1166
 
1157
1167
  def get_local_model_path(self, suffix="") -> (str, dict):
@@ -1186,6 +1196,65 @@ class Model(storey.ParallelExecutionRunnable, ModelObj):
1186
1196
  return None, None
1187
1197
 
1188
1198
 
1199
+ class LLModel(Model):
1200
+ def __init__(self, name: str, **kwargs):
1201
+ super().__init__(name, **kwargs)
1202
+
1203
+ def predict(
1204
+ self, body: Any, messages: list[dict], model_configuration: dict
1205
+ ) -> Any:
1206
+ return body
1207
+
1208
+ async def predict_async(
1209
+ self, body: Any, messages: list[dict], model_configuration: dict
1210
+ ) -> Any:
1211
+ return body
1212
+
1213
+ def run(self, body: Any, path: str, origin_name: Optional[str] = None) -> Any:
1214
+ messages, model_configuration = self.enrich_prompt(body, origin_name)
1215
+ return self.predict(
1216
+ body, messages=messages, model_configuration=model_configuration
1217
+ )
1218
+
1219
+ async def run_async(
1220
+ self, body: Any, path: str, origin_name: Optional[str] = None
1221
+ ) -> Any:
1222
+ messages, model_configuration = self.enrich_prompt(body, origin_name)
1223
+ return await self.predict_async(
1224
+ body, messages=messages, model_configuration=model_configuration
1225
+ )
1226
+
1227
+ def enrich_prompt(
1228
+ self, body: dict, origin_name: str
1229
+ ) -> Union[tuple[list[dict], dict], tuple[None, None]]:
1230
+ if origin_name and self.shared_proxy_mapping:
1231
+ llm_prompt_artifact = self.shared_proxy_mapping.get(origin_name)
1232
+ if isinstance(llm_prompt_artifact, str):
1233
+ llm_prompt_artifact = self._get_artifact_object(llm_prompt_artifact)
1234
+ self.shared_proxy_mapping[origin_name] = llm_prompt_artifact
1235
+ else:
1236
+ llm_prompt_artifact = (
1237
+ self.invocation_artifact or self._get_artifact_object()
1238
+ )
1239
+ if not (
1240
+ llm_prompt_artifact and isinstance(llm_prompt_artifact, LLMPromptArtifact)
1241
+ ):
1242
+ logger.warning(
1243
+ "LLMModel must be provided with LLMPromptArtifact",
1244
+ llm_prompt_artifact=llm_prompt_artifact,
1245
+ )
1246
+ return None, None
1247
+ prompt_legend = llm_prompt_artifact.spec.prompt_legend
1248
+ prompt_template = deepcopy(llm_prompt_artifact.read_prompt())
1249
+ kwargs = {
1250
+ place_holder: body.get(body_map["field"])
1251
+ for place_holder, body_map in prompt_legend.items()
1252
+ }
1253
+ for d in prompt_template:
1254
+ d["content"] = d["content"].format(**kwargs)
1255
+ return prompt_template, llm_prompt_artifact.spec.model_configuration
1256
+
1257
+
1189
1258
  class ModelSelector:
1190
1259
  """Used to select which models to run on each event."""
1191
1260
 
@@ -1292,6 +1361,7 @@ class ModelRunnerStep(MonitoredStep):
1292
1361
  """
1293
1362
 
1294
1363
  kind = "model_runner"
1364
+ _dict_fields = MonitoredStep._dict_fields + ["_shared_proxy_mapping"]
1295
1365
 
1296
1366
  def __init__(
1297
1367
  self,
@@ -1311,6 +1381,7 @@ class ModelRunnerStep(MonitoredStep):
1311
1381
  )
1312
1382
  self.raise_exception = raise_exception
1313
1383
  self.shape = "folder"
1384
+ self._shared_proxy_mapping = {}
1314
1385
 
1315
1386
  def add_shared_model_proxy(
1316
1387
  self,
@@ -1360,9 +1431,9 @@ class ModelRunnerStep(MonitoredStep):
1360
1431
  in path.
1361
1432
  :param override: bool allow override existing model on the current ModelRunnerStep.
1362
1433
  """
1363
- model_class = Model(
1364
- name=endpoint_name,
1365
- shared_runnable_name=shared_model_name,
1434
+ model_class, model_params = (
1435
+ "mlrun.serving.Model",
1436
+ {"name": endpoint_name, "shared_runnable_name": shared_model_name},
1366
1437
  )
1367
1438
  if isinstance(model_artifact, str):
1368
1439
  model_artifact_uri = model_artifact
@@ -1389,6 +1460,20 @@ class ModelRunnerStep(MonitoredStep):
1389
1460
  f"ModelRunnerStep can only add proxy models that were added to the root flow step, "
1390
1461
  f"model {shared_model_name} is not in the shared models."
1391
1462
  )
1463
+ if shared_model_name not in self._shared_proxy_mapping:
1464
+ self._shared_proxy_mapping[shared_model_name] = {
1465
+ endpoint_name: model_artifact.uri
1466
+ if isinstance(model_artifact, (ModelArtifact, LLMPromptArtifact))
1467
+ else model_artifact
1468
+ }
1469
+ else:
1470
+ self._shared_proxy_mapping[shared_model_name].update(
1471
+ {
1472
+ endpoint_name: model_artifact.uri
1473
+ if isinstance(model_artifact, (ModelArtifact, LLMPromptArtifact))
1474
+ else model_artifact
1475
+ }
1476
+ )
1392
1477
  self.add_model(
1393
1478
  endpoint_name=endpoint_name,
1394
1479
  model_class=model_class,
@@ -1401,6 +1486,7 @@ class ModelRunnerStep(MonitoredStep):
1401
1486
  outputs=outputs,
1402
1487
  input_path=input_path,
1403
1488
  result_path=result_path,
1489
+ **model_params,
1404
1490
  )
1405
1491
 
1406
1492
  def add_model(
@@ -1659,6 +1745,7 @@ class ModelRunnerStep(MonitoredStep):
1659
1745
  model_selector=model_selector,
1660
1746
  runnables=model_objects,
1661
1747
  execution_mechanism_by_runnable_name=execution_mechanism_by_model_name,
1748
+ shared_proxy_mapping=self._shared_proxy_mapping or None,
1662
1749
  name=self.name,
1663
1750
  context=context,
1664
1751
  )
@@ -2494,7 +2581,24 @@ class RootFlowStep(FlowStep):
2494
2581
  max_threads=self.shared_max_threads,
2495
2582
  pool_factor=self.pool_factor,
2496
2583
  )
2497
-
2584
+ monitored_steps = self.get_monitored_steps().values()
2585
+ for monitored_step in monitored_steps:
2586
+ if isinstance(monitored_step, ModelRunnerStep):
2587
+ for model, model_params in self.shared_models.values():
2588
+ if "shared_proxy_mapping" in model_params:
2589
+ model_params["shared_proxy_mapping"].update(
2590
+ deepcopy(
2591
+ monitored_step._shared_proxy_mapping.get(
2592
+ model_params.get("name"), {}
2593
+ )
2594
+ )
2595
+ )
2596
+ else:
2597
+ model_params["shared_proxy_mapping"] = deepcopy(
2598
+ monitored_step._shared_proxy_mapping.get(
2599
+ model_params.get("name"), {}
2600
+ )
2601
+ )
2498
2602
  for model, model_params in self.shared_models.values():
2499
2603
  model = get_class(model, namespace).from_dict(
2500
2604
  model_params, init_with_params=True
@@ -1,4 +1,4 @@
1
1
  {
2
- "git_commit": "9b3d4665fca9a019fc99b6902d8d71ebeff8d664",
3
- "version": "1.10.0-rc13"
2
+ "git_commit": "5f421886e871ccc04e021cd67fc4597e39ab890c",
3
+ "version": "1.10.0-rc14"
4
4
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mlrun
3
- Version: 1.10.0rc13
3
+ Version: 1.10.0rc14
4
4
  Summary: Tracking and config of machine learning runs
5
5
  Home-page: https://github.com/mlrun/mlrun
6
6
  Author: Yaron Haviv
@@ -44,7 +44,7 @@ Requires-Dist: semver~=3.0
44
44
  Requires-Dist: dependency-injector~=4.41
45
45
  Requires-Dist: fsspec<2024.7,>=2023.9.2
46
46
  Requires-Dist: v3iofs~=0.1.17
47
- Requires-Dist: storey~=1.10.7
47
+ Requires-Dist: storey~=1.10.8
48
48
  Requires-Dist: inflection~=0.5.0
49
49
  Requires-Dist: python-dotenv~=1.0
50
50
  Requires-Dist: setuptools>=75.2
@@ -2,11 +2,11 @@ mlrun/__init__.py,sha256=Y_AFhZV1hEx4vfiO-cyjup0aLGcp6R0SeL75GqLFQrc,7514
2
2
  mlrun/__main__.py,sha256=wQNaxW7QsqFBtWffnPkw-497fnpsrQzUnscBQQAP_UM,48364
3
3
  mlrun/config.py,sha256=dmJj0Yzd0ZpNf10gsjS-19UqJhuBkoOfhf2SoQTNqcg,72398
4
4
  mlrun/errors.py,sha256=bAk0t_qmCxQSPNK0TugOAfA5R6f0G6OYvEvXUWSJ_5U,9062
5
- mlrun/execution.py,sha256=CdxLlhn8q7-IhP3QVAy8nnbo_02V_NodVueB0-MAfoo,56187
5
+ mlrun/execution.py,sha256=dJ4PFwg5AlDHbCL2Q9dVDjWA_i64UTq2qBiF8kTU9tw,56922
6
6
  mlrun/features.py,sha256=jMEXo6NB36A6iaxNEJWzdtYwUmglYD90OIKTIEeWhE8,15841
7
7
  mlrun/k8s_utils.py,sha256=mMnGyouHoJC93ZD2KGf9neJM1pD7mR9IXLnHOEwYVTQ,21469
8
8
  mlrun/lists.py,sha256=OlaV2QIFUzmenad9kxNJ3k4whlDyxI3zFbGwr6vpC5Y,8561
9
- mlrun/model.py,sha256=Bd9mXtPIJNViNRWm4Lzoc1mpDkCpOfDY6TONMmVB4DI,88484
9
+ mlrun/model.py,sha256=wHtM8LylSOEFk6Hxl95CVm8DOPhofjsANYdIvKHH6dw,88956
10
10
  mlrun/render.py,sha256=5DlhD6JtzHgmj5RVlpaYiHGhX84Q7qdi4RCEUj2UMgw,13195
11
11
  mlrun/run.py,sha256=_ban8NoNWfQHps3QIVyWh_Hn2S6usNrFBTUMObaeueo,46904
12
12
  mlrun/secrets.py,sha256=dZPdkc_zzfscVQepOHUwmzFqnBavDCBXV9DQoH_eIYM,7800
@@ -18,12 +18,12 @@ mlrun/artifacts/base.py,sha256=G6t1HMAQW9Ct24EQnENMYglRPbNUfhoHkKxWdq0YQcI,29683
18
18
  mlrun/artifacts/dataset.py,sha256=bhb5Kfbs8P28yjnpN76th5lLEUl5nAqD4VqVzHEVPrM,16421
19
19
  mlrun/artifacts/document.py,sha256=p5HsWdmIIJ0NahS7y3EEQN2tfHtUrUmUG-8BEEyi_Jc,17373
20
20
  mlrun/artifacts/helpers.py,sha256=ejTEC9vkI2w5FHn5Gopw3VEIxuni0bazWUnR6BBWZfU,1662
21
- mlrun/artifacts/llm_prompt.py,sha256=xp1lvqogtDsAtEi3JXs1ZwrhSpxWpQgsEMyL_CJIrVA,5546
21
+ mlrun/artifacts/llm_prompt.py,sha256=uP_uq-SpbVs9uV9fFG3yF9e_X4XuXYt_EHAu4feaBfQ,9414
22
22
  mlrun/artifacts/manager.py,sha256=DEIQBfQhaondfChjmEN-zt-dBvV90yHLzIVqd4oGh00,16827
23
23
  mlrun/artifacts/model.py,sha256=8EVaD70SOkTohQIWqkDk0MEwskdofxs3wJTgspa2sho,25615
24
24
  mlrun/artifacts/plots.py,sha256=wmaxVXiAPSCyn3M7pIlcBu9pP3O8lrq0Ewx6iHRDF9s,4238
25
25
  mlrun/common/__init__.py,sha256=kXGBqhLN0rlAx0kTXhozGzFsIdSqW0uTSKMmsLgq_is,569
26
- mlrun/common/constants.py,sha256=2V-kw9Iq5KUONuxM8ngdGFBtyEB_-KDGEJzUt994Fp8,4059
26
+ mlrun/common/constants.py,sha256=tWqaog0fe3ZU6sIGToB8Joo7AY_3QjpnxA2GkiiAtj8,4033
27
27
  mlrun/common/helpers.py,sha256=DIdqs_eN3gO5bZ8iFobIvx8cEiOxYxhFIyut6-O69T0,1385
28
28
  mlrun/common/secrets.py,sha256=8g9xtIw-9DGcwiZRT62a5ozSQM-aYo8yK5Ghey9WM0g,5179
29
29
  mlrun/common/types.py,sha256=1gxThbmC0Vd0U1ffIkEwz4T4S7JOgHt70rvw8TCO21c,1073
@@ -73,7 +73,7 @@ mlrun/common/schemas/schedule.py,sha256=L7z9Lp06-xmFmdp0q5PypCU_DCl6zZIyQTVoJa01
73
73
  mlrun/common/schemas/secret.py,sha256=Td2UAeWHSAdA4nIP3rQv_PIVKVqcBnCnK6xjr528tS8,1486
74
74
  mlrun/common/schemas/serving.py,sha256=-3U45YLtmVWMZrx4R8kaPgFGoJ4JmD7RE3nydpYNTz8,1359
75
75
  mlrun/common/schemas/tag.py,sha256=1wqEiAujsElojWb3qmuyfcaLFjXSNAAQdafkDx7fkn0,891
76
- mlrun/common/schemas/workflow.py,sha256=emoUaBD_53pzrjglzTjSknCqvyx3_huj04wBp24G9fs,2432
76
+ mlrun/common/schemas/workflow.py,sha256=4KeTUIZCkIgEIKNDbMeJqyhUmIKvLdX1bQSNsmYMCwg,2378
77
77
  mlrun/common/schemas/model_monitoring/__init__.py,sha256=K9XumcIsTxdp8oNvCSluBGCS07rbJibHcA1DSg8Xe4w,1877
78
78
  mlrun/common/schemas/model_monitoring/constants.py,sha256=yjTaSGiRs0zYIE20QSuJuMNnS5iuJpnV1wBiq7leVpg,13238
79
79
  mlrun/common/schemas/model_monitoring/functions.py,sha256=VvbsW8UxD-Raj3gaLpHzEierXl_yA9PO11r1ps4fJZ4,2204
@@ -277,7 +277,7 @@ mlrun/platforms/iguazio.py,sha256=6VBTq8eQ3mzT96tzjYhAtcMQ2VjF4x8LpIPW5DAcX2Q,13
277
277
  mlrun/projects/__init__.py,sha256=hdCOA6_fp8X4qGGGT7Bj7sPbkM1PayWuaVZL0DkpuZw,1240
278
278
  mlrun/projects/operations.py,sha256=Rc__P5ucNAY2G-lHc2LrnZs15PUbNFt8-NqNNT2Bjpk,20623
279
279
  mlrun/projects/pipelines.py,sha256=kY5BUHAjNri-9KjWZiCZ9Wo5XwZFqpvqctWy5j8va60,51611
280
- mlrun/projects/project.py,sha256=Yhy2mnbWqpDiIBqH6jOZ8yFh4vbqs1Q-_Lkd0kQD4q0,252388
280
+ mlrun/projects/project.py,sha256=QaMjKRHh2nQtb-VrTtQTUQ-1KaYegebiTSDAoRicrA4,252950
281
281
  mlrun/runtimes/__init__.py,sha256=8cqrYKy1a0_87XG7V_p96untQ4t8RocadM4LVEEN1JM,9029
282
282
  mlrun/runtimes/base.py,sha256=FVEooeQMpwxIK2iW1R0FNbC5P1sZ_efKtJcsdNSYNmc,38266
283
283
  mlrun/runtimes/daskjob.py,sha256=zuWnFLgiPoYFMRSLYiwwG2MpFYKK662_ekbvu2VKvdQ,19906
@@ -307,13 +307,13 @@ mlrun/runtimes/nuclio/application/application.py,sha256=3WeVCeVUb6U5wJDVJSuTDzJ-
307
307
  mlrun/runtimes/nuclio/application/reverse_proxy.go,sha256=lEHH74vr2PridIHp1Jkc_NjkrWb5b6zawRrNxHQhwGU,2913
308
308
  mlrun/runtimes/sparkjob/__init__.py,sha256=GPP_ekItxiU9Ydn3mJa4Obph02Bg6DO-JYs791_MV58,607
309
309
  mlrun/runtimes/sparkjob/spark3job.py,sha256=5TdmQy5yDBtaq9y9fQGrNYTJ_0UqR9VnV7-SGiZEOyc,41287
310
- mlrun/serving/__init__.py,sha256=1MjUInuyxsF-dTHZuKelq2XLhg2GInH9LjAY3PcWEzs,1364
310
+ mlrun/serving/__init__.py,sha256=nriJAcVn5aatwU03T7SsE6ngJEGTxr3wIGt4WuvCCzY,1392
311
311
  mlrun/serving/merger.py,sha256=pfOQoozUyObCTpqXAMk94PmhZefn4bBrKufO3MKnkAc,6193
312
312
  mlrun/serving/remote.py,sha256=Igha2FipK3-6rV_PZ1K464kTbiTu8rhc6SMm-HiEJ6o,18817
313
313
  mlrun/serving/routers.py,sha256=SmBOlHn7rT2gWTa-W8f16UB0UthgIFc4D1cPOZAA9ss,54003
314
314
  mlrun/serving/server.py,sha256=NXqpuNMiIjavwhG8lwBKLVLh9QarP6DJm_0qB4pStfY,32523
315
315
  mlrun/serving/serving_wrapper.py,sha256=UL9hhWCfMPcTJO_XrkvNaFvck1U1E7oS8trTZyak0cA,835
316
- mlrun/serving/states.py,sha256=ctbHE7y4NvBa2PzcNyVHUtjY49J5112UMO7uMnRLdHU,115536
316
+ mlrun/serving/states.py,sha256=ScjJ7hecu-PptrN8jSN06i6NrjjHS0P0AV1c6SdhhIY,120093
317
317
  mlrun/serving/system_steps.py,sha256=9AqSQwv6nVljGKZoWJbksnuqsl3VqETcytEwjEVLmA4,16446
318
318
  mlrun/serving/utils.py,sha256=Zbfqm8TKNcTE8zRBezVBzpvR2WKeKeIRN7otNIaiYEc,4170
319
319
  mlrun/serving/v1_serving.py,sha256=c6J_MtpE-Tqu00-6r4eJOCO6rUasHDal9W2eBIcrl50,11853
@@ -347,11 +347,11 @@ mlrun/utils/notifications/notification/mail.py,sha256=ZyJ3eqd8simxffQmXzqd3bgbAq
347
347
  mlrun/utils/notifications/notification/slack.py,sha256=kfhogR5keR7Zjh0VCjJNK3NR5_yXT7Cv-x9GdOUW4Z8,7294
348
348
  mlrun/utils/notifications/notification/webhook.py,sha256=zxh8CAlbPnTazsk6r05X5TKwqUZVOH5KBU2fJbzQlG4,5330
349
349
  mlrun/utils/version/__init__.py,sha256=YnzE6tlf24uOQ8y7Z7l96QLAI6-QEii7-77g8ynmzy0,613
350
- mlrun/utils/version/version.json,sha256=I0d-Zr8pj4sVMPmU8xjd5QW6mgwiBqvk6IZ11EKj5OI,90
350
+ mlrun/utils/version/version.json,sha256=tQy-mD_fOGta6_Eg7aN0kFOHik3ux1rqs6L2E8cyYXE,90
351
351
  mlrun/utils/version/version.py,sha256=M2hVhRrgkN3SxacZHs3ZqaOsqAA7B6a22ne324IQ1HE,1877
352
- mlrun-1.10.0rc13.dist-info/licenses/LICENSE,sha256=zTiv1CxWNkOk1q8eJS1G_8oD4gWpWLwWxj_Agcsi8Os,11337
353
- mlrun-1.10.0rc13.dist-info/METADATA,sha256=CFjTSGQmViUQjTMCf8wVnyanD-4f79aHVjvc8so-e2Q,26411
354
- mlrun-1.10.0rc13.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
355
- mlrun-1.10.0rc13.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
356
- mlrun-1.10.0rc13.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
357
- mlrun-1.10.0rc13.dist-info/RECORD,,
352
+ mlrun-1.10.0rc14.dist-info/licenses/LICENSE,sha256=zTiv1CxWNkOk1q8eJS1G_8oD4gWpWLwWxj_Agcsi8Os,11337
353
+ mlrun-1.10.0rc14.dist-info/METADATA,sha256=IxvfEPLAicKlw4neullhNBCn0p9_IYmpULcZA_iyPOA,26411
354
+ mlrun-1.10.0rc14.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
355
+ mlrun-1.10.0rc14.dist-info/entry_points.txt,sha256=1Owd16eAclD5pfRCoJpYC2ZJSyGNTtUr0nCELMioMmU,46
356
+ mlrun-1.10.0rc14.dist-info/top_level.txt,sha256=NObLzw3maSF9wVrgSeYBv-fgnHkAJ1kEkh12DLdd5KM,6
357
+ mlrun-1.10.0rc14.dist-info/RECORD,,