kainguru-sdk 0.1.4.dev0__tar.gz
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.
- kainguru_sdk-0.1.4.dev0/.gitignore +22 -0
- kainguru_sdk-0.1.4.dev0/MAINTAINERS.md +72 -0
- kainguru_sdk-0.1.4.dev0/Makefile +28 -0
- kainguru_sdk-0.1.4.dev0/PKG-INFO +251 -0
- kainguru_sdk-0.1.4.dev0/README.md +222 -0
- kainguru_sdk-0.1.4.dev0/pyproject.toml +65 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/__init__.py +48 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/_config.py +63 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/_errors.py +81 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/_polling.py +98 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/_transport.py +206 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/async_client.py +74 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/client.py +74 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/models.py +234 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/py.typed +0 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/resources/__init__.py +9 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/resources/_common.py +35 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/resources/executions.py +123 -0
- kainguru_sdk-0.1.4.dev0/src/kainguru/resources/finetuning.py +121 -0
- kainguru_sdk-0.1.4.dev0/tests/conftest.py +29 -0
- kainguru_sdk-0.1.4.dev0/tests/resources/openapi.json +1331 -0
- kainguru_sdk-0.1.4.dev0/tests/test_auth.py +41 -0
- kainguru_sdk-0.1.4.dev0/tests/test_executions.py +144 -0
- kainguru_sdk-0.1.4.dev0/tests/test_finetuning.py +53 -0
- kainguru_sdk-0.1.4.dev0/tests/test_polling.py +122 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtualenv
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# Tooling caches
|
|
14
|
+
.pytest_cache/
|
|
15
|
+
.mypy_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# OS
|
|
21
|
+
.DS_Store
|
|
22
|
+
Thumbs.db
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Maintaining kainguru-sdk
|
|
2
|
+
|
|
3
|
+
Internal notes for building, testing, and publishing the Python SDK.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
kainguru-sdk-python/
|
|
9
|
+
pyproject.toml # PEP 621 metadata, hatchling build backend
|
|
10
|
+
Makefile # generate / lint / typecheck / test / build
|
|
11
|
+
src/kainguru/
|
|
12
|
+
__init__.py # public exports
|
|
13
|
+
_config.py # KainguruConfig: api key + base URL resolution
|
|
14
|
+
_environment.py # Environment enum + base-URL map
|
|
15
|
+
_transport.py # httpx wrappers, auth, retry, error mapping, unwrap
|
|
16
|
+
_errors.py # exception hierarchy
|
|
17
|
+
_polling.py # sync + async await_completion engines
|
|
18
|
+
models.py # GENERATED (datamodel-code-generator), pydantic v2
|
|
19
|
+
client.py # KainguruClient (sync)
|
|
20
|
+
async_client.py # AsyncKainguruClient
|
|
21
|
+
resources/ # ExecutionsResource / FineTuningResource (+ async)
|
|
22
|
+
py.typed # PEP 561 marker
|
|
23
|
+
tests/
|
|
24
|
+
resources/openapi.json # pinned spec snapshot
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Setup
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
python -m venv .venv && source .venv/bin/activate
|
|
31
|
+
make install # pip install -e ".[dev]"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Local workflow
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
make generate # regenerate src/kainguru/models.py from the pinned spec
|
|
38
|
+
make lint # ruff check
|
|
39
|
+
make typecheck # mypy (strict)
|
|
40
|
+
make test # pytest (sync + async via pytest-asyncio)
|
|
41
|
+
make build # wheel + sdist into dist/
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Model-drift check (CI)
|
|
45
|
+
|
|
46
|
+
`src/kainguru/models.py` is committed. CI runs `make generate` and
|
|
47
|
+
`git diff --exit-code src/kainguru/models.py` to detect drift against
|
|
48
|
+
`tests/resources/openapi.json`. Update the pinned spec and regenerate when the
|
|
49
|
+
backend contract changes.
|
|
50
|
+
|
|
51
|
+
> Note: `datamodel-code-generator` emits PEP 604 (`X | None`) syntax, so the
|
|
52
|
+
> package floor is **Python 3.10** (`requires-python = ">=3.10"`).
|
|
53
|
+
|
|
54
|
+
## httpx base URL gotcha
|
|
55
|
+
|
|
56
|
+
httpx resolves request paths against `base_url` per RFC 3986, so the base URL
|
|
57
|
+
**must end with `/`** or the `/api` context path is dropped. `KainguruConfig`
|
|
58
|
+
normalizes this; request paths are written without a leading slash
|
|
59
|
+
(`"v1/executions"`).
|
|
60
|
+
|
|
61
|
+
## Publishing
|
|
62
|
+
|
|
63
|
+
- PyPI name: `kainguru-sdk`; import name: `kainguru`.
|
|
64
|
+
- Automated via GitHub Actions on tag push; see
|
|
65
|
+
`.github/workflows/kainguru-sdk-python-publish.yml` and the step-by-step guide in
|
|
66
|
+
`.claude/plans/kainguru-pypi-publish-steps.md`.
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Manual publish (fallback)
|
|
70
|
+
make build
|
|
71
|
+
python -m twine upload dist/*
|
|
72
|
+
```
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
.PHONY: install generate lint typecheck test build all
|
|
2
|
+
|
|
3
|
+
install:
|
|
4
|
+
python -m pip install -e ".[dev]"
|
|
5
|
+
|
|
6
|
+
# Regenerate pydantic models from the pinned OpenAPI spec.
|
|
7
|
+
generate:
|
|
8
|
+
datamodel-codegen \
|
|
9
|
+
--input tests/resources/openapi.json \
|
|
10
|
+
--input-file-type openapi \
|
|
11
|
+
--output src/kainguru/models.py \
|
|
12
|
+
--output-model-type pydantic_v2.BaseModel \
|
|
13
|
+
--target-python-version 3.10 \
|
|
14
|
+
--disable-timestamp
|
|
15
|
+
|
|
16
|
+
lint:
|
|
17
|
+
ruff check src tests
|
|
18
|
+
|
|
19
|
+
typecheck:
|
|
20
|
+
mypy
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
pytest
|
|
24
|
+
|
|
25
|
+
build:
|
|
26
|
+
python -m build
|
|
27
|
+
|
|
28
|
+
all: generate lint typecheck test build
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kainguru-sdk
|
|
3
|
+
Version: 0.1.4.dev0
|
|
4
|
+
Summary: Python SDK for the Kainguru ML platform — run models and fine-tune them.
|
|
5
|
+
Project-URL: Homepage, https://kainguru.com
|
|
6
|
+
Project-URL: Repository, https://github.com/kainguru/kainguru
|
|
7
|
+
Author: Kainguru
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: fine-tuning,kainguru,machine-learning,ml,sdk
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Typing :: Typed
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: httpx>=0.27
|
|
19
|
+
Requires-Dist: pydantic>=2.5
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: build>=1.2; extra == 'dev'
|
|
22
|
+
Requires-Dist: datamodel-code-generator>=0.25; extra == 'dev'
|
|
23
|
+
Requires-Dist: mypy>=1.11; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
25
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: respx>=0.21; extra == 'dev'
|
|
27
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# kainguru-sdk
|
|
31
|
+
|
|
32
|
+
Python SDK for the [Kainguru](https://kainguru.com) ML platform — run models and
|
|
33
|
+
fine-tune them from any Python application.
|
|
34
|
+
|
|
35
|
+
- Python 3.10+
|
|
36
|
+
- **Sync and async** clients with an identical surface (`KainguruClient`, `AsyncKainguruClient`)
|
|
37
|
+
- Typed pydantic v2 models
|
|
38
|
+
- Built-in polling with configurable timeout and backoff
|
|
39
|
+
- Automatic retry on 429 / 5xx with exponential backoff (honors `Retry-After`)
|
|
40
|
+
|
|
41
|
+
Covers `/v1/executions` and `/v1/fine-tuning`.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Installation
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install kainguru-sdk
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Import name is `kainguru`:
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from kainguru import KainguruClient
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Authentication
|
|
60
|
+
|
|
61
|
+
You must pass **both** an API key and the base URL explicitly. The API key is issued
|
|
62
|
+
from the Kainguru Dashboard (it begins with `kg_`); the base URL is the full host
|
|
63
|
+
including the `/api` context path:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
from kainguru import KainguruClient
|
|
67
|
+
|
|
68
|
+
client = KainguruClient(
|
|
69
|
+
api_key="kg_your_api_key",
|
|
70
|
+
base_url="https://your-host/api",
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Both fields are required. If either is missing or blank, the constructor raises
|
|
75
|
+
`KainguruConfigError` (fail fast). There are no defaults, environment presets, or
|
|
76
|
+
environment-variable fallbacks.
|
|
77
|
+
|
|
78
|
+
`KainguruClient` is a context manager — use `with` to close the HTTP pool automatically.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Quick start (sync)
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from kainguru import KainguruClient
|
|
86
|
+
|
|
87
|
+
with KainguruClient(
|
|
88
|
+
api_key="kg_your_api_key",
|
|
89
|
+
base_url="https://your-host/api",
|
|
90
|
+
) as client:
|
|
91
|
+
submitted = client.executions.execute(
|
|
92
|
+
"my-mlflow-id",
|
|
93
|
+
{"prompt": "hello world"},
|
|
94
|
+
output_format="json",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
done = client.executions.await_completion(submitted.id)
|
|
98
|
+
|
|
99
|
+
print(done.status) # ModelStatus.COMPLETED / FAILED
|
|
100
|
+
print(done.execution.output) # model output
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
`ml_flow_id` (first positional arg) is the model's **MLflow id** — the identifier the
|
|
104
|
+
Dashboard shows for the model.
|
|
105
|
+
|
|
106
|
+
## Quick start (async)
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
import asyncio
|
|
110
|
+
from kainguru import AsyncKainguruClient
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
async def main():
|
|
114
|
+
async with AsyncKainguruClient(
|
|
115
|
+
api_key="kg_your_api_key", base_url="https://your-host/api"
|
|
116
|
+
) as client:
|
|
117
|
+
submitted = await client.executions.execute(
|
|
118
|
+
"my-mlflow-id", {"prompt": "hello world"}, output_format="json"
|
|
119
|
+
)
|
|
120
|
+
done = await client.executions.await_completion(submitted.id)
|
|
121
|
+
print(done.status)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
asyncio.run(main())
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Executions API
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
exec = client.executions
|
|
133
|
+
|
|
134
|
+
# Run a model (returns immediately, typically PENDING)
|
|
135
|
+
pending = exec.execute(ml_flow_id, input, output_format=None, exec_id=None)
|
|
136
|
+
|
|
137
|
+
# Poll the current status once
|
|
138
|
+
current = exec.get(id)
|
|
139
|
+
|
|
140
|
+
# Block until COMPLETED or FAILED (default: 2 s interval, 5 min timeout)
|
|
141
|
+
done = exec.await_completion(id)
|
|
142
|
+
|
|
143
|
+
# Block with custom poll options
|
|
144
|
+
done = exec.await_completion(id, poll_interval=1.0, timeout=60.0)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Terminal statuses:** `COMPLETED`, `FAILED`. `REGISTERED` is treated as non-terminal.
|
|
148
|
+
A `FAILED` job *returns* from `await_completion` — inspect `dto.status`; it is not raised.
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Fine-Tuning API
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
ft = client.fine_tuning
|
|
156
|
+
|
|
157
|
+
# Start fine-tuning
|
|
158
|
+
pending = ft.execute(model_id, "my-fine-tuned-variant", input)
|
|
159
|
+
|
|
160
|
+
# Get status / poll until done
|
|
161
|
+
current = ft.get_status(id)
|
|
162
|
+
done = ft.await_completion(id)
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
(The async client exposes the same methods with `await`.)
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Polling options
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
client.executions.await_completion(
|
|
173
|
+
id,
|
|
174
|
+
poll_interval=2.0, # base interval between polls in s (default 2.0)
|
|
175
|
+
timeout=300.0, # total wall-clock timeout in s (default 300.0)
|
|
176
|
+
backoff=1.5, # multiply interval each attempt (default 1.0 = fixed)
|
|
177
|
+
max_interval=30.0, # cap on interval after backoff (default 30.0)
|
|
178
|
+
)
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
On timeout, `await_completion` raises `KainguruTimeoutError`, which carries the last DTO
|
|
182
|
+
seen via `err.last_dto`.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Configuration
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
client = KainguruClient(
|
|
190
|
+
api_key="kg_...", # required, explicit key
|
|
191
|
+
base_url="https://your-host/api", # required, full host incl. /api context path
|
|
192
|
+
timeout=30.0, # per-request timeout in s
|
|
193
|
+
max_retries=3, # retries on 429 / 5xx / network
|
|
194
|
+
)
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Both `api_key` and `base_url` are required and must be passed explicitly — there are no
|
|
198
|
+
defaults or environment-variable fallbacks. The base URL must be the full host including
|
|
199
|
+
the dashboard's `/api` context path.
|
|
200
|
+
|
|
201
|
+
Every method also accepts a per-request `api_key=` to override the client key for one call.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Error handling
|
|
206
|
+
|
|
207
|
+
All errors extend `KainguruError`.
|
|
208
|
+
|
|
209
|
+
| Error | When |
|
|
210
|
+
|---|---|
|
|
211
|
+
| `KainguruConfigError` | Missing/invalid configuration (e.g. no API key) |
|
|
212
|
+
| `KainguruAPIError` | Non-2xx HTTP response, or `success=false` in the body. Has `status_code`, `body`, `api_code` |
|
|
213
|
+
| `KainguruAuthError` | 401 / 403 (subclass of `KainguruAPIError`) |
|
|
214
|
+
| `KainguruNotFoundError` | 404 (subclass of `KainguruAPIError`) |
|
|
215
|
+
| `KainguruRateLimitError` | 429 (subclass of `KainguruAPIError`); exposes `retry_after` |
|
|
216
|
+
| `KainguruTimeoutError` | `await_completion` exceeded `timeout`; carries `last_dto` |
|
|
217
|
+
| `KainguruConnectionError` | Network / transport failure; chained via `from` |
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
from kainguru import (
|
|
221
|
+
KainguruAPIError,
|
|
222
|
+
KainguruNotFoundError,
|
|
223
|
+
KainguruTimeoutError,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
result = client.executions.await_completion(id)
|
|
228
|
+
if result.status.value == "FAILED":
|
|
229
|
+
... # FAILED is returned, not raised — inspect the result
|
|
230
|
+
except KainguruNotFoundError as e:
|
|
231
|
+
print("not found:", e.status_code)
|
|
232
|
+
except KainguruAPIError as e:
|
|
233
|
+
print(f"HTTP {e.status_code}: {e.body}")
|
|
234
|
+
except KainguruTimeoutError as e:
|
|
235
|
+
print("timed out; last:", e.last_dto)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
The SDK automatically retries `429` and `5xx` responses up to 3 times with exponential
|
|
239
|
+
backoff (base 1 s, doubling per attempt); `Retry-After` headers are respected.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Limitations (v0.1.0)
|
|
244
|
+
|
|
245
|
+
- Cancel endpoints are not exposed yet (they require JWT/Keycloak auth, not an API key).
|
|
246
|
+
- `input` parameters are untyped (`dict[str, Any]`).
|
|
247
|
+
- `output_format` is a free `str`; allowed values are not yet enumerated by the backend.
|
|
248
|
+
|
|
249
|
+
---
|
|
250
|
+
|
|
251
|
+
> Building or publishing the SDK yourself? See **[MAINTAINERS.md](MAINTAINERS.md)**.
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# kainguru-sdk
|
|
2
|
+
|
|
3
|
+
Python SDK for the [Kainguru](https://kainguru.com) ML platform — run models and
|
|
4
|
+
fine-tune them from any Python application.
|
|
5
|
+
|
|
6
|
+
- Python 3.10+
|
|
7
|
+
- **Sync and async** clients with an identical surface (`KainguruClient`, `AsyncKainguruClient`)
|
|
8
|
+
- Typed pydantic v2 models
|
|
9
|
+
- Built-in polling with configurable timeout and backoff
|
|
10
|
+
- Automatic retry on 429 / 5xx with exponential backoff (honors `Retry-After`)
|
|
11
|
+
|
|
12
|
+
Covers `/v1/executions` and `/v1/fine-tuning`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pip install kainguru-sdk
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Import name is `kainguru`:
|
|
23
|
+
|
|
24
|
+
```python
|
|
25
|
+
from kainguru import KainguruClient
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Authentication
|
|
31
|
+
|
|
32
|
+
You must pass **both** an API key and the base URL explicitly. The API key is issued
|
|
33
|
+
from the Kainguru Dashboard (it begins with `kg_`); the base URL is the full host
|
|
34
|
+
including the `/api` context path:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from kainguru import KainguruClient
|
|
38
|
+
|
|
39
|
+
client = KainguruClient(
|
|
40
|
+
api_key="kg_your_api_key",
|
|
41
|
+
base_url="https://your-host/api",
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Both fields are required. If either is missing or blank, the constructor raises
|
|
46
|
+
`KainguruConfigError` (fail fast). There are no defaults, environment presets, or
|
|
47
|
+
environment-variable fallbacks.
|
|
48
|
+
|
|
49
|
+
`KainguruClient` is a context manager — use `with` to close the HTTP pool automatically.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Quick start (sync)
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from kainguru import KainguruClient
|
|
57
|
+
|
|
58
|
+
with KainguruClient(
|
|
59
|
+
api_key="kg_your_api_key",
|
|
60
|
+
base_url="https://your-host/api",
|
|
61
|
+
) as client:
|
|
62
|
+
submitted = client.executions.execute(
|
|
63
|
+
"my-mlflow-id",
|
|
64
|
+
{"prompt": "hello world"},
|
|
65
|
+
output_format="json",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
done = client.executions.await_completion(submitted.id)
|
|
69
|
+
|
|
70
|
+
print(done.status) # ModelStatus.COMPLETED / FAILED
|
|
71
|
+
print(done.execution.output) # model output
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`ml_flow_id` (first positional arg) is the model's **MLflow id** — the identifier the
|
|
75
|
+
Dashboard shows for the model.
|
|
76
|
+
|
|
77
|
+
## Quick start (async)
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
import asyncio
|
|
81
|
+
from kainguru import AsyncKainguruClient
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
async def main():
|
|
85
|
+
async with AsyncKainguruClient(
|
|
86
|
+
api_key="kg_your_api_key", base_url="https://your-host/api"
|
|
87
|
+
) as client:
|
|
88
|
+
submitted = await client.executions.execute(
|
|
89
|
+
"my-mlflow-id", {"prompt": "hello world"}, output_format="json"
|
|
90
|
+
)
|
|
91
|
+
done = await client.executions.await_completion(submitted.id)
|
|
92
|
+
print(done.status)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
asyncio.run(main())
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Executions API
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
exec = client.executions
|
|
104
|
+
|
|
105
|
+
# Run a model (returns immediately, typically PENDING)
|
|
106
|
+
pending = exec.execute(ml_flow_id, input, output_format=None, exec_id=None)
|
|
107
|
+
|
|
108
|
+
# Poll the current status once
|
|
109
|
+
current = exec.get(id)
|
|
110
|
+
|
|
111
|
+
# Block until COMPLETED or FAILED (default: 2 s interval, 5 min timeout)
|
|
112
|
+
done = exec.await_completion(id)
|
|
113
|
+
|
|
114
|
+
# Block with custom poll options
|
|
115
|
+
done = exec.await_completion(id, poll_interval=1.0, timeout=60.0)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Terminal statuses:** `COMPLETED`, `FAILED`. `REGISTERED` is treated as non-terminal.
|
|
119
|
+
A `FAILED` job *returns* from `await_completion` — inspect `dto.status`; it is not raised.
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## Fine-Tuning API
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
ft = client.fine_tuning
|
|
127
|
+
|
|
128
|
+
# Start fine-tuning
|
|
129
|
+
pending = ft.execute(model_id, "my-fine-tuned-variant", input)
|
|
130
|
+
|
|
131
|
+
# Get status / poll until done
|
|
132
|
+
current = ft.get_status(id)
|
|
133
|
+
done = ft.await_completion(id)
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
(The async client exposes the same methods with `await`.)
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Polling options
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
client.executions.await_completion(
|
|
144
|
+
id,
|
|
145
|
+
poll_interval=2.0, # base interval between polls in s (default 2.0)
|
|
146
|
+
timeout=300.0, # total wall-clock timeout in s (default 300.0)
|
|
147
|
+
backoff=1.5, # multiply interval each attempt (default 1.0 = fixed)
|
|
148
|
+
max_interval=30.0, # cap on interval after backoff (default 30.0)
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
On timeout, `await_completion` raises `KainguruTimeoutError`, which carries the last DTO
|
|
153
|
+
seen via `err.last_dto`.
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
client = KainguruClient(
|
|
161
|
+
api_key="kg_...", # required, explicit key
|
|
162
|
+
base_url="https://your-host/api", # required, full host incl. /api context path
|
|
163
|
+
timeout=30.0, # per-request timeout in s
|
|
164
|
+
max_retries=3, # retries on 429 / 5xx / network
|
|
165
|
+
)
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Both `api_key` and `base_url` are required and must be passed explicitly — there are no
|
|
169
|
+
defaults or environment-variable fallbacks. The base URL must be the full host including
|
|
170
|
+
the dashboard's `/api` context path.
|
|
171
|
+
|
|
172
|
+
Every method also accepts a per-request `api_key=` to override the client key for one call.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Error handling
|
|
177
|
+
|
|
178
|
+
All errors extend `KainguruError`.
|
|
179
|
+
|
|
180
|
+
| Error | When |
|
|
181
|
+
|---|---|
|
|
182
|
+
| `KainguruConfigError` | Missing/invalid configuration (e.g. no API key) |
|
|
183
|
+
| `KainguruAPIError` | Non-2xx HTTP response, or `success=false` in the body. Has `status_code`, `body`, `api_code` |
|
|
184
|
+
| `KainguruAuthError` | 401 / 403 (subclass of `KainguruAPIError`) |
|
|
185
|
+
| `KainguruNotFoundError` | 404 (subclass of `KainguruAPIError`) |
|
|
186
|
+
| `KainguruRateLimitError` | 429 (subclass of `KainguruAPIError`); exposes `retry_after` |
|
|
187
|
+
| `KainguruTimeoutError` | `await_completion` exceeded `timeout`; carries `last_dto` |
|
|
188
|
+
| `KainguruConnectionError` | Network / transport failure; chained via `from` |
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
from kainguru import (
|
|
192
|
+
KainguruAPIError,
|
|
193
|
+
KainguruNotFoundError,
|
|
194
|
+
KainguruTimeoutError,
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
try:
|
|
198
|
+
result = client.executions.await_completion(id)
|
|
199
|
+
if result.status.value == "FAILED":
|
|
200
|
+
... # FAILED is returned, not raised — inspect the result
|
|
201
|
+
except KainguruNotFoundError as e:
|
|
202
|
+
print("not found:", e.status_code)
|
|
203
|
+
except KainguruAPIError as e:
|
|
204
|
+
print(f"HTTP {e.status_code}: {e.body}")
|
|
205
|
+
except KainguruTimeoutError as e:
|
|
206
|
+
print("timed out; last:", e.last_dto)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
The SDK automatically retries `429` and `5xx` responses up to 3 times with exponential
|
|
210
|
+
backoff (base 1 s, doubling per attempt); `Retry-After` headers are respected.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Limitations (v0.1.0)
|
|
215
|
+
|
|
216
|
+
- Cancel endpoints are not exposed yet (they require JWT/Keycloak auth, not an API key).
|
|
217
|
+
- `input` parameters are untyped (`dict[str, Any]`).
|
|
218
|
+
- `output_format` is a free `str`; allowed values are not yet enumerated by the backend.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
> Building or publishing the SDK yourself? See **[MAINTAINERS.md](MAINTAINERS.md)**.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "kainguru-sdk"
|
|
7
|
+
version = "0.1.4.dev0"
|
|
8
|
+
description = "Python SDK for the Kainguru ML platform — run models and fine-tune them."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Kainguru" }]
|
|
13
|
+
keywords = ["kainguru", "ml", "machine-learning", "sdk", "fine-tuning"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"Programming Language :: Python :: 3.10",
|
|
17
|
+
"Programming Language :: Python :: 3.11",
|
|
18
|
+
"Programming Language :: Python :: 3.12",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
dependencies = [
|
|
24
|
+
"httpx>=0.27",
|
|
25
|
+
"pydantic>=2.5",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.urls]
|
|
29
|
+
Homepage = "https://kainguru.com"
|
|
30
|
+
Repository = "https://github.com/kainguru/kainguru"
|
|
31
|
+
|
|
32
|
+
[project.optional-dependencies]
|
|
33
|
+
dev = [
|
|
34
|
+
"pytest>=8.0",
|
|
35
|
+
"pytest-asyncio>=0.23",
|
|
36
|
+
"respx>=0.21",
|
|
37
|
+
"ruff>=0.6",
|
|
38
|
+
"mypy>=1.11",
|
|
39
|
+
"build>=1.2",
|
|
40
|
+
"datamodel-code-generator>=0.25",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[tool.hatch.build.targets.wheel]
|
|
44
|
+
packages = ["src/kainguru"]
|
|
45
|
+
|
|
46
|
+
[tool.pytest.ini_options]
|
|
47
|
+
asyncio_mode = "auto"
|
|
48
|
+
testpaths = ["tests"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
line-length = 100
|
|
52
|
+
src = ["src", "tests"]
|
|
53
|
+
|
|
54
|
+
[tool.ruff.lint]
|
|
55
|
+
select = ["E", "F", "I", "UP", "B"]
|
|
56
|
+
|
|
57
|
+
[tool.mypy]
|
|
58
|
+
python_version = "3.10"
|
|
59
|
+
strict = true
|
|
60
|
+
files = ["src/kainguru"]
|
|
61
|
+
# models.py is generated by datamodel-code-generator; its output (e.g. constr(...)
|
|
62
|
+
# constraint types) isn't static-type clean, so it's excluded from mypy checking.
|
|
63
|
+
[[tool.mypy.overrides]]
|
|
64
|
+
module = "kainguru.models"
|
|
65
|
+
ignore_errors = true
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""Kainguru Python SDK — run models and fine-tune them on the Kainguru ML platform."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from ._config import KainguruConfig
|
|
6
|
+
from ._errors import (
|
|
7
|
+
KainguruAPIError,
|
|
8
|
+
KainguruAuthError,
|
|
9
|
+
KainguruConfigError,
|
|
10
|
+
KainguruConnectionError,
|
|
11
|
+
KainguruError,
|
|
12
|
+
KainguruNotFoundError,
|
|
13
|
+
KainguruRateLimitError,
|
|
14
|
+
KainguruTimeoutError,
|
|
15
|
+
)
|
|
16
|
+
from .async_client import AsyncKainguruClient
|
|
17
|
+
from .client import KainguruClient
|
|
18
|
+
from .models import (
|
|
19
|
+
ApplicationDto,
|
|
20
|
+
ExecutionResultDto,
|
|
21
|
+
ModelExecResponseDto,
|
|
22
|
+
ModelFineTunedDetailDto,
|
|
23
|
+
ModelStatus,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
__version__ = "0.1.0"
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"__version__",
|
|
30
|
+
"KainguruClient",
|
|
31
|
+
"AsyncKainguruClient",
|
|
32
|
+
"KainguruConfig",
|
|
33
|
+
# Errors
|
|
34
|
+
"KainguruError",
|
|
35
|
+
"KainguruConfigError",
|
|
36
|
+
"KainguruAPIError",
|
|
37
|
+
"KainguruAuthError",
|
|
38
|
+
"KainguruNotFoundError",
|
|
39
|
+
"KainguruRateLimitError",
|
|
40
|
+
"KainguruTimeoutError",
|
|
41
|
+
"KainguruConnectionError",
|
|
42
|
+
# Models
|
|
43
|
+
"ModelStatus",
|
|
44
|
+
"ModelExecResponseDto",
|
|
45
|
+
"ModelFineTunedDetailDto",
|
|
46
|
+
"ApplicationDto",
|
|
47
|
+
"ExecutionResultDto",
|
|
48
|
+
]
|