lightning-sdk 0.2.11__py3-none-any.whl → 0.2.13__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.
- lightning_sdk/__init__.py +1 -1
- lightning_sdk/api/deployment_api.py +35 -3
- lightning_sdk/api/lit_container_api.py +13 -7
- lightning_sdk/api/llm_api.py +46 -0
- lightning_sdk/api/studio_api.py +17 -0
- lightning_sdk/cli/entrypoint.py +1 -1
- lightning_sdk/cli/serve.py +221 -62
- lightning_sdk/deployment/deployment.py +53 -7
- lightning_sdk/lightning_cloud/openapi/__init__.py +11 -1
- lightning_sdk/lightning_cloud/openapi/api/__init__.py +1 -0
- lightning_sdk/lightning_cloud/openapi/api/cloud_space_environment_template_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/api/cluster_service_api.py +13 -1
- lightning_sdk/lightning_cloud/openapi/api/data_connection_service_api.py +4 -4
- lightning_sdk/lightning_cloud/openapi/api/git_credentials_service_api.py +497 -0
- lightning_sdk/lightning_cloud/openapi/api/jobs_service_api.py +124 -0
- lightning_sdk/lightning_cloud/openapi/models/__init__.py +10 -1
- lightning_sdk/lightning_cloud/openapi/models/assistant_id_conversations_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/create_deployment_request_defines_a_spec_for_the_job_that_allows_for_autoscaling_jobs.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/deployments_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/orgs_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/projects_id_body.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/update.py +65 -195
- lightning_sdk/lightning_cloud/openapi/models/update1.py +357 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_provider.py +1 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_cloud_space_environment_template.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_accelerator.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_security_options.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_cluster_spec.py +79 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_deployment_request.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_create_git_credentials_request.py +175 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_cloud_space_environment_template_response.py +1 -53
- lightning_sdk/lightning_cloud/openapi/models/v1_delete_git_credentials_response.py +97 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_deployment_state.py +2 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_get_user_response.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_git_credentials.py +227 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_job_resource.py +279 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_job_type.py +108 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_git_credentials_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_list_job_resources_response.py +123 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_nebius_direct_v1.py +149 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_organization.py +55 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_project_settings.py +29 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_reservation_billing_session.py +279 -0
- lightning_sdk/lightning_cloud/openapi/models/v1_resources.py +55 -3
- lightning_sdk/lightning_cloud/openapi/models/v1_update_user_request.py +1 -27
- lightning_sdk/lightning_cloud/openapi/models/v1_usage.py +27 -1
- lightning_sdk/lightning_cloud/openapi/models/v1_user_features.py +157 -1
- lightning_sdk/llm/__init__.py +3 -0
- lightning_sdk/llm/llm.py +118 -0
- lightning_sdk/plugin.py +19 -0
- lightning_sdk/serve.py +4 -6
- lightning_sdk/studio.py +33 -0
- {lightning_sdk-0.2.11.dist-info → lightning_sdk-0.2.13.dist-info}/METADATA +1 -1
- {lightning_sdk-0.2.11.dist-info → lightning_sdk-0.2.13.dist-info}/RECORD +60 -47
- lightning_sdk/lightning_cloud/openapi/models/environmenttemplates_id_body.py +0 -253
- /lightning_sdk/cli/{docker.py → docker_cli.py} +0 -0
- {lightning_sdk-0.2.11.dist-info → lightning_sdk-0.2.13.dist-info}/LICENSE +0 -0
- {lightning_sdk-0.2.11.dist-info → lightning_sdk-0.2.13.dist-info}/WHEEL +0 -0
- {lightning_sdk-0.2.11.dist-info → lightning_sdk-0.2.13.dist-info}/entry_points.txt +0 -0
- {lightning_sdk-0.2.11.dist-info → lightning_sdk-0.2.13.dist-info}/top_level.txt +0 -0
lightning_sdk/llm/llm.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import Dict, List, Optional, Set, Tuple, Union
|
|
2
|
+
|
|
3
|
+
from lightning_sdk.api import UserApi
|
|
4
|
+
from lightning_sdk.api.llm_api import LLMApi
|
|
5
|
+
from lightning_sdk.lightning_cloud.login import Auth
|
|
6
|
+
from lightning_sdk.lightning_cloud.openapi import V1Assistant
|
|
7
|
+
from lightning_sdk.lightning_cloud.openapi.rest import ApiException
|
|
8
|
+
from lightning_sdk.organization import Organization
|
|
9
|
+
from lightning_sdk.user import User
|
|
10
|
+
from lightning_sdk.utils.resolve import _resolve_org, _resolve_user
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class LLM:
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
name: str,
|
|
17
|
+
user: Union[str, "User", None] = None,
|
|
18
|
+
org: Union[str, "Organization", None] = None,
|
|
19
|
+
) -> None:
|
|
20
|
+
self._auth = Auth()
|
|
21
|
+
self._user = None
|
|
22
|
+
|
|
23
|
+
try:
|
|
24
|
+
self._auth.authenticate()
|
|
25
|
+
self._user = User(name=UserApi()._get_user_by_id(self._auth.user_id).username)
|
|
26
|
+
except ConnectionError as e:
|
|
27
|
+
raise e
|
|
28
|
+
|
|
29
|
+
self._name = name
|
|
30
|
+
try:
|
|
31
|
+
self._user = _resolve_user(self._user or user)
|
|
32
|
+
except ValueError:
|
|
33
|
+
self._user = None
|
|
34
|
+
|
|
35
|
+
self._name = name
|
|
36
|
+
self._org, self._model_name = self._parse_model_name(name)
|
|
37
|
+
try:
|
|
38
|
+
# check if it is a org model
|
|
39
|
+
self._org = _resolve_org(self._org or org)
|
|
40
|
+
except ApiException:
|
|
41
|
+
self._org = None
|
|
42
|
+
|
|
43
|
+
self._llm_api = LLMApi()
|
|
44
|
+
self._public_models = self._build_model_lookup(self._get_public_models())
|
|
45
|
+
self._org_models = self._build_model_lookup(self._get_org_models())
|
|
46
|
+
self._user_models = self._build_model_lookup(self._get_user_models())
|
|
47
|
+
self._model = self._get_model()
|
|
48
|
+
self._conversations = {}
|
|
49
|
+
|
|
50
|
+
def _parse_model_name(self, name: str) -> Tuple[str, str]:
|
|
51
|
+
parts = name.split("/")
|
|
52
|
+
if len(parts) == 1:
|
|
53
|
+
# a user model or a org model
|
|
54
|
+
return None, parts[0]
|
|
55
|
+
if len(parts) == 2:
|
|
56
|
+
return parts[0], parts[1]
|
|
57
|
+
raise ValueError(
|
|
58
|
+
f"Model name must be in the format `organization/model_name` or `model_name`, but got '{name}'."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
def _build_model_lookup(self, endpoints: List[str]) -> Dict[str, Set[str]]:
|
|
62
|
+
result = {}
|
|
63
|
+
for endpoint in endpoints:
|
|
64
|
+
result.setdefault(endpoint.model, []).append(endpoint)
|
|
65
|
+
return result
|
|
66
|
+
|
|
67
|
+
def _get_public_models(self) -> List[str]:
|
|
68
|
+
return self._llm_api.get_public_models()
|
|
69
|
+
|
|
70
|
+
def _get_org_models(self) -> List[str]:
|
|
71
|
+
return self._llm_api.get_org_models(self._org.id) if self._org else []
|
|
72
|
+
|
|
73
|
+
def _get_user_models(self) -> List[str]:
|
|
74
|
+
return self._llm_api.get_user_models(self._user.id) if self._user else []
|
|
75
|
+
|
|
76
|
+
def _get_model(self) -> V1Assistant:
|
|
77
|
+
# TODO how to handle multiple models with same model type? For now, just use the first one
|
|
78
|
+
if self._model_name in self._public_models:
|
|
79
|
+
return self._public_models.get(self._model_name)[0]
|
|
80
|
+
if self._model_name in self._org_models:
|
|
81
|
+
return self._org_models.get(self._model_name)[0]
|
|
82
|
+
if self._model_name in self._user_models:
|
|
83
|
+
return self._user_models.get(self._model_name)[0]
|
|
84
|
+
|
|
85
|
+
available_models = []
|
|
86
|
+
if self._public_models:
|
|
87
|
+
available_models.append(f"Public Models: {', '.join(self._public_models.keys())}")
|
|
88
|
+
|
|
89
|
+
if self._org and self._org_models:
|
|
90
|
+
available_models.append(f"Org ({self._org.name}) Models: {', '.join(self._org_models.keys())}")
|
|
91
|
+
|
|
92
|
+
if self._user and self._user_models:
|
|
93
|
+
available_models.append(f"User ({self._user.name}) Models: {', '.join(self._user_models.keys())}")
|
|
94
|
+
|
|
95
|
+
available_models_str = "\n".join(available_models)
|
|
96
|
+
raise ValueError(f"Model '{self._model_name}' not found. \nAvailable models: \n{available_models_str}")
|
|
97
|
+
|
|
98
|
+
def chat(
|
|
99
|
+
self,
|
|
100
|
+
prompt: str,
|
|
101
|
+
system_prompt: Optional[str] = None,
|
|
102
|
+
max_completion_tokens: Optional[int] = 500,
|
|
103
|
+
conversation: Optional[str] = None,
|
|
104
|
+
) -> str:
|
|
105
|
+
conversation_id = self._conversations.get(conversation) if conversation else None
|
|
106
|
+
output = self._llm_api.start_conversation(
|
|
107
|
+
prompt=prompt,
|
|
108
|
+
system_prompt=system_prompt,
|
|
109
|
+
max_completion_tokens=max_completion_tokens,
|
|
110
|
+
assistant_id=self._model.id,
|
|
111
|
+
conversation_id=conversation_id,
|
|
112
|
+
)
|
|
113
|
+
if conversation and not conversation_id:
|
|
114
|
+
self._conversations[conversation] = output.conversation_id
|
|
115
|
+
return output.choices[0].delta.content
|
|
116
|
+
|
|
117
|
+
def list_conversations(self) -> List[Dict]:
|
|
118
|
+
return list(self._conversations.keys())
|
lightning_sdk/plugin.py
CHANGED
|
@@ -395,6 +395,25 @@ class SlurmJobsPlugin(_Plugin):
|
|
|
395
395
|
return resp
|
|
396
396
|
|
|
397
397
|
|
|
398
|
+
class CustomPortPlugin(_Plugin):
|
|
399
|
+
"""Plugin handling the port of a given service."""
|
|
400
|
+
|
|
401
|
+
_plugin_run_name = "Custom Port"
|
|
402
|
+
_slug_name = "custom-port"
|
|
403
|
+
|
|
404
|
+
def run(self, name: Optional[str] = None, port: int = 8000) -> str:
|
|
405
|
+
"""Starts a new port to the given Studio."""
|
|
406
|
+
if name is None:
|
|
407
|
+
name = _run_name("port")
|
|
408
|
+
|
|
409
|
+
return self._studio._studio_api.start_new_port(
|
|
410
|
+
teamspace_id=self._studio._teamspace.id,
|
|
411
|
+
studio_id=self._studio._studio.id,
|
|
412
|
+
name=name,
|
|
413
|
+
port=port,
|
|
414
|
+
)
|
|
415
|
+
|
|
416
|
+
|
|
398
417
|
@runtime_checkable
|
|
399
418
|
class _RunnablePlugin(Protocol):
|
|
400
419
|
_plugin_run_name: str
|
lightning_sdk/serve.py
CHANGED
|
@@ -10,7 +10,7 @@ from rich.console import Console
|
|
|
10
10
|
from lightning_sdk import Deployment, Machine, Teamspace
|
|
11
11
|
from lightning_sdk.api.deployment_api import AutoScaleConfig, DeploymentApi, Env, Secret
|
|
12
12
|
from lightning_sdk.api.lit_container_api import LitContainerApi
|
|
13
|
-
from lightning_sdk.api.utils import _get_cloud_url
|
|
13
|
+
from lightning_sdk.api.utils import _get_cloud_url
|
|
14
14
|
from lightning_sdk.lightning_cloud.env import LIGHTNING_CLOUD_URL
|
|
15
15
|
|
|
16
16
|
_DOCKER_NOT_RUNNING_MSG = (
|
|
@@ -189,12 +189,9 @@ Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additio
|
|
|
189
189
|
if "status" in line:
|
|
190
190
|
yield {"status": line["status"].strip()}
|
|
191
191
|
|
|
192
|
-
registry_url = _get_registry_url()
|
|
193
192
|
container_basename = repository.split("/")[-1]
|
|
194
|
-
repository = (
|
|
195
|
-
|
|
196
|
-
f"{teamspace.owner.name}/{teamspace.name}/{container_basename}"
|
|
197
|
-
)
|
|
193
|
+
repository = lit_cr.get_container_url(repository, tag, teamspace, cloud_account)
|
|
194
|
+
|
|
198
195
|
yield {
|
|
199
196
|
"finish": True,
|
|
200
197
|
"status": "Container pushed successfully",
|
|
@@ -303,6 +300,7 @@ Update [underline]{os.path.abspath("Dockerfile")}[/underline] to add any additio
|
|
|
303
300
|
include_credentials=include_credentials,
|
|
304
301
|
cloudspace_id=cloudspace_id,
|
|
305
302
|
from_onboarding=from_onboarding,
|
|
303
|
+
command="",
|
|
306
304
|
)
|
|
307
305
|
|
|
308
306
|
return {"deployment": deployment, "url": url}
|
lightning_sdk/studio.py
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import glob
|
|
1
2
|
import os
|
|
2
3
|
import warnings
|
|
3
4
|
from typing import TYPE_CHECKING, Any, Dict, Mapping, Optional, Tuple, Union
|
|
4
5
|
|
|
6
|
+
from tqdm.auto import tqdm
|
|
7
|
+
|
|
5
8
|
from lightning_sdk.api.studio_api import StudioApi
|
|
6
9
|
from lightning_sdk.api.utils import _machine_to_compute_name
|
|
7
10
|
from lightning_sdk.constants import _LIGHTNING_DEBUG
|
|
@@ -265,6 +268,34 @@ class Studio:
|
|
|
265
268
|
progress_bar=progress_bar,
|
|
266
269
|
)
|
|
267
270
|
|
|
271
|
+
def upload_folder(self, folder_path: str, remote_path: Optional[str] = None, progress_bar: bool = True) -> None:
|
|
272
|
+
"""Uploads a given folder to a remote path on the Studio."""
|
|
273
|
+
if folder_path is None:
|
|
274
|
+
raise ValueError("Cannot upload a folder that is None.")
|
|
275
|
+
folder_path = os.path.normpath(folder_path)
|
|
276
|
+
if os.path.isfile(folder_path):
|
|
277
|
+
raise NotADirectoryError(f"Cannot upload a file as a folder. '{folder_path}' is a file.")
|
|
278
|
+
if not os.path.exists(folder_path):
|
|
279
|
+
raise NotADirectoryError(f"Cannot upload a folder that does not exist. '{folder_path}' is not a directory.")
|
|
280
|
+
all_files = []
|
|
281
|
+
for fp in glob.glob(os.path.join(folder_path, "**"), recursive=True):
|
|
282
|
+
if not os.path.isfile(fp):
|
|
283
|
+
continue
|
|
284
|
+
rel_path = os.path.relpath(fp, folder_path)
|
|
285
|
+
remote_file = os.path.join(remote_path, rel_path) if remote_path else rel_path
|
|
286
|
+
all_files.append((fp, remote_file))
|
|
287
|
+
|
|
288
|
+
if progress_bar:
|
|
289
|
+
progress_bar = tqdm(total=len(all_files), desc="Uploading files", unit="file")
|
|
290
|
+
for local_file, remote_path in sorted(all_files, key=lambda p: p[1]):
|
|
291
|
+
if progress_bar:
|
|
292
|
+
progress_bar.set_description(f"Uploading {local_file}")
|
|
293
|
+
self.upload_file(local_file, remote_path=remote_path, progress_bar=False)
|
|
294
|
+
if progress_bar:
|
|
295
|
+
progress_bar.update(1)
|
|
296
|
+
if progress_bar:
|
|
297
|
+
progress_bar.close()
|
|
298
|
+
|
|
268
299
|
def download_file(self, remote_path: str, file_path: Optional[str] = None) -> None:
|
|
269
300
|
"""Downloads a file from the Studio to a given target path."""
|
|
270
301
|
if file_path is None:
|
|
@@ -445,6 +476,7 @@ class Studio:
|
|
|
445
476
|
def _add_plugin(self, plugin_name: str) -> None:
|
|
446
477
|
"""Adds the just installed plugin to the internal list of plugins."""
|
|
447
478
|
from lightning_sdk.plugin import (
|
|
479
|
+
CustomPortPlugin,
|
|
448
480
|
InferenceServerPlugin,
|
|
449
481
|
JobsPlugin,
|
|
450
482
|
MultiMachineTrainingPlugin,
|
|
@@ -458,6 +490,7 @@ class Studio:
|
|
|
458
490
|
"jobs": JobsPlugin,
|
|
459
491
|
"multi-machine-training": MultiMachineTrainingPlugin,
|
|
460
492
|
"inference-server": InferenceServerPlugin,
|
|
493
|
+
"custom-port": CustomPortPlugin,
|
|
461
494
|
}.get(plugin_name, Plugin)
|
|
462
495
|
|
|
463
496
|
description = self._list_installed_plugins()[plugin_name]
|