mlrun 1.10.0rc11__py3-none-any.whl → 1.10.0rc13__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 +2 -1
- mlrun/__main__.py +7 -1
- mlrun/artifacts/base.py +9 -3
- mlrun/artifacts/dataset.py +2 -1
- mlrun/artifacts/llm_prompt.py +6 -2
- mlrun/artifacts/model.py +2 -2
- mlrun/common/constants.py +1 -0
- mlrun/common/runtimes/constants.py +10 -1
- mlrun/common/schemas/__init__.py +1 -1
- mlrun/common/schemas/model_monitoring/model_endpoints.py +1 -1
- mlrun/common/schemas/serving.py +7 -0
- mlrun/config.py +21 -2
- mlrun/datastore/__init__.py +3 -1
- mlrun/datastore/alibaba_oss.py +1 -1
- mlrun/datastore/azure_blob.py +1 -1
- mlrun/datastore/base.py +6 -31
- mlrun/datastore/datastore.py +109 -33
- mlrun/datastore/datastore_profile.py +31 -0
- mlrun/datastore/dbfs_store.py +1 -1
- mlrun/datastore/google_cloud_storage.py +2 -2
- mlrun/datastore/model_provider/__init__.py +13 -0
- mlrun/datastore/model_provider/model_provider.py +160 -0
- mlrun/datastore/model_provider/openai_provider.py +144 -0
- mlrun/datastore/remote_client.py +65 -0
- mlrun/datastore/s3.py +1 -1
- mlrun/datastore/storeytargets.py +1 -1
- mlrun/datastore/utils.py +22 -0
- mlrun/datastore/v3io.py +1 -1
- mlrun/db/base.py +1 -1
- mlrun/db/httpdb.py +9 -4
- mlrun/db/nopdb.py +1 -1
- mlrun/execution.py +28 -7
- mlrun/launcher/base.py +23 -13
- mlrun/launcher/local.py +3 -1
- mlrun/launcher/remote.py +4 -2
- mlrun/model.py +65 -0
- mlrun/model_monitoring/db/tsdb/v3io/v3io_connector.py +175 -8
- mlrun/package/packagers_manager.py +2 -0
- mlrun/projects/operations.py +8 -1
- mlrun/projects/pipelines.py +40 -18
- mlrun/projects/project.py +28 -5
- mlrun/run.py +42 -2
- mlrun/runtimes/__init__.py +6 -0
- mlrun/runtimes/base.py +24 -6
- mlrun/runtimes/daskjob.py +1 -0
- mlrun/runtimes/databricks_job/databricks_runtime.py +1 -0
- mlrun/runtimes/local.py +1 -6
- mlrun/serving/server.py +1 -2
- mlrun/serving/states.py +438 -23
- mlrun/serving/system_steps.py +27 -29
- mlrun/utils/helpers.py +13 -2
- mlrun/utils/notifications/notification_pusher.py +15 -0
- mlrun/utils/version/version.json +2 -2
- {mlrun-1.10.0rc11.dist-info → mlrun-1.10.0rc13.dist-info}/METADATA +2 -2
- {mlrun-1.10.0rc11.dist-info → mlrun-1.10.0rc13.dist-info}/RECORD +59 -55
- {mlrun-1.10.0rc11.dist-info → mlrun-1.10.0rc13.dist-info}/WHEEL +0 -0
- {mlrun-1.10.0rc11.dist-info → mlrun-1.10.0rc13.dist-info}/entry_points.txt +0 -0
- {mlrun-1.10.0rc11.dist-info → mlrun-1.10.0rc13.dist-info}/licenses/LICENSE +0 -0
- {mlrun-1.10.0rc11.dist-info → mlrun-1.10.0rc13.dist-info}/top_level.txt +0 -0
|
@@ -456,6 +456,36 @@ class DatastoreProfileTDEngine(DatastoreProfile):
|
|
|
456
456
|
)
|
|
457
457
|
|
|
458
458
|
|
|
459
|
+
class OpenAIProfile(DatastoreProfile):
|
|
460
|
+
type: str = pydantic.v1.Field("openai")
|
|
461
|
+
_private_attributes = "api_key"
|
|
462
|
+
api_key: typing.Optional[str] = None
|
|
463
|
+
organization: typing.Optional[str] = None
|
|
464
|
+
project: typing.Optional[str] = None
|
|
465
|
+
base_url: typing.Optional[str] = None
|
|
466
|
+
timeout: typing.Optional[float] = None
|
|
467
|
+
max_retries: typing.Optional[int] = None
|
|
468
|
+
|
|
469
|
+
def secrets(self) -> dict:
|
|
470
|
+
res = {}
|
|
471
|
+
if self.api_key:
|
|
472
|
+
res["OPENAI_API_KEY"] = self.api_key
|
|
473
|
+
if self.organization:
|
|
474
|
+
res["OPENAI_ORG_ID"] = self.organization
|
|
475
|
+
if self.project:
|
|
476
|
+
res["OPENAI_PROJECT_ID"] = self.project
|
|
477
|
+
if self.base_url:
|
|
478
|
+
res["OPENAI_BASE_URL"] = self.base_url
|
|
479
|
+
if self.timeout:
|
|
480
|
+
res["OPENAI_TIMEOUT"] = self.timeout
|
|
481
|
+
if self.max_retries:
|
|
482
|
+
res["OPENAI_MAX_RETRIES"] = self.max_retries
|
|
483
|
+
return res
|
|
484
|
+
|
|
485
|
+
def url(self, subpath):
|
|
486
|
+
return f"{self.type}://{subpath.lstrip('/')}"
|
|
487
|
+
|
|
488
|
+
|
|
459
489
|
_DATASTORE_TYPE_TO_PROFILE_CLASS: dict[str, type[DatastoreProfile]] = {
|
|
460
490
|
"v3io": DatastoreProfileV3io,
|
|
461
491
|
"s3": DatastoreProfileS3,
|
|
@@ -469,6 +499,7 @@ _DATASTORE_TYPE_TO_PROFILE_CLASS: dict[str, type[DatastoreProfile]] = {
|
|
|
469
499
|
"hdfs": DatastoreProfileHdfs,
|
|
470
500
|
"taosws": DatastoreProfileTDEngine,
|
|
471
501
|
"config": ConfigProfile,
|
|
502
|
+
"openai": OpenAIProfile,
|
|
472
503
|
}
|
|
473
504
|
|
|
474
505
|
|
mlrun/datastore/dbfs_store.py
CHANGED
|
@@ -104,7 +104,7 @@ class DBFSStore(DataStore):
|
|
|
104
104
|
token=self._get_secret_or_env("DATABRICKS_TOKEN"),
|
|
105
105
|
instance=self._get_secret_or_env("DATABRICKS_HOST"),
|
|
106
106
|
)
|
|
107
|
-
return self.
|
|
107
|
+
return self._sanitize_options(res)
|
|
108
108
|
|
|
109
109
|
def _verify_filesystem_and_key(self, key: str):
|
|
110
110
|
if not self.filesystem:
|
|
@@ -105,12 +105,12 @@ class GoogleCloudStorageStore(DataStore):
|
|
|
105
105
|
except json.JSONDecodeError:
|
|
106
106
|
# If it's not json, handle it as a filename
|
|
107
107
|
token = credentials
|
|
108
|
-
return self.
|
|
108
|
+
return self._sanitize_options(dict(token=token))
|
|
109
109
|
else:
|
|
110
110
|
logger.info(
|
|
111
111
|
"No GCS credentials available - auth will rely on auto-discovery of credentials"
|
|
112
112
|
)
|
|
113
|
-
return self.
|
|
113
|
+
return self._sanitize_options(None)
|
|
114
114
|
|
|
115
115
|
def get_storage_options(self):
|
|
116
116
|
return self.storage_options
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Copyright 2023 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Copyright 2025 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
from collections.abc import Awaitable
|
|
15
|
+
from typing import Callable, Optional, TypeVar, Union
|
|
16
|
+
|
|
17
|
+
import mlrun.errors
|
|
18
|
+
from mlrun.datastore.remote_client import (
|
|
19
|
+
BaseRemoteClient,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
T = TypeVar("T")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ModelProvider(BaseRemoteClient):
|
|
26
|
+
"""
|
|
27
|
+
The ModelProvider class is an abstract base for integrating with external
|
|
28
|
+
model providers, primarily generative AI (GenAI) services.
|
|
29
|
+
|
|
30
|
+
Designed to be subclassed, it defines a consistent interface and shared
|
|
31
|
+
functionality for tasks such as text generation, embeddings, and invoking
|
|
32
|
+
fine-tuned models. Subclasses should implement provider-specific logic,
|
|
33
|
+
including SDK client initialization, model invocation, and custom operations.
|
|
34
|
+
|
|
35
|
+
Key Features:
|
|
36
|
+
- Establishes a consistent, reusable client management for model provider integrations.
|
|
37
|
+
- Simplifies GenAI service integration by abstracting common operations.
|
|
38
|
+
- Reduces duplication through shared components for common tasks.
|
|
39
|
+
- Holds default invocation parameters (e.g., temperature, max_tokens) to avoid boilerplate
|
|
40
|
+
code and promote consistency.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
support_async = False
|
|
44
|
+
|
|
45
|
+
def __init__(
|
|
46
|
+
self,
|
|
47
|
+
parent,
|
|
48
|
+
kind,
|
|
49
|
+
name,
|
|
50
|
+
endpoint="",
|
|
51
|
+
secrets: Optional[dict] = None,
|
|
52
|
+
default_invoke_kwargs: Optional[dict] = None,
|
|
53
|
+
):
|
|
54
|
+
super().__init__(
|
|
55
|
+
parent=parent, name=name, kind=kind, endpoint=endpoint, secrets=secrets
|
|
56
|
+
)
|
|
57
|
+
self.default_invoke_kwargs = default_invoke_kwargs or {}
|
|
58
|
+
self._client = None
|
|
59
|
+
self._default_operation = None
|
|
60
|
+
self._async_client = None
|
|
61
|
+
self._default_async_operation = None
|
|
62
|
+
|
|
63
|
+
def load_client(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Initializes the SDK client for the model provider with the given keyword arguments
|
|
66
|
+
and assigns it to an instance attribute (e.g., self._client).
|
|
67
|
+
|
|
68
|
+
Subclasses should override this method to:
|
|
69
|
+
- Create and configure the provider-specific client instance.
|
|
70
|
+
- Assign the client instance to self._client.
|
|
71
|
+
- Define a default operation callable (e.g., a method to invoke model completions)
|
|
72
|
+
and assign it to self._default_operation.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
raise NotImplementedError("load_client method is not implemented")
|
|
76
|
+
|
|
77
|
+
def invoke(
|
|
78
|
+
self,
|
|
79
|
+
messages: Optional[list[dict]] = None,
|
|
80
|
+
as_str: bool = False,
|
|
81
|
+
**invoke_kwargs,
|
|
82
|
+
) -> Optional[Union[str, T]]:
|
|
83
|
+
"""
|
|
84
|
+
Invokes a generative AI model with the provided messages and additional parameters.
|
|
85
|
+
This method is designed to be a flexible interface for interacting with various
|
|
86
|
+
generative AI backends (e.g., OpenAI, Hugging Face, etc.). It allows users to send
|
|
87
|
+
a list of messages (following a standardized format) and receive a response. The
|
|
88
|
+
response can be returned as plain text or in its full structured format, depending
|
|
89
|
+
on the `as_str` parameter.
|
|
90
|
+
|
|
91
|
+
:param messages: A list of dictionaries representing the conversation history or input messages.
|
|
92
|
+
Each dictionary should follow the format::
|
|
93
|
+
{"role": "system"| "user" | "assistant" ..., "content": "Message content as a string"}
|
|
94
|
+
Example:
|
|
95
|
+
|
|
96
|
+
.. code-block:: json
|
|
97
|
+
|
|
98
|
+
[
|
|
99
|
+
{"role": "system", "content": "You are a helpful assistant."},
|
|
100
|
+
{"role": "user", "content": "What is the capital of France?"}
|
|
101
|
+
]
|
|
102
|
+
|
|
103
|
+
This format is consistent across all backends. Defaults to None if no messages
|
|
104
|
+
are provided.
|
|
105
|
+
|
|
106
|
+
:param as_str: A boolean flag indicating whether to return the response as a plain string.
|
|
107
|
+
- If True, the function extracts and returns the main content of the first
|
|
108
|
+
response.
|
|
109
|
+
- If False, the function returns the full response object,
|
|
110
|
+
which may include additional metadata or multiple response options.
|
|
111
|
+
Defaults to False.
|
|
112
|
+
|
|
113
|
+
:param invoke_kwargs:
|
|
114
|
+
Additional keyword arguments to be passed to the underlying model API call.
|
|
115
|
+
These can include parameters such as temperature, max tokens, etc.,
|
|
116
|
+
depending on the capabilities of the specific backend being used.
|
|
117
|
+
|
|
118
|
+
:return:
|
|
119
|
+
- If `as_str` is True: Returns the main content of the first response as a string.
|
|
120
|
+
- If `as_str` is False: Returns the full response object.
|
|
121
|
+
|
|
122
|
+
"""
|
|
123
|
+
raise NotImplementedError("invoke method is not implemented")
|
|
124
|
+
|
|
125
|
+
def customized_invoke(
|
|
126
|
+
self, operation: Optional[Callable[..., T]] = None, **invoke_kwargs
|
|
127
|
+
) -> Optional[T]:
|
|
128
|
+
raise NotImplementedError("customized_invoke method is not implemented")
|
|
129
|
+
|
|
130
|
+
@property
|
|
131
|
+
def client(self):
|
|
132
|
+
return self._client
|
|
133
|
+
|
|
134
|
+
@property
|
|
135
|
+
def model(self):
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def get_invoke_kwargs(self, invoke_kwargs):
|
|
139
|
+
kwargs = self.default_invoke_kwargs.copy()
|
|
140
|
+
kwargs.update(invoke_kwargs)
|
|
141
|
+
return kwargs
|
|
142
|
+
|
|
143
|
+
@property
|
|
144
|
+
def async_client(self):
|
|
145
|
+
if not self.support_async:
|
|
146
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
147
|
+
f"{self.__class__.__name__} does not support async operations"
|
|
148
|
+
)
|
|
149
|
+
return self._async_client
|
|
150
|
+
|
|
151
|
+
async def async_customized_invoke(self, **kwargs):
|
|
152
|
+
raise NotImplementedError("async_customized_invoke is not implemented")
|
|
153
|
+
|
|
154
|
+
async def async_invoke(
|
|
155
|
+
self,
|
|
156
|
+
messages: Optional[list[dict]] = None,
|
|
157
|
+
as_str: bool = False,
|
|
158
|
+
**invoke_kwargs,
|
|
159
|
+
) -> Awaitable[str]:
|
|
160
|
+
raise NotImplementedError("async_invoke is not implemented")
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# Copyright 2025 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Callable, Optional, TypeVar, Union
|
|
16
|
+
|
|
17
|
+
import mlrun
|
|
18
|
+
from mlrun.datastore.model_provider.model_provider import ModelProvider
|
|
19
|
+
|
|
20
|
+
T = TypeVar("T")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class OpenAIProvider(ModelProvider):
|
|
24
|
+
"""
|
|
25
|
+
OpenAIProvider is a wrapper around the OpenAI SDK that provides an interface
|
|
26
|
+
for interacting with OpenAI's generative AI services.
|
|
27
|
+
|
|
28
|
+
It supports both synchronous and asynchronous operations, allowing flexible
|
|
29
|
+
integration into various workflows.
|
|
30
|
+
|
|
31
|
+
This class extends the ModelProvider base class and implements OpenAI-specific
|
|
32
|
+
functionality, including client initialization, model invocation, and custom
|
|
33
|
+
operations tailored to the OpenAI API.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(
|
|
37
|
+
self,
|
|
38
|
+
parent,
|
|
39
|
+
schema,
|
|
40
|
+
name,
|
|
41
|
+
endpoint="",
|
|
42
|
+
secrets: Optional[dict] = None,
|
|
43
|
+
default_invoke_kwargs: Optional[dict] = None,
|
|
44
|
+
):
|
|
45
|
+
endpoint = endpoint or mlrun.mlconf.model_providers.openai_default_model
|
|
46
|
+
if schema != "openai":
|
|
47
|
+
raise mlrun.errors.MLRunInvalidArgumentError(
|
|
48
|
+
"OpenAIProvider supports only 'openai' as the provider kind."
|
|
49
|
+
)
|
|
50
|
+
super().__init__(
|
|
51
|
+
parent=parent,
|
|
52
|
+
kind=schema,
|
|
53
|
+
name=name,
|
|
54
|
+
endpoint=endpoint,
|
|
55
|
+
secrets=secrets,
|
|
56
|
+
default_invoke_kwargs=default_invoke_kwargs,
|
|
57
|
+
)
|
|
58
|
+
self.options = self.get_client_options()
|
|
59
|
+
self.load_client()
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def parse_endpoint_and_path(cls, endpoint, subpath) -> (str, str):
|
|
63
|
+
if endpoint and subpath:
|
|
64
|
+
endpoint = endpoint + subpath
|
|
65
|
+
# in openai there is no usage of subpath variable. if the model contains "/", it is part of the model name.
|
|
66
|
+
subpath = ""
|
|
67
|
+
return endpoint, subpath
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def model(self):
|
|
71
|
+
return self.endpoint
|
|
72
|
+
|
|
73
|
+
def load_client(self) -> None:
|
|
74
|
+
"""
|
|
75
|
+
Initializes the OpenAI SDK client using the provided options.
|
|
76
|
+
|
|
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`.
|
|
80
|
+
|
|
81
|
+
It also sets the default operation to `self.client.chat.completions.create`, which is
|
|
82
|
+
typically used for invoking chat-based model completions.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ImportError: If the `openai` package is not installed.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
from openai import OpenAI # noqa
|
|
89
|
+
|
|
90
|
+
self._client = OpenAI(**self.options)
|
|
91
|
+
self._default_operation = self.client.chat.completions.create
|
|
92
|
+
except ImportError as exc:
|
|
93
|
+
raise ImportError("openai package is not installed") from exc
|
|
94
|
+
|
|
95
|
+
def get_client_options(self):
|
|
96
|
+
res = dict(
|
|
97
|
+
api_key=self._get_secret_or_env("OPENAI_API_KEY"),
|
|
98
|
+
organization=self._get_secret_or_env("OPENAI_ORG_ID"),
|
|
99
|
+
project=self._get_secret_or_env("OPENAI_PROJECT_ID"),
|
|
100
|
+
base_url=self._get_secret_or_env("OPENAI_BASE_URL"),
|
|
101
|
+
timeout=self._get_secret_or_env("OPENAI_TIMEOUT"),
|
|
102
|
+
max_retries=self._get_secret_or_env("OPENAI_MAX_RETRIES"),
|
|
103
|
+
)
|
|
104
|
+
return self._sanitize_options(res)
|
|
105
|
+
|
|
106
|
+
def customized_invoke(
|
|
107
|
+
self, operation: Optional[Callable[..., T]] = None, **invoke_kwargs
|
|
108
|
+
) -> Optional[T]:
|
|
109
|
+
invoke_kwargs = self.get_invoke_kwargs(invoke_kwargs)
|
|
110
|
+
if operation:
|
|
111
|
+
return operation(**invoke_kwargs, model=self.model)
|
|
112
|
+
else:
|
|
113
|
+
return self._default_operation(**invoke_kwargs, model=self.model)
|
|
114
|
+
|
|
115
|
+
def invoke(
|
|
116
|
+
self,
|
|
117
|
+
messages: Optional[list[dict]] = None,
|
|
118
|
+
as_str: bool = False,
|
|
119
|
+
**invoke_kwargs,
|
|
120
|
+
) -> Optional[Union[str, T]]:
|
|
121
|
+
"""
|
|
122
|
+
OpenAI-specific implementation of `ModelProvider.invoke`.
|
|
123
|
+
Invokes an OpenAI model operation using the sync client.
|
|
124
|
+
For full details, see `ModelProvider.invoke`.
|
|
125
|
+
|
|
126
|
+
:param messages: Same as ModelProvider.invoke.
|
|
127
|
+
|
|
128
|
+
:param as_str: bool
|
|
129
|
+
If `True`, returns only the main content of the first response
|
|
130
|
+
(`response.choices[0].message.content`).
|
|
131
|
+
If `False`, returns the full response object, whose type depends on
|
|
132
|
+
the specific OpenAI SDK operation used (e.g., chat completion, completion, etc.).
|
|
133
|
+
|
|
134
|
+
:param invoke_kwargs:
|
|
135
|
+
Same as ModelProvider.invoke.
|
|
136
|
+
|
|
137
|
+
"""
|
|
138
|
+
invoke_kwargs = self.get_invoke_kwargs(invoke_kwargs)
|
|
139
|
+
response = self._default_operation(
|
|
140
|
+
model=self.endpoint, messages=messages, **invoke_kwargs
|
|
141
|
+
)
|
|
142
|
+
if as_str:
|
|
143
|
+
return response.choices[0].message.content
|
|
144
|
+
return response
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright 2025 Iguazio
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
from typing import Optional
|
|
16
|
+
|
|
17
|
+
import mlrun
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseRemoteClient:
|
|
21
|
+
"""
|
|
22
|
+
The BaseRemoteClient class serves as a foundational component for managing
|
|
23
|
+
secrets and configurations.
|
|
24
|
+
It is designed to be extended by subclasses that interact with external services,
|
|
25
|
+
such as file systems (e.g., Datastore) or model providers (e.g., ModelProvider).
|
|
26
|
+
|
|
27
|
+
This class is intended to provide shared functionality and should not be
|
|
28
|
+
used directly. Instead, create a subclass to implement logic specific to
|
|
29
|
+
your use case, such as interactions with S3 storage or invoking model providers like OpenAI.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, parent, kind, name, endpoint="", secrets: Optional[dict] = None):
|
|
33
|
+
self._parent = parent
|
|
34
|
+
self.kind = kind
|
|
35
|
+
self.name = name
|
|
36
|
+
self.endpoint = endpoint
|
|
37
|
+
self._secrets = secrets or {}
|
|
38
|
+
self.secret_pfx = ""
|
|
39
|
+
|
|
40
|
+
def _get_secret_or_env(self, key, default=None):
|
|
41
|
+
# Project-secrets are mounted as env variables whose name can be retrieved from SecretsStore
|
|
42
|
+
return mlrun.get_secret_or_env(
|
|
43
|
+
key, secret_provider=self._get_secret, default=default
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def _get_parent_secret(self, key):
|
|
47
|
+
return self._parent.secret(self.secret_pfx + key)
|
|
48
|
+
|
|
49
|
+
def _get_secret(self, key: str, default=None):
|
|
50
|
+
return self._secrets.get(key, default) or self._get_parent_secret(key)
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def url(self):
|
|
54
|
+
return f"{self.kind}://{self.endpoint}"
|
|
55
|
+
|
|
56
|
+
@staticmethod
|
|
57
|
+
def _sanitize_options(options):
|
|
58
|
+
if not options:
|
|
59
|
+
return {}
|
|
60
|
+
options = {k: v for k, v in options.items() if v is not None and v != ""}
|
|
61
|
+
return options
|
|
62
|
+
|
|
63
|
+
@classmethod
|
|
64
|
+
def parse_endpoint_and_path(cls, endpoint, subpath) -> (str, str):
|
|
65
|
+
return endpoint, subpath
|
mlrun/datastore/s3.py
CHANGED
mlrun/datastore/storeytargets.py
CHANGED
|
@@ -46,7 +46,7 @@ def get_url_and_storage_options(path, external_storage_options=None):
|
|
|
46
46
|
storage_options = merge(external_storage_options, storage_options)
|
|
47
47
|
else:
|
|
48
48
|
storage_options = storage_options or external_storage_options
|
|
49
|
-
return url, DataStore.
|
|
49
|
+
return url, DataStore._sanitize_options(storage_options)
|
|
50
50
|
|
|
51
51
|
|
|
52
52
|
class TDEngineStoreyTarget(storey.TDEngineTarget):
|
mlrun/datastore/utils.py
CHANGED
|
@@ -311,3 +311,25 @@ class KafkaParameters:
|
|
|
311
311
|
valid_keys.update(ref_dict.keys())
|
|
312
312
|
# Return a new dictionary with only valid keys
|
|
313
313
|
return {k: v for k, v in input_dict.items() if k in valid_keys}
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
def parse_url(url):
|
|
317
|
+
if url and url.startswith("v3io://") and not url.startswith("v3io:///"):
|
|
318
|
+
url = url.replace("v3io://", "v3io:///", 1)
|
|
319
|
+
parsed_url = urlparse(url)
|
|
320
|
+
schema = parsed_url.scheme.lower()
|
|
321
|
+
endpoint = parsed_url.hostname
|
|
322
|
+
if endpoint:
|
|
323
|
+
# HACK - urlparse returns the hostname after in lower case - we want the original case:
|
|
324
|
+
# the hostname is a substring of the netloc, in which it's the original case, so we find the indexes of the
|
|
325
|
+
# hostname in the netloc and take it from there
|
|
326
|
+
lower_hostname = parsed_url.hostname
|
|
327
|
+
netloc = str(parsed_url.netloc)
|
|
328
|
+
lower_netloc = netloc.lower()
|
|
329
|
+
hostname_index_in_netloc = lower_netloc.index(str(lower_hostname))
|
|
330
|
+
endpoint = netloc[
|
|
331
|
+
hostname_index_in_netloc : hostname_index_in_netloc + len(lower_hostname)
|
|
332
|
+
]
|
|
333
|
+
if parsed_url.port:
|
|
334
|
+
endpoint += f":{parsed_url.port}"
|
|
335
|
+
return schema, endpoint, parsed_url
|
mlrun/datastore/v3io.py
CHANGED
mlrun/db/base.py
CHANGED
mlrun/db/httpdb.py
CHANGED
|
@@ -608,7 +608,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
608
608
|
error = f"store log {project}/{uid}"
|
|
609
609
|
self.api_call("POST", path, error, params, body)
|
|
610
610
|
|
|
611
|
-
def get_log(self, uid, project="", offset=0, size=None):
|
|
611
|
+
def get_log(self, uid, project="", offset=0, size=None, attempt=None):
|
|
612
612
|
"""Retrieve 1 MB data of log.
|
|
613
613
|
|
|
614
614
|
:param uid: Log unique ID
|
|
@@ -616,6 +616,8 @@ class HTTPRunDB(RunDBInterface):
|
|
|
616
616
|
:param offset: Retrieve partial log, get up to ``size`` bytes starting at offset ``offset``
|
|
617
617
|
from beginning of log (must be >= 0)
|
|
618
618
|
:param size: If set to ``-1`` will retrieve and print all data to end of the log by chunks of 1MB each.
|
|
619
|
+
:param attempt: For retriable runs, the attempt number to retrieve the log for.
|
|
620
|
+
1 is the initial attempt.
|
|
619
621
|
:returns: The following objects:
|
|
620
622
|
|
|
621
623
|
- state - The state of the runtime object which generates this log, if it exists. In case no known state
|
|
@@ -636,6 +638,8 @@ class HTTPRunDB(RunDBInterface):
|
|
|
636
638
|
return state, offset
|
|
637
639
|
|
|
638
640
|
params = {"offset": offset, "size": size}
|
|
641
|
+
if attempt:
|
|
642
|
+
params["attempt"] = attempt
|
|
639
643
|
path = self._path_of("logs", project, uid)
|
|
640
644
|
error = f"get log {project}/{uid}"
|
|
641
645
|
resp = self.api_call("GET", path, error, params=params)
|
|
@@ -658,7 +662,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
658
662
|
resp = self.api_call("GET", path, error)
|
|
659
663
|
return resp.json()["size"]
|
|
660
664
|
|
|
661
|
-
def watch_log(self, uid, project="", watch=True, offset=0):
|
|
665
|
+
def watch_log(self, uid, project="", watch=True, offset=0, attempt=None):
|
|
662
666
|
"""Retrieve logs of a running process by chunks of 1MB, and watch the progress of the execution until it
|
|
663
667
|
completes. This method will print out the logs and continue to periodically poll for, and print,
|
|
664
668
|
new logs as long as the state of the runtime which generates this log is either ``pending`` or ``running``.
|
|
@@ -668,10 +672,11 @@ class HTTPRunDB(RunDBInterface):
|
|
|
668
672
|
:param watch: If set to ``True`` will continue tracking the log as described above. Otherwise this function
|
|
669
673
|
is practically equivalent to the :py:func:`~get_log` function.
|
|
670
674
|
:param offset: Minimal offset in the log to watch.
|
|
675
|
+
:param attempt: For retriable runs, the attempt number to retrieve the log for. 1 is the initial attempt.
|
|
671
676
|
:returns: The final state of the log being watched and the final offset.
|
|
672
677
|
"""
|
|
673
678
|
|
|
674
|
-
state, text = self.get_log(uid, project, offset=offset)
|
|
679
|
+
state, text = self.get_log(uid, project, offset=offset, attempt=attempt)
|
|
675
680
|
if text:
|
|
676
681
|
print(text.decode(errors=mlrun.mlconf.httpdb.logs.decode.errors))
|
|
677
682
|
nil_resp = 0
|
|
@@ -687,7 +692,7 @@ class HTTPRunDB(RunDBInterface):
|
|
|
687
692
|
mlrun.mlconf.httpdb.logs.pull_logs_backoff_no_logs_default_interval
|
|
688
693
|
)
|
|
689
694
|
)
|
|
690
|
-
state, text = self.get_log(uid, project, offset=offset)
|
|
695
|
+
state, text = self.get_log(uid, project, offset=offset, attempt=attempt)
|
|
691
696
|
if text:
|
|
692
697
|
nil_resp = 0
|
|
693
698
|
print(
|
mlrun/db/nopdb.py
CHANGED
|
@@ -63,7 +63,7 @@ class NopDB(RunDBInterface):
|
|
|
63
63
|
def store_log(self, uid, project="", body=None, append=False):
|
|
64
64
|
pass
|
|
65
65
|
|
|
66
|
-
def get_log(self, uid, project="", offset=0, size=0):
|
|
66
|
+
def get_log(self, uid, project="", offset=0, size=0, attempt=None):
|
|
67
67
|
pass
|
|
68
68
|
|
|
69
69
|
def store_run(self, struct, uid, project="", iter=0):
|