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.
- mlrun/__init__.py +24 -3
- mlrun/__main__.py +0 -4
- mlrun/artifacts/dataset.py +2 -2
- mlrun/artifacts/document.py +6 -1
- mlrun/artifacts/llm_prompt.py +21 -15
- mlrun/artifacts/model.py +3 -3
- mlrun/artifacts/plots.py +1 -1
- mlrun/{model_monitoring/db/tsdb/tdengine → auth}/__init__.py +2 -3
- mlrun/auth/nuclio.py +89 -0
- mlrun/auth/providers.py +429 -0
- mlrun/auth/utils.py +415 -0
- mlrun/common/constants.py +14 -0
- mlrun/common/model_monitoring/helpers.py +123 -0
- mlrun/common/runtimes/constants.py +28 -0
- mlrun/common/schemas/__init__.py +14 -3
- mlrun/common/schemas/alert.py +2 -2
- mlrun/common/schemas/api_gateway.py +3 -0
- mlrun/common/schemas/auth.py +12 -10
- mlrun/common/schemas/client_spec.py +4 -0
- mlrun/common/schemas/constants.py +25 -0
- mlrun/common/schemas/frontend_spec.py +1 -8
- mlrun/common/schemas/function.py +34 -0
- mlrun/common/schemas/hub.py +33 -20
- mlrun/common/schemas/model_monitoring/__init__.py +2 -1
- mlrun/common/schemas/model_monitoring/constants.py +12 -15
- mlrun/common/schemas/model_monitoring/functions.py +13 -4
- mlrun/common/schemas/model_monitoring/model_endpoints.py +11 -0
- mlrun/common/schemas/pipeline.py +1 -1
- mlrun/common/schemas/secret.py +17 -2
- mlrun/common/secrets.py +95 -1
- mlrun/common/types.py +10 -10
- mlrun/config.py +69 -19
- mlrun/data_types/infer.py +2 -2
- mlrun/datastore/__init__.py +12 -5
- mlrun/datastore/azure_blob.py +162 -47
- mlrun/datastore/base.py +274 -10
- mlrun/datastore/datastore.py +7 -2
- mlrun/datastore/datastore_profile.py +84 -22
- mlrun/datastore/model_provider/huggingface_provider.py +225 -41
- mlrun/datastore/model_provider/mock_model_provider.py +87 -0
- mlrun/datastore/model_provider/model_provider.py +206 -74
- mlrun/datastore/model_provider/openai_provider.py +226 -66
- mlrun/datastore/s3.py +39 -18
- mlrun/datastore/sources.py +1 -1
- mlrun/datastore/store_resources.py +4 -4
- mlrun/datastore/storeytargets.py +17 -12
- mlrun/datastore/targets.py +1 -1
- mlrun/datastore/utils.py +25 -6
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/base.py +63 -32
- mlrun/db/httpdb.py +373 -153
- mlrun/db/nopdb.py +54 -21
- mlrun/errors.py +4 -2
- mlrun/execution.py +66 -25
- mlrun/feature_store/api.py +1 -1
- mlrun/feature_store/common.py +1 -1
- mlrun/feature_store/feature_vector_utils.py +1 -1
- mlrun/feature_store/steps.py +8 -6
- mlrun/frameworks/_common/utils.py +3 -3
- mlrun/frameworks/_dl_common/loggers/logger.py +1 -1
- mlrun/frameworks/_dl_common/loggers/tensorboard_logger.py +2 -1
- mlrun/frameworks/_ml_common/loggers/mlrun_logger.py +1 -1
- mlrun/frameworks/_ml_common/utils.py +2 -1
- mlrun/frameworks/auto_mlrun/auto_mlrun.py +4 -3
- mlrun/frameworks/lgbm/mlrun_interfaces/mlrun_interface.py +2 -1
- mlrun/frameworks/onnx/dataset.py +2 -1
- mlrun/frameworks/onnx/mlrun_interface.py +2 -1
- mlrun/frameworks/pytorch/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/pytorch/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/frameworks/pytorch/utils.py +2 -1
- mlrun/frameworks/sklearn/metric.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/logging_callback.py +5 -4
- mlrun/frameworks/tf_keras/callbacks/mlrun_logging_callback.py +2 -1
- mlrun/frameworks/tf_keras/callbacks/tensorboard_logging_callback.py +2 -1
- mlrun/hub/__init__.py +52 -0
- mlrun/hub/base.py +142 -0
- mlrun/hub/module.py +172 -0
- mlrun/hub/step.py +113 -0
- mlrun/k8s_utils.py +105 -16
- mlrun/launcher/base.py +15 -7
- mlrun/launcher/local.py +4 -1
- mlrun/model.py +14 -4
- mlrun/model_monitoring/__init__.py +0 -1
- mlrun/model_monitoring/api.py +65 -28
- mlrun/model_monitoring/applications/__init__.py +1 -1
- mlrun/model_monitoring/applications/base.py +299 -128
- mlrun/model_monitoring/applications/context.py +2 -4
- mlrun/model_monitoring/controller.py +132 -58
- mlrun/model_monitoring/db/_schedules.py +38 -29
- mlrun/model_monitoring/db/_stats.py +6 -16
- mlrun/model_monitoring/db/tsdb/__init__.py +9 -7
- mlrun/model_monitoring/db/tsdb/base.py +29 -9
- mlrun/model_monitoring/db/tsdb/preaggregate.py +234 -0
- mlrun/model_monitoring/db/tsdb/stream_graph_steps.py +63 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_metrics_queries.py +414 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_predictions_queries.py +376 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/queries/timescaledb_results_queries.py +590 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connection.py +434 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_connector.py +541 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_operations.py +808 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_schema.py +502 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream.py +163 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/timescaledb_stream_graph_steps.py +60 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_dataframe_processor.py +141 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/utils/timescaledb_query_builder.py +585 -0
- mlrun/model_monitoring/db/tsdb/timescaledb/writer_graph_steps.py +73 -0
- mlrun/model_monitoring/db/tsdb/v3io/stream_graph_steps.py +20 -9
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +235 -51
- mlrun/model_monitoring/features_drift_table.py +2 -1
- mlrun/model_monitoring/helpers.py +30 -6
- mlrun/model_monitoring/stream_processing.py +34 -28
- mlrun/model_monitoring/writer.py +224 -4
- mlrun/package/__init__.py +2 -1
- mlrun/platforms/__init__.py +0 -43
- mlrun/platforms/iguazio.py +8 -4
- mlrun/projects/operations.py +17 -11
- mlrun/projects/pipelines.py +2 -2
- mlrun/projects/project.py +187 -123
- mlrun/run.py +95 -21
- mlrun/runtimes/__init__.py +2 -186
- mlrun/runtimes/base.py +103 -25
- mlrun/runtimes/constants.py +225 -0
- mlrun/runtimes/daskjob.py +5 -2
- mlrun/runtimes/databricks_job/databricks_runtime.py +2 -1
- mlrun/runtimes/local.py +5 -2
- mlrun/runtimes/mounts.py +20 -2
- mlrun/runtimes/nuclio/__init__.py +12 -7
- mlrun/runtimes/nuclio/api_gateway.py +36 -6
- mlrun/runtimes/nuclio/application/application.py +339 -40
- mlrun/runtimes/nuclio/function.py +222 -72
- mlrun/runtimes/nuclio/serving.py +132 -42
- mlrun/runtimes/pod.py +213 -21
- mlrun/runtimes/utils.py +49 -9
- mlrun/secrets.py +99 -14
- mlrun/serving/__init__.py +2 -0
- mlrun/serving/remote.py +84 -11
- mlrun/serving/routers.py +26 -44
- mlrun/serving/server.py +138 -51
- mlrun/serving/serving_wrapper.py +6 -2
- mlrun/serving/states.py +997 -283
- mlrun/serving/steps.py +62 -0
- mlrun/serving/system_steps.py +149 -95
- mlrun/serving/v2_serving.py +9 -10
- mlrun/track/trackers/mlflow_tracker.py +29 -31
- mlrun/utils/helpers.py +292 -94
- mlrun/utils/http.py +9 -2
- mlrun/utils/notifications/notification/base.py +18 -0
- mlrun/utils/notifications/notification/git.py +3 -5
- mlrun/utils/notifications/notification/mail.py +39 -16
- mlrun/utils/notifications/notification/slack.py +2 -4
- mlrun/utils/notifications/notification/webhook.py +2 -5
- mlrun/utils/notifications/notification_pusher.py +3 -3
- mlrun/utils/version/version.json +2 -2
- mlrun/utils/version/version.py +3 -4
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/METADATA +63 -74
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/RECORD +161 -143
- mlrun/api/schemas/__init__.py +0 -259
- mlrun/db/auth_utils.py +0 -152
- mlrun/model_monitoring/db/tsdb/tdengine/schemas.py +0 -344
- mlrun/model_monitoring/db/tsdb/tdengine/stream_graph_steps.py +0 -75
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connection.py +0 -281
- mlrun/model_monitoring/db/tsdb/tdengine/tdengine_connector.py +0 -1266
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc18.dist-info → mlrun-1.11.0rc16.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
104
|
+
@property
|
|
105
|
+
def client(self) -> Any:
|
|
74
106
|
"""
|
|
75
|
-
|
|
107
|
+
Lazily return the synchronous OpenAI client.
|
|
76
108
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
82
|
-
|
|
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
|
|
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
|
|
105
|
-
) ->
|
|
168
|
+
self, operation: Optional[Callable] = None, **invoke_kwargs
|
|
169
|
+
) -> Union["ChatCompletion", "BaseModel"]:
|
|
106
170
|
"""
|
|
107
|
-
OpenAI
|
|
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
|
-
|
|
110
|
-
`
|
|
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.
|
|
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[
|
|
218
|
+
operation: Optional[Callable[..., Awaitable[Any]]] = None,
|
|
143
219
|
**invoke_kwargs,
|
|
144
|
-
) ->
|
|
220
|
+
) -> Union["ChatCompletion", "BaseModel"]:
|
|
145
221
|
"""
|
|
146
|
-
OpenAI
|
|
222
|
+
Asynchronously invokes a model operation from the OpenAI client with the given keyword arguments.
|
|
147
223
|
|
|
148
|
-
|
|
149
|
-
`
|
|
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
|
-
|
|
153
|
-
result = openai_model_provider.
|
|
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
|
|
162
|
-
|
|
163
|
-
|
|
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:
|
|
184
|
-
|
|
288
|
+
messages: list[dict],
|
|
289
|
+
invoke_response_format: InvokeResponseFormat = InvokeResponseFormat.FULL,
|
|
185
290
|
**invoke_kwargs,
|
|
186
|
-
) ->
|
|
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
|
|
190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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:
|
|
213
|
-
|
|
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
|
|
219
|
-
|
|
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
|
-
|
|
365
|
+
[
|
|
366
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
367
|
+
{"role": "user", "content": "What is the capital of France?"}
|
|
368
|
+
]
|
|
222
369
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
mlrun/datastore/sources.py
CHANGED
|
@@ -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,
|
|
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,
|
|
79
|
+
endpoint, path = parse_path(uri)
|
|
80
80
|
self._tabels[uri] = Table(
|
|
81
|
-
|
|
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,
|
|
90
|
+
endpoint, path = parse_path(uri)
|
|
91
91
|
endpoint = endpoint or mlrun.mlconf.redis.url
|
|
92
92
|
self._tabels[uri] = Table(
|
|
93
|
-
|
|
93
|
+
path,
|
|
94
94
|
RedisDriver(redis_url=endpoint, key_prefix="/"),
|
|
95
95
|
flush_interval_secs=mlrun.mlconf.feature_store.flush_interval,
|
|
96
96
|
)
|
mlrun/datastore/storeytargets.py
CHANGED
|
@@ -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
|
-
|
|
24
|
+
DatastoreProfileKafkaStream,
|
|
25
25
|
DatastoreProfileKafkaTarget,
|
|
26
|
-
|
|
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
|
|
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,
|
|
57
|
-
raise
|
|
58
|
-
f"Unexpected datastore profile type:{datastore_profile
|
|
59
|
-
"Only
|
|
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
|
-
|
|
62
|
-
|
|
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
|
-
|
|
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
|
|
183
|
+
kwargs["path"] = f"{endpoint}/{uri}"
|
|
179
184
|
super().__init__(*args, **kwargs)
|
|
180
185
|
|
|
181
186
|
|
mlrun/datastore/targets.py
CHANGED
|
@@ -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,
|
|
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)
|