usfbios 1.0.0__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.
- usfbios/__init__.py +97 -0
- usfbios/_client.py +165 -0
- usfbios/_types.py +616 -0
- usfbios/datasets.py +187 -0
- usfbios/gpu.py +84 -0
- usfbios/models.py +93 -0
- usfbios/training.py +205 -0
- usfbios/wallet.py +33 -0
- usfbios-1.0.0.dist-info/METADATA +297 -0
- usfbios-1.0.0.dist-info/RECORD +12 -0
- usfbios-1.0.0.dist-info/WHEEL +4 -0
- usfbios-1.0.0.dist-info/licenses/LICENSE +21 -0
usfbios/__init__.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
usfbios -- Official Python SDK for the USF BIOS fine-tuning platform.
|
|
3
|
+
|
|
4
|
+
Usage::
|
|
5
|
+
|
|
6
|
+
from usfbios import USFBios
|
|
7
|
+
|
|
8
|
+
client = USFBios(api_key="sk_live_...")
|
|
9
|
+
|
|
10
|
+
# Search for models
|
|
11
|
+
models = client.models.search(query="llama")
|
|
12
|
+
|
|
13
|
+
# Create a training job
|
|
14
|
+
job = client.training.create(
|
|
15
|
+
model="meta-llama/Llama-3.1-8B-Instruct",
|
|
16
|
+
dataset_id="ds_abc123",
|
|
17
|
+
method="sft",
|
|
18
|
+
adapter="lora",
|
|
19
|
+
)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from typing import Any, Dict, Optional
|
|
23
|
+
|
|
24
|
+
from ._client import ApiError, HttpClient, VERSION
|
|
25
|
+
from ._types import ApiKeyIntrospection
|
|
26
|
+
from .models import Models
|
|
27
|
+
from .datasets import Datasets
|
|
28
|
+
from .training import Training
|
|
29
|
+
from .wallet import Wallet
|
|
30
|
+
from .gpu import GPU
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
__version__ = VERSION
|
|
34
|
+
__all__ = [
|
|
35
|
+
"USFBios",
|
|
36
|
+
"ApiError",
|
|
37
|
+
"VERSION",
|
|
38
|
+
"Models",
|
|
39
|
+
"Datasets",
|
|
40
|
+
"Training",
|
|
41
|
+
"Wallet",
|
|
42
|
+
"GPU",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class USFBios:
|
|
47
|
+
"""Main client for the USF BIOS API.
|
|
48
|
+
|
|
49
|
+
Create an instance with either an API key or a JWT access token,
|
|
50
|
+
then use the resource properties to interact with the API.
|
|
51
|
+
|
|
52
|
+
Example with API key::
|
|
53
|
+
|
|
54
|
+
client = USFBios(api_key="sk_live_...")
|
|
55
|
+
|
|
56
|
+
Example with JWT::
|
|
57
|
+
|
|
58
|
+
client = USFBios(access_token="eyJhbG...", org_id="org_abc123")
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
api_key: Optional[str] = None,
|
|
64
|
+
access_token: Optional[str] = None,
|
|
65
|
+
org_id: Optional[str] = None,
|
|
66
|
+
workspace_id: Optional[str] = None,
|
|
67
|
+
base_url: Optional[str] = None,
|
|
68
|
+
timeout: Optional[float] = None,
|
|
69
|
+
) -> None:
|
|
70
|
+
http = HttpClient(
|
|
71
|
+
api_key=api_key,
|
|
72
|
+
access_token=access_token,
|
|
73
|
+
org_id=org_id,
|
|
74
|
+
workspace_id=workspace_id,
|
|
75
|
+
base_url=base_url,
|
|
76
|
+
timeout=timeout,
|
|
77
|
+
)
|
|
78
|
+
self._http = http
|
|
79
|
+
|
|
80
|
+
self.models: Models = Models(http)
|
|
81
|
+
"""Search models, fetch configs, check adapter compatibility."""
|
|
82
|
+
|
|
83
|
+
self.datasets: Datasets = Datasets(http)
|
|
84
|
+
"""Upload, import, preview, and manage training datasets."""
|
|
85
|
+
|
|
86
|
+
self.training: Training = Training(http)
|
|
87
|
+
"""Create, monitor, stop, and resume fine-tuning jobs."""
|
|
88
|
+
|
|
89
|
+
self.wallet: Wallet = Wallet(http)
|
|
90
|
+
"""View wallet balance and transaction history."""
|
|
91
|
+
|
|
92
|
+
self.gpu: GPU = GPU(http)
|
|
93
|
+
"""View GPU pricing and get hardware recommendations."""
|
|
94
|
+
|
|
95
|
+
def introspect(self) -> ApiKeyIntrospection:
|
|
96
|
+
"""Introspect the current API key to discover its permissions."""
|
|
97
|
+
return self._http.get("/api/api-keys/introspect")
|
usfbios/_client.py
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""HTTP client for the USF BIOS API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from typing import Any, Dict, Optional, Tuple
|
|
5
|
+
|
|
6
|
+
import requests
|
|
7
|
+
|
|
8
|
+
VERSION = "1.0.0"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ApiError(Exception):
|
|
12
|
+
"""Typed error raised when the API returns a non-2xx status."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, status: int, body: Dict[str, Any]) -> None:
|
|
15
|
+
msg = (
|
|
16
|
+
body.get("detail")
|
|
17
|
+
or body.get("error")
|
|
18
|
+
or body.get("message")
|
|
19
|
+
or "API error {}".format(status)
|
|
20
|
+
)
|
|
21
|
+
super().__init__(msg)
|
|
22
|
+
self.status: int = status
|
|
23
|
+
self.code: Optional[str] = body.get("code")
|
|
24
|
+
self.request_id: Optional[str] = body.get("request_id")
|
|
25
|
+
self.message: str = msg
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class HttpClient:
|
|
29
|
+
"""Shared HTTP transport used by resource modules via composition."""
|
|
30
|
+
|
|
31
|
+
def __init__(
|
|
32
|
+
self,
|
|
33
|
+
api_key: Optional[str] = None,
|
|
34
|
+
access_token: Optional[str] = None,
|
|
35
|
+
org_id: Optional[str] = None,
|
|
36
|
+
workspace_id: Optional[str] = None,
|
|
37
|
+
base_url: Optional[str] = None,
|
|
38
|
+
timeout: Optional[float] = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self._base_url = (base_url or "https://api-bios.us.inc").rstrip("/")
|
|
41
|
+
self._api_key = api_key
|
|
42
|
+
self._access_token = access_token
|
|
43
|
+
self._org_id = org_id
|
|
44
|
+
self._workspace_id = workspace_id
|
|
45
|
+
self._timeout = timeout if timeout is not None else 30.0
|
|
46
|
+
|
|
47
|
+
if not self._api_key and not self._access_token:
|
|
48
|
+
raise ValueError("USFBios: either api_key or access_token is required")
|
|
49
|
+
|
|
50
|
+
self._session = requests.Session()
|
|
51
|
+
self._session.headers.update(self._build_base_headers())
|
|
52
|
+
|
|
53
|
+
def _build_base_headers(self) -> Dict[str, str]:
|
|
54
|
+
headers: Dict[str, str] = {
|
|
55
|
+
"User-Agent": "usfbios-python/{}".format(VERSION),
|
|
56
|
+
"Accept": "application/json",
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if self._api_key:
|
|
60
|
+
headers["X-API-Key"] = self._api_key
|
|
61
|
+
elif self._access_token:
|
|
62
|
+
headers["Authorization"] = "Bearer {}".format(self._access_token)
|
|
63
|
+
|
|
64
|
+
if self._org_id:
|
|
65
|
+
headers["X-Org-ID"] = self._org_id
|
|
66
|
+
if self._workspace_id:
|
|
67
|
+
headers["X-Workspace-ID"] = self._workspace_id
|
|
68
|
+
|
|
69
|
+
return headers
|
|
70
|
+
|
|
71
|
+
def _handle_response(self, response: requests.Response) -> Any:
|
|
72
|
+
if not response.ok:
|
|
73
|
+
try:
|
|
74
|
+
body = response.json()
|
|
75
|
+
except (ValueError, RuntimeError):
|
|
76
|
+
body = {"error": response.text or "API error {}".format(response.status_code)}
|
|
77
|
+
raise ApiError(response.status_code, body)
|
|
78
|
+
|
|
79
|
+
if response.status_code == 204 or not response.content:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
return response.json()
|
|
84
|
+
except (ValueError, RuntimeError):
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def request(
|
|
88
|
+
self,
|
|
89
|
+
method: str,
|
|
90
|
+
path: str,
|
|
91
|
+
body: Optional[Any] = None,
|
|
92
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
93
|
+
) -> Any:
|
|
94
|
+
"""Send a JSON request and parse the response."""
|
|
95
|
+
url = "{}{}".format(self._base_url, path)
|
|
96
|
+
headers = {"Content-Type": "application/json"}
|
|
97
|
+
if extra_headers:
|
|
98
|
+
headers.update(extra_headers)
|
|
99
|
+
|
|
100
|
+
response = self._session.request(
|
|
101
|
+
method=method,
|
|
102
|
+
url=url,
|
|
103
|
+
json=body,
|
|
104
|
+
headers=headers,
|
|
105
|
+
timeout=self._timeout,
|
|
106
|
+
)
|
|
107
|
+
return self._handle_response(response)
|
|
108
|
+
|
|
109
|
+
def get(self, path: str, extra_headers: Optional[Dict[str, str]] = None) -> Any:
|
|
110
|
+
"""Send a GET request."""
|
|
111
|
+
url = "{}{}".format(self._base_url, path)
|
|
112
|
+
response = self._session.get(url, headers=extra_headers, timeout=self._timeout)
|
|
113
|
+
return self._handle_response(response)
|
|
114
|
+
|
|
115
|
+
def post(
|
|
116
|
+
self,
|
|
117
|
+
path: str,
|
|
118
|
+
body: Optional[Any] = None,
|
|
119
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
120
|
+
) -> Any:
|
|
121
|
+
"""Send a POST request with a JSON body."""
|
|
122
|
+
return self.request("POST", path, body, extra_headers)
|
|
123
|
+
|
|
124
|
+
def patch(
|
|
125
|
+
self,
|
|
126
|
+
path: str,
|
|
127
|
+
body: Optional[Any] = None,
|
|
128
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
129
|
+
) -> Any:
|
|
130
|
+
"""Send a PATCH request with a JSON body."""
|
|
131
|
+
return self.request("PATCH", path, body, extra_headers)
|
|
132
|
+
|
|
133
|
+
def put(
|
|
134
|
+
self,
|
|
135
|
+
path: str,
|
|
136
|
+
body: Optional[Any] = None,
|
|
137
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
138
|
+
) -> Any:
|
|
139
|
+
"""Send a PUT request with a JSON body."""
|
|
140
|
+
return self.request("PUT", path, body, extra_headers)
|
|
141
|
+
|
|
142
|
+
def delete(
|
|
143
|
+
self,
|
|
144
|
+
path: str,
|
|
145
|
+
body: Optional[Any] = None,
|
|
146
|
+
extra_headers: Optional[Dict[str, str]] = None,
|
|
147
|
+
) -> Any:
|
|
148
|
+
"""Send a DELETE request."""
|
|
149
|
+
return self.request("DELETE", path, body, extra_headers)
|
|
150
|
+
|
|
151
|
+
def upload(
|
|
152
|
+
self,
|
|
153
|
+
path: str,
|
|
154
|
+
files: Dict[str, Tuple[str, Any, str]],
|
|
155
|
+
data: Optional[Dict[str, str]] = None,
|
|
156
|
+
) -> Any:
|
|
157
|
+
"""Upload files via multipart/form-data."""
|
|
158
|
+
url = "{}{}".format(self._base_url, path)
|
|
159
|
+
response = self._session.post(
|
|
160
|
+
url,
|
|
161
|
+
files=files,
|
|
162
|
+
data=data,
|
|
163
|
+
timeout=self._timeout,
|
|
164
|
+
)
|
|
165
|
+
return self._handle_response(response)
|