mlrun 1.10.0rc18__py3-none-any.whl → 1.11.0rc16__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 (167) hide show
  1. mlrun/__init__.py +24 -3
  2. mlrun/__main__.py +0 -4
  3. mlrun/artifacts/dataset.py +2 -2
  4. mlrun/artifacts/document.py +6 -1
  5. mlrun/artifacts/llm_prompt.py +21 -15
  6. mlrun/artifacts/model.py +3 -3
  7. mlrun/artifacts/plots.py +1 -1
  8. mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
  9. mlrun/auth/nuclio.py +89 -0
  10. mlrun/auth/providers.py +429 -0
  11. mlrun/auth/utils.py +415 -0
  12. mlrun/common/constants.py +14 -0
  13. mlrun/common/model_monitoring/helpers.py +123 -0
  14. mlrun/common/runtimes/constants.py +28 -0
  15. mlrun/common/schemas/__init__.py +14 -3
  16. mlrun/common/schemas/alert.py +2 -2
  17. mlrun/common/schemas/api_gateway.py +3 -0
  18. mlrun/common/schemas/auth.py +12 -10
  19. mlrun/common/schemas/client_spec.py +4 -0
  20. mlrun/common/schemas/constants.py +25 -0
  21. mlrun/common/schemas/frontend_spec.py +1 -8
  22. mlrun/common/schemas/function.py +34 -0
  23. mlrun/common/schemas/hub.py +33 -20
  24. mlrun/common/schemas/model_monitoring/__init__.py +2 -1
  25. mlrun/common/schemas/model_monitoring/constants.py +12 -15
  26. mlrun/common/schemas/model_monitoring/functions.py +13 -4
  27. mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
  28. mlrun/common/schemas/pipeline.py +1 -1
  29. mlrun/common/schemas/secret.py +17 -2
  30. mlrun/common/secrets.py +95 -1
  31. mlrun/common/types.py +10 -10
  32. mlrun/config.py +69 -19
  33. mlrun/data_types/infer.py +2 -2
  34. mlrun/datastore/__init__.py +12 -5
  35. mlrun/datastore/azure_blob.py +162 -47
  36. mlrun/datastore/base.py +274 -10
  37. mlrun/datastore/datastore.py +7 -2
  38. mlrun/datastore/datastore_profile.py +84 -22
  39. mlrun/datastore/model_provider/huggingface_provider.py +225 -41
  40. mlrun/datastore/model_provider/mock_model_provider.py +87 -0
  41. mlrun/datastore/model_provider/model_provider.py +206 -74
  42. mlrun/datastore/model_provider/openai_provider.py +226 -66
  43. mlrun/datastore/s3.py +39 -18
  44. mlrun/datastore/sources.py +1 -1
  45. mlrun/datastore/store_resources.py +4 -4
  46. mlrun/datastore/storeytargets.py +17 -12
  47. mlrun/datastore/targets.py +1 -1
  48. mlrun/datastore/utils.py +25 -6
  49. mlrun/datastore/v3io.py +1 -1
  50. mlrun/db/base.py +63 -32
  51. mlrun/db/httpdb.py +373 -153
  52. mlrun/db/nopdb.py +54 -21
  53. mlrun/errors.py +4 -2
  54. mlrun/execution.py +66 -25
  55. mlrun/feature_store/api.py +1 -1
  56. mlrun/feature_store/common.py +1 -1
  57. mlrun/feature_store/feature_vector_utils.py +1 -1
  58. mlrun/feature_store/steps.py +8 -6
  59. mlrun/frameworks/_common/utils.py +3 -3
  60. mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
  61. mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
  62. mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
  63. mlrun/frameworks/_ml_common/utils.py +2 -1
  64. mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
  65. mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
  66. mlrun/frameworks/onnx/dataset.py +2 -1
  67. mlrun/frameworks/onnx/mlrun_interface.py +2 -1
  68. mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
  69. mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
  70. mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
  71. mlrun/frameworks/pytorch/utils.py +2 -1
  72. mlrun/frameworks/sklearn/metric.py +2 -1
  73. mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
  74. mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
  75. mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
  76. mlrun/hub/__init__.py +52 -0
  77. mlrun/hub/base.py +142 -0
  78. mlrun/hub/module.py +172 -0
  79. mlrun/hub/step.py +113 -0
  80. mlrun/k8s_utils.py +105 -16
  81. mlrun/launcher/base.py +15 -7
  82. mlrun/launcher/local.py +4 -1
  83. mlrun/model.py +14 -4
  84. mlrun/model_monitoring/__init__.py +0 -1
  85. mlrun/model_monitoring/api.py +65 -28
  86. mlrun/model_monitoring/applications/__init__.py +1 -1
  87. mlrun/model_monitoring/applications/base.py +299 -128
  88. mlrun/model_monitoring/applications/context.py +2 -4
  89. mlrun/model_monitoring/controller.py +132 -58
  90. mlrun/model_monitoring/db/_schedules.py +38 -29
  91. mlrun/model_monitoring/db/_stats.py +6 -16
  92. mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
  93. mlrun/model_monitoring/db/tsdb/base.py +29 -9
  94. mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
  95. mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
  96. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
  97. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
  98. mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
  99. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
  100. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
  101. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
  102. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
  103. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
  104. mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
  105. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
  106. mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
  107. mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
  108. mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
  109. mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
  110. mlrun/model_monitoring/features_drift_table.py +2 -1
  111. mlrun/model_monitoring/helpers.py +30 -6
  112. mlrun/model_monitoring/stream_processing.py +34 -28
  113. mlrun/model_monitoring/writer.py +224 -4
  114. mlrun/package/__init__.py +2 -1
  115. mlrun/platforms/__init__.py +0 -43
  116. mlrun/platforms/iguazio.py +8 -4
  117. mlrun/projects/operations.py +17 -11
  118. mlrun/projects/pipelines.py +2 -2
  119. mlrun/projects/project.py +187 -123
  120. mlrun/run.py +95 -21
  121. mlrun/runtimes/__init__.py +2 -186
  122. mlrun/runtimes/base.py +103 -25
  123. mlrun/runtimes/constants.py +225 -0
  124. mlrun/runtimes/daskjob.py +5 -2
  125. mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
  126. mlrun/runtimes/local.py +5 -2
  127. mlrun/runtimes/mounts.py +20 -2
  128. mlrun/runtimes/nuclio/__init__.py +12 -7
  129. mlrun/runtimes/nuclio/api_gateway.py +36 -6
  130. mlrun/runtimes/nuclio/application/application.py +339 -40
  131. mlrun/runtimes/nuclio/function.py +222 -72
  132. mlrun/runtimes/nuclio/serving.py +132 -42
  133. mlrun/runtimes/pod.py +213 -21
  134. mlrun/runtimes/utils.py +49 -9
  135. mlrun/secrets.py +99 -14
  136. mlrun/serving/__init__.py +2 -0
  137. mlrun/serving/remote.py +84 -11
  138. mlrun/serving/routers.py +26 -44
  139. mlrun/serving/server.py +138 -51
  140. mlrun/serving/serving_wrapper.py +6 -2
  141. mlrun/serving/states.py +997 -283
  142. mlrun/serving/steps.py +62 -0
  143. mlrun/serving/system_steps.py +149 -95
  144. mlrun/serving/v2_serving.py +9 -10
  145. mlrun/track/trackers/mlflow_tracker.py +29 -31
  146. mlrun/utils/helpers.py +292 -94
  147. mlrun/utils/http.py +9 -2
  148. mlrun/utils/notifications/notification/base.py +18 -0
  149. mlrun/utils/notifications/notification/git.py +3 -5
  150. mlrun/utils/notifications/notification/mail.py +39 -16
  151. mlrun/utils/notifications/notification/slack.py +2 -4
  152. mlrun/utils/notifications/notification/webhook.py +2 -5
  153. mlrun/utils/notifications/notification_pusher.py +3 -3
  154. mlrun/utils/version/version.json +2 -2
  155. mlrun/utils/version/version.py +3 -4
  156. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
  157. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
  158. mlrun/api/schemas/__init__.py +0 -259
  159. mlrun/db/auth_utils.py +0 -152
  160. mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
  161. mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
  162. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
  163. mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
  164. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
  165. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
  166. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
  167. {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/top_level.txt +0 -0
@@ -12,14 +12,20 @@
12
12
  # See the License for the specific language governing permissions and
13
13
  # limitations under the License.
14
14
  import inspect
15
- from collections.abc import Awaitable
16
- from typing import Callable, Optional, TypeVar, Union
15
+ from collections.abc import Awaitable, Callable
16
+ from typing import TYPE_CHECKING, Any, Optional, Union
17
17
 
18
18
  import mlrun
19
- from mlrun.datastore.model_provider.model_provider import ModelProvider
19
+ from mlrun.datastore.model_provider.model_provider import (
20
+ InvokeResponseFormat,
21
+ ModelProvider,
22
+ UsageResponseKeys,
23
+ )
20
24
  from mlrun.datastore.utils import accepts_param
21
25
 
22
- T = TypeVar("T")
26
+ if TYPE_CHECKING:
27
+ from openai._models import BaseModel # noqa
28
+ from openai.types.chat.chat_completion import ChatCompletion
23
29
 
24
30
 
25
31
  class OpenAIProvider(ModelProvider):
@@ -36,6 +42,7 @@ class OpenAIProvider(ModelProvider):
36
42
  """
37
43
 
38
44
  support_async = True
45
+ response_class = None
39
46
 
40
47
  def __init__(
41
48
  self,
@@ -60,7 +67,31 @@ class OpenAIProvider(ModelProvider):
60
67
  default_invoke_kwargs=default_invoke_kwargs,
61
68
  )
62
69
  self.options = self.get_client_options()
63
- self.load_client()
70
+
71
+ @classmethod
72
+ def _import_response_class(cls) -> None:
73
+ if not cls.response_class:
74
+ try:
75
+ from openai.types.chat.chat_completion import ChatCompletion
76
+ except ImportError as exc:
77
+ raise ImportError("openai package is not installed") from exc
78
+ cls.response_class = ChatCompletion
79
+
80
+ @staticmethod
81
+ def _extract_string_output(response: "ChatCompletion") -> str:
82
+ """
83
+ Extracts the text content of the first choice from an OpenAI ChatCompletion response.
84
+ Only supports responses with a single choice. Raises an error if multiple choices exist.
85
+
86
+ :param response: The ChatCompletion response from OpenAI.
87
+ :return: The text content of the first message in the response.
88
+ :raises MLRunInvalidArgumentError: If the response contains more than one choice.
89
+ """
90
+ if len(response.choices) != 1:
91
+ raise mlrun.errors.MLRunInvalidArgumentError(
92
+ "OpenAIProvider: extracting string from response is only supported for single-response outputs"
93
+ )
94
+ return response.choices[0].message.content
64
95
 
65
96
  @classmethod
66
97
  def parse_endpoint_and_path(cls, endpoint, subpath) -> (str, str):
@@ -70,25 +101,58 @@ class OpenAIProvider(ModelProvider):
70
101
  subpath = ""
71
102
  return endpoint, subpath
72
103
 
73
- def load_client(self) -> None:
104
+ @property
105
+ def client(self) -> Any:
74
106
  """
75
- Initializes the OpenAI SDK client using the provided options.
107
+ Lazily return the synchronous OpenAI client.
76
108
 
77
- This method imports the `OpenAI` class from the `openai` package, instantiates
78
- a client with the given keyword arguments (`self.options`), and assigns it to
79
- `self._client` and `self._async_client`.
109
+ If the client has not been initialized yet, it will be created
110
+ by calling `load_client`.
111
+ """
112
+ self.load_client()
113
+ return self._client
114
+
115
+ def load_client(self) -> None:
116
+ """
117
+ Lazily initialize the synchronous OpenAI client.
80
118
 
81
- Raises:
82
- ImportError: If the `openai` package is not installed.
119
+ The client is created only if it does not already exist.
120
+ Raises ImportError if the openai package is not installed.
83
121
  """
122
+ if self._client:
123
+ return
84
124
  try:
85
- from openai import OpenAI, AsyncOpenAI # noqa
125
+ from openai import OpenAI # noqa
86
126
 
87
127
  self._client = OpenAI(**self.options)
88
- self._async_client = AsyncOpenAI(**self.options)
89
128
  except ImportError as exc:
90
129
  raise ImportError("openai package is not installed") from exc
91
130
 
131
+ def load_async_client(self) -> None:
132
+ """
133
+ Lazily initialize the asynchronous OpenAI client.
134
+
135
+ The client is created only if it does not already exist.
136
+ Raises ImportError if the openai package is not installed.
137
+ """
138
+ if not self._async_client:
139
+ try:
140
+ from openai import AsyncOpenAI # noqa
141
+
142
+ self._async_client = AsyncOpenAI(**self.options)
143
+ except ImportError as exc:
144
+ raise ImportError("openai package is not installed") from exc
145
+
146
+ @property
147
+ def async_client(self) -> Any:
148
+ """
149
+ Return the asynchronous OpenAI client, creating it on first access.
150
+
151
+ The client is lazily initialized via `load_async_client`.
152
+ """
153
+ self.load_async_client()
154
+ return self._async_client
155
+
92
156
  def get_client_options(self) -> dict:
93
157
  res = dict(
94
158
  api_key=self._get_secret_or_env("OPENAI_API_KEY"),
@@ -101,28 +165,40 @@ class OpenAIProvider(ModelProvider):
101
165
  return self._sanitize_options(res)
102
166
 
103
167
  def custom_invoke(
104
- self, operation: Optional[Callable[..., T]] = None, **invoke_kwargs
105
- ) -> Optional[T]:
168
+ self, operation: Optional[Callable] = None, **invoke_kwargs
169
+ ) -> Union["ChatCompletion", "BaseModel"]:
106
170
  """
107
- OpenAI-specific implementation of `ModelProvider.custom_invoke`.
171
+ Invokes a model operation from the OpenAI client with the given keyword arguments.
172
+
173
+ This method provides flexibility to either:
174
+ - Call a specific OpenAI client operation (e.g., `client.images.generate`).
175
+ - Default to `chat.completions.create` when no operation is provided.
108
176
 
109
- Invokes an OpenAI model operation using the sync client. For full details, see
110
- `ModelProvider.custom_invoke`.
177
+ The operation must be a callable that accepts keyword arguments. If the callable
178
+ does not accept a `model` parameter, it will be omitted from the call.
111
179
 
112
180
  Example:
113
181
  ```python
114
- result = openai_model_provider.invoke(
182
+ result = openai_model_provider.custom_invoke(
115
183
  openai_model_provider.client.images.generate,
116
184
  prompt="A futuristic cityscape at sunset",
117
185
  n=1,
118
186
  size="1024x1024",
119
187
  )
120
188
  ```
121
- :param operation: Same as ModelProvider.custom_invoke.
122
- :param invoke_kwargs: Same as ModelProvider.custom_invoke.
123
- :return: Same as ModelProvider.custom_invoke.
124
189
 
190
+ :param operation: A callable representing the OpenAI operation to invoke.
191
+ If not provided, defaults to `client.chat.completions.create`.
192
+
193
+ :param invoke_kwargs: Additional keyword arguments to pass to the operation.
194
+ These are merged with `default_invoke_kwargs` and may
195
+ include parameters such as `temperature`, `max_tokens`,
196
+ or `messages`.
197
+
198
+ :return: The full response returned by the operation, typically
199
+ an OpenAI `ChatCompletion` or other OpenAI SDK model.
125
200
  """
201
+
126
202
  invoke_kwargs = self.get_invoke_kwargs(invoke_kwargs)
127
203
  model_kwargs = {"model": invoke_kwargs.pop("model", None) or self.model}
128
204
 
@@ -139,28 +215,39 @@ class OpenAIProvider(ModelProvider):
139
215
 
140
216
  async def async_custom_invoke(
141
217
  self,
142
- operation: Optional[Callable[..., Awaitable[T]]] = None,
218
+ operation: Optional[Callable[..., Awaitable[Any]]] = None,
143
219
  **invoke_kwargs,
144
- ) -> Optional[T]:
220
+ ) -> Union["ChatCompletion", "BaseModel"]:
145
221
  """
146
- OpenAI-specific implementation of `ModelProvider.async_custom_invoke`.
222
+ Asynchronously invokes a model operation from the OpenAI client with the given keyword arguments.
147
223
 
148
- Invokes an OpenAI model operation using the async client. For full details, see
149
- `ModelProvider.async_custom_invoke`.
224
+ This method provides flexibility to either:
225
+ - Call a specific async OpenAI client operation (e.g., `async_client.images.generate`).
226
+ - Default to `chat.completions.create` when no operation is provided.
227
+
228
+ The operation must be an async callable that accepts keyword arguments.
229
+ If the callable does not accept a `model` parameter, it will be omitted from the call.
150
230
 
151
231
  Example:
152
- ```python
153
- result = openai_model_provider.invoke(
232
+ ```python
233
+ result = await openai_model_provider.async_custom_invoke(
154
234
  openai_model_provider.async_client.images.generate,
155
235
  prompt="A futuristic cityscape at sunset",
156
236
  n=1,
157
237
  size="1024x1024",
158
238
  )
159
- ```
239
+ ```
240
+
241
+ :param operation: An async callable representing the OpenAI operation to invoke.
242
+ If not provided, defaults to `async_client.chat.completions.create`.
160
243
 
161
- :param operation: Same as ModelProvider.async_custom_invoke.
162
- :param invoke_kwargs: Same as ModelProvider.async_custom_invoke.
163
- :return: Same as ModelProvider.async_custom_invoke.
244
+ :param invoke_kwargs: Additional keyword arguments to pass to the operation.
245
+ These are merged with `default_invoke_kwargs` and may
246
+ include parameters such as `temperature`, `max_tokens`,
247
+ or `messages`.
248
+
249
+ :return: The full response returned by the awaited operation,
250
+ typically an OpenAI `ChatCompletion` or other OpenAI SDK model.
164
251
 
165
252
  """
166
253
  invoke_kwargs = self.get_invoke_kwargs(invoke_kwargs)
@@ -178,60 +265,133 @@ class OpenAIProvider(ModelProvider):
178
265
  **invoke_kwargs, **model_kwargs
179
266
  )
180
267
 
268
+ def _response_handler(
269
+ self,
270
+ response: "ChatCompletion",
271
+ invoke_response_format: InvokeResponseFormat = InvokeResponseFormat.FULL,
272
+ **kwargs,
273
+ ) -> ["ChatCompletion", str, dict[str, Any]]:
274
+ if InvokeResponseFormat.is_str_response(invoke_response_format.value):
275
+ str_response = self._extract_string_output(response)
276
+ if invoke_response_format == InvokeResponseFormat.STRING:
277
+ return str_response
278
+ if invoke_response_format == InvokeResponseFormat.USAGE:
279
+ usage = response.to_dict()["usage"]
280
+ response = {
281
+ UsageResponseKeys.ANSWER: str_response,
282
+ UsageResponseKeys.USAGE: usage,
283
+ }
284
+ return response
285
+
181
286
  def invoke(
182
287
  self,
183
- messages: Optional[list[dict]] = None,
184
- as_str: bool = False,
288
+ messages: list[dict],
289
+ invoke_response_format: InvokeResponseFormat = InvokeResponseFormat.FULL,
185
290
  **invoke_kwargs,
186
- ) -> Optional[Union[str, T]]:
291
+ ) -> Union[dict[str, Any], str, "ChatCompletion"]:
187
292
  """
188
293
  OpenAI-specific implementation of `ModelProvider.invoke`.
189
- Invokes an OpenAI model operation using the sync client.
190
- For full details, see `ModelProvider.invoke`.
294
+ Invokes an OpenAI model operation using the synchronous client.
295
+
296
+ :param messages:
297
+ A list of dictionaries representing the conversation history or input messages.
298
+ Each dictionary should follow the format::
299
+ {
300
+ "role": "system" | "user" | "assistant",
301
+ "content": "Message content as a string",
302
+ }
303
+
304
+ Example:
305
+
306
+ .. code-block:: json
307
+
308
+ [
309
+ {"role": "system", "content": "You are a helpful assistant."},
310
+ {"role": "user", "content": "What is the capital of France?"}
311
+ ]
312
+
313
+ Defaults to None if no messages are provided.
314
+
315
+ :param invoke_response_format:
316
+ Specifies the format of the returned response. Options:
191
317
 
192
- :param messages: Same as ModelProvider.invoke.
318
+ - "string": Returns only the generated text content, taken from a single response.
319
+ - "usage": Combines the generated text with metadata (e.g., token usage), returning a dictionary::
193
320
 
194
- :param as_str: bool
195
- If `True`, returns only the main content of the first response
196
- (`response.choices[0].message.content`).
197
- If `False`, returns the full response object, whose type depends on
198
- the specific OpenAI SDK operation used (e.g., chat completion, completion, etc.).
321
+ .. code-block:: json
322
+ {
323
+ "answer": "<generated_text>",
324
+ "usage": <ChatCompletion>.to_dict()["usage"]
325
+ }
326
+
327
+ - "full": Returns the full OpenAI `ChatCompletion` object.
199
328
 
200
329
  :param invoke_kwargs:
201
- Same as ModelProvider.invoke.
202
- :return: Same as ModelProvider.invoke.
330
+ Additional keyword arguments passed to the OpenAI client.
203
331
 
332
+ :return:
333
+ A string, dictionary, or `ChatCompletion` object, depending on `invoke_response_format`.
204
334
  """
335
+
205
336
  response = self.custom_invoke(messages=messages, **invoke_kwargs)
206
- if as_str:
207
- return response.choices[0].message.content
208
- return response
337
+ return self._response_handler(
338
+ messages=messages,
339
+ invoke_response_format=invoke_response_format,
340
+ response=response,
341
+ )
209
342
 
210
343
  async def async_invoke(
211
344
  self,
212
- messages: Optional[list[dict]] = None,
213
- as_str: bool = False,
345
+ messages: list[dict],
346
+ invoke_response_format=InvokeResponseFormat.FULL,
214
347
  **invoke_kwargs,
215
- ) -> str:
348
+ ) -> Union[str, "ChatCompletion", dict]:
216
349
  """
217
350
  OpenAI-specific implementation of `ModelProvider.async_invoke`.
218
- Invokes an OpenAI model operation using the async client.
219
- For full details, see `ModelProvider.async_invoke`.
351
+ Invokes an OpenAI model operation using the asynchronous client.
352
+
353
+ :param messages:
354
+ A list of dictionaries representing the conversation history or input messages.
355
+ Each dictionary should follow the format::
356
+ {
357
+ "role": "system" | "user" | "assistant",
358
+ "content": "Message content as a string",
359
+ }
360
+
361
+ Example:
362
+
363
+ .. code-block:: json
220
364
 
221
- :param messages: Same as ModelProvider.async_invoke.
365
+ [
366
+ {"role": "system", "content": "You are a helpful assistant."},
367
+ {"role": "user", "content": "What is the capital of France?"}
368
+ ]
222
369
 
223
- :param as_str: bool
224
- If `True`, returns only the main content of the first response
225
- (`response.choices[0].message.content`).
226
- If `False`, returns the full awaited response object, whose type depends on
227
- the specific OpenAI SDK operation used (e.g., chat completion, completion, etc.).
370
+ Defaults to None if no messages are provided.
371
+
372
+ :param invoke_response_format:
373
+ Specifies the format of the returned response. Options:
374
+
375
+ - "string": Returns only the generated text content, taken from a single response.
376
+ - "usage": Combines the generated text with metadata (e.g., token usage), returning a dictionary::
377
+
378
+ .. code-block:: json
379
+ {
380
+ "answer": "<generated_text>",
381
+ "usage": <ChatCompletion>.to_dict()["usage"]
382
+ }
383
+
384
+ - "full": Returns the full OpenAI `ChatCompletion` object.
228
385
 
229
386
  :param invoke_kwargs:
230
- Same as ModelProvider.async_invoke.
231
- :returns Same as ModelProvider.async_invoke.
387
+ Additional keyword arguments passed to the OpenAI client.
232
388
 
389
+ :return:
390
+ A string, dictionary, or `ChatCompletion` object, depending on `invoke_response_format`.
233
391
  """
234
392
  response = await self.async_custom_invoke(messages=messages, **invoke_kwargs)
235
- if as_str:
236
- return response.choices[0].message.content
237
- return response
393
+ return self._response_handler(
394
+ messages=messages,
395
+ invoke_response_format=invoke_response_format,
396
+ response=response,
397
+ )
mlrun/datastore/s3.py CHANGED
@@ -13,21 +13,47 @@
13
13
  # limitations under the License.
14
14
 
15
15
  import time
16
+ import warnings
16
17
  from typing import Optional
17
18
  from urllib.parse import urlparse
18
19
 
19
20
  import boto3
21
+ import botocore.exceptions
20
22
  from boto3.s3.transfer import TransferConfig
21
23
  from fsspec.registry import get_filesystem_class
22
24
 
23
25
  import mlrun.errors
24
26
 
25
27
  from .base import DataStore, FileStats, make_datastore_schema_sanitizer
28
+ from .utils import parse_s3_bucket_and_key
29
+
30
+ __all__ = ["parse_s3_bucket_and_key"]
26
31
 
27
32
 
28
33
  class S3Store(DataStore):
29
34
  using_bucket = True
30
35
 
36
+ # TODO: Remove this in 1.12.0
37
+ def _get_endpoint_url_with_deprecation_warning(self):
38
+ """Get S3 endpoint URL with backward compatibility for deprecated S3_ENDPOINT_URL"""
39
+ # First try the new environment variable
40
+ endpoint_url = self._get_secret_or_env("AWS_ENDPOINT_URL_S3")
41
+ if endpoint_url:
42
+ return endpoint_url
43
+
44
+ # Check for deprecated environment variable
45
+ deprecated_endpoint_url = self._get_secret_or_env("S3_ENDPOINT_URL")
46
+ if deprecated_endpoint_url:
47
+ warnings.warn(
48
+ "S3_ENDPOINT_URL is deprecated in 1.10.0 and will be removed in 1.12.0, "
49
+ "use AWS_ENDPOINT_URL_S3 instead.",
50
+ # TODO: Remove this in 1.12.0
51
+ FutureWarning,
52
+ )
53
+ return deprecated_endpoint_url
54
+
55
+ return None
56
+
31
57
  def __init__(
32
58
  self, parent, schema, name, endpoint="", secrets: Optional[dict] = None
33
59
  ):
@@ -41,7 +67,7 @@ class S3Store(DataStore):
41
67
  access_key_id = self._get_secret_or_env("AWS_ACCESS_KEY_ID")
42
68
  secret_key = self._get_secret_or_env("AWS_SECRET_ACCESS_KEY")
43
69
  token_file = self._get_secret_or_env("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
44
- endpoint_url = self._get_secret_or_env("S3_ENDPOINT_URL")
70
+ endpoint_url = self._get_endpoint_url_with_deprecation_warning()
45
71
  force_non_anonymous = self._get_secret_or_env("S3_NON_ANONYMOUS")
46
72
  profile_name = self._get_secret_or_env("AWS_PROFILE")
47
73
  assume_role_arn = self._get_secret_or_env("MLRUN_AWS_ROLE_ARN")
@@ -159,7 +185,7 @@ class S3Store(DataStore):
159
185
  def get_storage_options(self):
160
186
  force_non_anonymous = self._get_secret_or_env("S3_NON_ANONYMOUS")
161
187
  profile = self._get_secret_or_env("AWS_PROFILE")
162
- endpoint_url = self._get_secret_or_env("S3_ENDPOINT_URL")
188
+ endpoint_url = self._get_endpoint_url_with_deprecation_warning()
163
189
  access_key_id = self._get_secret_or_env("AWS_ACCESS_KEY_ID")
164
190
  secret = self._get_secret_or_env("AWS_SECRET_ACCESS_KEY")
165
191
  token_file = self._get_secret_or_env("AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE")
@@ -203,9 +229,17 @@ class S3Store(DataStore):
203
229
  def get(self, key, size=None, offset=0):
204
230
  bucket, key = self.get_bucket_and_key(key)
205
231
  obj = self.s3.Object(bucket, key)
206
- if size or offset:
207
- return obj.get(Range=S3Store.get_range(size, offset))["Body"].read()
208
- return obj.get()["Body"].read()
232
+ try:
233
+ if size or offset:
234
+ return obj.get(Range=S3Store.get_range(size, offset))["Body"].read()
235
+ return obj.get()["Body"].read()
236
+
237
+ except botocore.exceptions.ClientError as exc:
238
+ if exc.response["Error"]["Code"] == "NoSuchKey":
239
+ # "NoSuchKey" errors codes - equivalent to `FileNotFoundError`
240
+ raise FileNotFoundError(f"s3://{bucket}/{key}") from exc
241
+ # Other errors are raised as-is
242
+ raise
209
243
 
210
244
  def put(self, key, data, append=False):
211
245
  data, _ = self._prepare_put_data(data, append)
@@ -237,16 +271,3 @@ class S3Store(DataStore):
237
271
  # In order to raise an error if there is connection error, ML-7056.
238
272
  self.filesystem.exists(path=path)
239
273
  self.filesystem.rm(path=path, recursive=recursive, maxdepth=maxdepth)
240
-
241
-
242
- def parse_s3_bucket_and_key(s3_path):
243
- try:
244
- path_parts = s3_path.replace("s3://", "").split("/")
245
- bucket = path_parts.pop(0)
246
- key = "/".join(path_parts)
247
- except Exception as exc:
248
- raise mlrun.errors.MLRunInvalidArgumentError(
249
- "failed to parse s3 bucket and key"
250
- ) from exc
251
-
252
- return bucket, key
@@ -460,7 +460,7 @@ class ParquetSource(BaseSourceDriver):
460
460
  if not filter_tuple:
461
461
  continue
462
462
  col_name, op, value = filter_tuple
463
- if op.lower() in ("in", "not in") and isinstance(value, (list, tuple, set)):
463
+ if op.lower() in ("in", "not in") and isinstance(value, list | tuple | set):
464
464
  none_exists = False
465
465
  value = list(value)
466
466
  for sub_value in value:
@@ -76,9 +76,9 @@ class ResourceCache:
76
76
  return self._tabels[uri]
77
77
 
78
78
  if uri.startswith("v3io://") or uri.startswith("v3ios://"):
79
- endpoint, uri = parse_path(uri)
79
+ endpoint, path = parse_path(uri)
80
80
  self._tabels[uri] = Table(
81
- uri,
81
+ path,
82
82
  V3ioDriver(webapi=endpoint or mlrun.mlconf.v3io_api),
83
83
  flush_interval_secs=mlrun.mlconf.feature_store.flush_interval,
84
84
  )
@@ -87,10 +87,10 @@ class ResourceCache:
87
87
  if uri.startswith("redis://") or uri.startswith("rediss://"):
88
88
  from storey.redis_driver import RedisDriver
89
89
 
90
- endpoint, uri = parse_path(uri)
90
+ endpoint, path = parse_path(uri)
91
91
  endpoint = endpoint or mlrun.mlconf.redis.url
92
92
  self._tabels[uri] = Table(
93
- uri,
93
+ path,
94
94
  RedisDriver(redis_url=endpoint, key_prefix="/"),
95
95
  flush_interval_secs=mlrun.mlconf.feature_store.flush_interval,
96
96
  )
@@ -18,12 +18,12 @@ from mergedeep import merge
18
18
  from storey import V3ioDriver
19
19
 
20
20
  import mlrun
21
- import mlrun.model_monitoring.helpers
21
+ import mlrun.common.model_monitoring.helpers
22
22
  from mlrun.datastore.base import DataStore
23
23
  from mlrun.datastore.datastore_profile import (
24
- DatastoreProfileKafkaSource,
24
+ DatastoreProfileKafkaStream,
25
25
  DatastoreProfileKafkaTarget,
26
- DatastoreProfileTDEngine,
26
+ DatastoreProfilePostgreSQL,
27
27
  datastore_profile_read,
28
28
  )
29
29
 
@@ -49,17 +49,22 @@ def get_url_and_storage_options(path, external_storage_options=None):
49
49
  return url, DataStore._sanitize_options(storage_options)
50
50
 
51
51
 
52
- class TDEngineStoreyTarget(storey.TDEngineTarget):
52
+ class TimescaleDBStoreyTarget(storey.TimescaleDBTarget):
53
53
  def __init__(self, *args, url: str, **kwargs):
54
54
  if url.startswith("ds://"):
55
55
  datastore_profile = datastore_profile_read(url)
56
- if not isinstance(datastore_profile, DatastoreProfileTDEngine):
57
- raise ValueError(
58
- f"Unexpected datastore profile type:{datastore_profile.type}."
59
- "Only DatastoreProfileTDEngine is supported"
56
+ if not isinstance(datastore_profile, DatastoreProfilePostgreSQL):
57
+ raise mlrun.errors.MLRunInvalidArgumentError(
58
+ f"Unexpected datastore profile type: {type(datastore_profile)}. "
59
+ "Only DatastoreProfilePostgreSQL is supported"
60
60
  )
61
- url = datastore_profile.dsn()
62
- super().__init__(*args, url=url, **kwargs)
61
+ # Use the shared helper to determine the correct database name
62
+ # This ensures consistency with TimescaleDBConnector's database naming
63
+ database = mlrun.common.model_monitoring.helpers.get_tsdb_database_name(
64
+ datastore_profile.database
65
+ )
66
+ url = datastore_profile.dsn(database=database)
67
+ super().__init__(*args, dsn=url, **kwargs)
63
68
 
64
69
 
65
70
  class StoreyTargetUtils:
@@ -138,7 +143,7 @@ class KafkaStoreyTarget(storey.KafkaTarget):
138
143
  datastore_profile = datastore_profile_read(path)
139
144
  if not isinstance(
140
145
  datastore_profile,
141
- (DatastoreProfileKafkaSource, DatastoreProfileKafkaTarget),
146
+ DatastoreProfileKafkaStream | DatastoreProfileKafkaTarget,
142
147
  ):
143
148
  raise mlrun.errors.MLRunInvalidArgumentError(
144
149
  f"Unsupported datastore profile type: {type(datastore_profile)}"
@@ -175,7 +180,7 @@ class RedisNoSqlStoreyTarget(storey.NoSqlTarget):
175
180
  endpoint, uri = mlrun.datastore.targets.RedisNoSqlTarget.get_server_endpoint(
176
181
  path
177
182
  )
178
- kwargs["path"] = endpoint + "/" + uri
183
+ kwargs["path"] = f"{endpoint}/{uri}"
179
184
  super().__init__(*args, **kwargs)
180
185
 
181
186
 
@@ -532,7 +532,7 @@ class BaseStoreTarget(DataTargetBase):
532
532
  if (
533
533
  file_system.protocol == "file"
534
534
  # fsspec 2023.10.0 changed protocol from "file" to ("file", "local")
535
- or isinstance(file_system.protocol, (tuple, list))
535
+ or isinstance(file_system.protocol, tuple | list)
536
536
  and "file" in file_system.protocol
537
537
  ):
538
538
  dir = os.path.dirname(target_path)