cubic-sdk 0.3.2__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.
- cubic_sdk-0.3.2/.github/workflows/ci.yml +21 -0
- cubic_sdk-0.3.2/.github/workflows/publish.yml +69 -0
- cubic_sdk-0.3.2/.gitignore +18 -0
- cubic_sdk-0.3.2/CHANGELOG.md +43 -0
- cubic_sdk-0.3.2/PKG-INFO +213 -0
- cubic_sdk-0.3.2/README.md +184 -0
- cubic_sdk-0.3.2/pyproject.toml +47 -0
- cubic_sdk-0.3.2/src/cubic/__init__.py +91 -0
- cubic_sdk-0.3.2/src/cubic/_async_client.py +130 -0
- cubic_sdk-0.3.2/src/cubic/_client.py +247 -0
- cubic_sdk-0.3.2/src/cubic/_exceptions.py +171 -0
- cubic_sdk-0.3.2/src/cubic/_version.py +1 -0
- cubic_sdk-0.3.2/src/cubic/py.typed +0 -0
- cubic_sdk-0.3.2/src/cubic/resources/__init__.py +0 -0
- cubic_sdk-0.3.2/src/cubic/resources/completions.py +406 -0
- cubic_sdk-0.3.2/src/cubic/resources/cubes.py +50 -0
- cubic_sdk-0.3.2/src/cubic/resources/models.py +114 -0
- cubic_sdk-0.3.2/src/cubic/types.py +248 -0
- cubic_sdk-0.3.2/src/cubic/webhooks.py +107 -0
- cubic_sdk-0.3.2/tests/conftest.py +108 -0
- cubic_sdk-0.3.2/tests/test_async_client.py +132 -0
- cubic_sdk-0.3.2/tests/test_completions.py +252 -0
- cubic_sdk-0.3.2/tests/test_cubes.py +73 -0
- cubic_sdk-0.3.2/tests/test_errors_and_retries.py +188 -0
- cubic_sdk-0.3.2/tests/test_models.py +120 -0
- cubic_sdk-0.3.2/tests/test_wait.py +151 -0
- cubic_sdk-0.3.2/tests/test_webhooks.py +107 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- run: pip install -e ".[dev]"
|
|
21
|
+
- run: pytest -q
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Publishes to PyPI via trusted publishing (OIDC) — no API token stored anywhere.
|
|
2
|
+
# Triggered by pushing a version tag (git tag -a vX.Y.Z && git push --follow-tags).
|
|
3
|
+
#
|
|
4
|
+
# One-time setup on pypi.org before the first run:
|
|
5
|
+
# Account → Publishing → "Add a new pending publisher":
|
|
6
|
+
# project: cubic-sdk
|
|
7
|
+
# owner: cubic-zone
|
|
8
|
+
# repository: cubic-python
|
|
9
|
+
# workflow: publish.yml
|
|
10
|
+
# environment: pypi
|
|
11
|
+
|
|
12
|
+
name: Publish to PyPI
|
|
13
|
+
|
|
14
|
+
on:
|
|
15
|
+
push:
|
|
16
|
+
tags: ["v*"]
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
test:
|
|
20
|
+
runs-on: ubuntu-latest
|
|
21
|
+
strategy:
|
|
22
|
+
fail-fast: false
|
|
23
|
+
matrix:
|
|
24
|
+
python-version: ["3.10", "3.13"]
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: ${{ matrix.python-version }}
|
|
30
|
+
- run: pip install -e ".[dev]"
|
|
31
|
+
- run: pytest -q
|
|
32
|
+
|
|
33
|
+
build:
|
|
34
|
+
needs: test
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: actions/setup-python@v5
|
|
39
|
+
with:
|
|
40
|
+
python-version: "3.12"
|
|
41
|
+
- name: Check tag matches package version
|
|
42
|
+
run: |
|
|
43
|
+
PKG_VERSION=$(python -c "exec(open('src/cubic/_version.py').read()); print(__version__)")
|
|
44
|
+
TAG_VERSION="${GITHUB_REF_NAME#v}"
|
|
45
|
+
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
|
|
46
|
+
echo "Tag $GITHUB_REF_NAME does not match package version $PKG_VERSION" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
- run: pip install build
|
|
50
|
+
- run: python -m build
|
|
51
|
+
- uses: actions/upload-artifact@v4
|
|
52
|
+
with:
|
|
53
|
+
name: dist
|
|
54
|
+
path: dist/
|
|
55
|
+
|
|
56
|
+
publish:
|
|
57
|
+
needs: build
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
environment:
|
|
60
|
+
name: pypi
|
|
61
|
+
url: https://pypi.org/project/cubic-sdk/
|
|
62
|
+
permissions:
|
|
63
|
+
id-token: write # OIDC token for PyPI trusted publishing
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/download-artifact@v4
|
|
66
|
+
with:
|
|
67
|
+
name: dist
|
|
68
|
+
path: dist/
|
|
69
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.2 (2026-07-12)
|
|
4
|
+
|
|
5
|
+
- First PyPI release: `pip install cubic-sdk`.
|
|
6
|
+
- Repository moved to the `cubic-zone` GitHub organization; project URLs
|
|
7
|
+
updated. CI and trusted-publishing workflows added. No code changes.
|
|
8
|
+
|
|
9
|
+
## 0.3.1 (2026-07-12)
|
|
10
|
+
|
|
11
|
+
- The default `base_url` is now the hosted API (`https://api.cubic.zone`)
|
|
12
|
+
instead of the local dev server. Local development now requires an explicit
|
|
13
|
+
`base_url="http://localhost:8010"` or `CUBIC_BASE_URL`.
|
|
14
|
+
|
|
15
|
+
## 0.3.0 (2026-07-11)
|
|
16
|
+
|
|
17
|
+
- `client.models` resource: `list()` (public catalog, cached in-process for
|
|
18
|
+
1h, `provider=` filter, `force_refresh=`) and `retrieve(model_name)`
|
|
19
|
+
(client-side lookup with `provider=` disambiguation and did-you-mean
|
|
20
|
+
suggestions via `ModelNotFoundError`).
|
|
21
|
+
- PyPI packaging prep: `py.typed` marker (PEP 561), trove classifiers,
|
|
22
|
+
project URLs, changelog.
|
|
23
|
+
|
|
24
|
+
## 0.2.0 (2026-07-10)
|
|
25
|
+
|
|
26
|
+
- `AsyncCubic`: the full client surface as coroutines, sharing retry and
|
|
27
|
+
parsing logic with the sync client.
|
|
28
|
+
- `completions.wait(request_id)` and the `result.wait()` shortcut: poll for a
|
|
29
|
+
queued run's persisted record with backoff; raises `CompletionError` for
|
|
30
|
+
error records and `WaitTimeoutError` on deadline.
|
|
31
|
+
- `cubic.webhooks`: `verify()` / `verify_signature()` / `parse()` for signed
|
|
32
|
+
callback deliveries (`X-Maxwell-Signature`, HMAC-SHA256 over the raw body),
|
|
33
|
+
plus `derive_project_secret()` for self-hosted deployments.
|
|
34
|
+
|
|
35
|
+
## 0.1.0 (2026-07-09)
|
|
36
|
+
|
|
37
|
+
- Initial release: `Cubic` client with `completions.create` /
|
|
38
|
+
`completions.retrieve` and `cubes.retrieve`.
|
|
39
|
+
- Unified cube/polycube execution: one `create()` call for both kinds, typed
|
|
40
|
+
`CompletionResult` / `PolycubeResult` with a common `result.content`.
|
|
41
|
+
- Typed exception hierarchy incl. pipeline errors surfaced from HTTP 200
|
|
42
|
+
bodies (`MissingVariableError`, `ProviderError`, `CubeNotFoundError`…).
|
|
43
|
+
- Automatic idempotent retries via auto-generated `client_request_id`.
|
cubic_sdk-0.3.2/PKG-INFO
ADDED
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: cubic-sdk
|
|
3
|
+
Version: 0.3.2
|
|
4
|
+
Summary: Official Python SDK for Cubic — run your Cubes and Polycubes from any application.
|
|
5
|
+
Project-URL: Homepage, https://cubic.zone
|
|
6
|
+
Project-URL: Documentation, https://cubic.zone/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/cubic-zone/cubic-python
|
|
8
|
+
Project-URL: Changelog, https://github.com/cubic-zone/cubic-python/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Cubic
|
|
10
|
+
License: MIT
|
|
11
|
+
Keywords: cubic,llm,prompts,sdk
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2.7
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# Cubic Python SDK
|
|
31
|
+
|
|
32
|
+
Run your Cubes and Polycubes from any Python application with a single API key.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install cubic-sdk # installs the `cubic` import package
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from cubic import Cubic
|
|
42
|
+
|
|
43
|
+
client = Cubic(api_key="mxk_...") # or set CUBIC_API_KEY
|
|
44
|
+
|
|
45
|
+
result = client.completions.create(
|
|
46
|
+
cube_id="cbe_a1B2c3D4e5F6g7",
|
|
47
|
+
variables={"customer_name": "Ada", "issue": "billing"},
|
|
48
|
+
)
|
|
49
|
+
print(result.content)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`cube_id` accepts any public Cube ID — plain cubes and Polycubes share one ID
|
|
53
|
+
space (`cbe_…`, with legacy `prmt…`/`poly…` IDs still valid). You never need to
|
|
54
|
+
know which kind an ID is: the same call runs either, and `result.content` is
|
|
55
|
+
the delivered output for both.
|
|
56
|
+
|
|
57
|
+
## Running a completion
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
result = client.completions.create(
|
|
61
|
+
cube_id="cbe_a1B2c3D4e5F6g7",
|
|
62
|
+
variables={"customer_name": "Ada"},
|
|
63
|
+
|
|
64
|
+
# plain cubes only:
|
|
65
|
+
version=5, # pin a version (default: latest)
|
|
66
|
+
parameters={"temperature": 0.7}, # merged over the cube's parameters
|
|
67
|
+
models=[{"provider": "anthropic", "model_name": "claude-sonnet-4-5"}],
|
|
68
|
+
history=[{"role": "user", "content": "hi"}],
|
|
69
|
+
|
|
70
|
+
# both kinds:
|
|
71
|
+
test_mode=True, # no provider spend, no credit debit
|
|
72
|
+
metadata={"trace": "abc"},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
result.content # str | dict — the winning completion (or final node output)
|
|
76
|
+
result.kind # "cube" | "polycube"
|
|
77
|
+
result.metrics # tokens, cost, credits_charged, latency, cache hits
|
|
78
|
+
result.request_id # keep this for retrieval / support
|
|
79
|
+
result.is_partial # cube delivered content but some fallbacks failed
|
|
80
|
+
result.segments # polycube only: per-node outputs, metrics, errors
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Async client
|
|
84
|
+
|
|
85
|
+
`AsyncCubic` has the identical surface with awaitable methods:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from cubic import AsyncCubic
|
|
89
|
+
|
|
90
|
+
async with AsyncCubic(api_key="mxk_...") as client:
|
|
91
|
+
result = await client.completions.create(cube_id="cbe_...", variables={...})
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Async execution (callbacks)
|
|
95
|
+
|
|
96
|
+
If the cube defines a callback URL — or you pass `callback_url=` — the run is
|
|
97
|
+
queued and delivered to your endpoint when done:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
job = client.completions.create(cube_id="cbe_...", variables={...},
|
|
101
|
+
callback_url="https://you.example/hook")
|
|
102
|
+
job.is_queued # True
|
|
103
|
+
job.request_id # correlate with the X-Maxwell-Request-Id callback header
|
|
104
|
+
|
|
105
|
+
record = job.wait(timeout=120) # poll until persisted
|
|
106
|
+
record = client.completions.wait(job.request_id) # same, by id
|
|
107
|
+
record = client.completions.retrieve(job.request_id) # single poll
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`wait()` backs off from 0.5s to 4s between polls (fix it with
|
|
111
|
+
`poll_interval=`), raises `CompletionError` if the run finished with status
|
|
112
|
+
`error`, and `WaitTimeoutError` if nothing was persisted in time (the run
|
|
113
|
+
itself is unaffected). With `AsyncCubic`, `await job.wait()`.
|
|
114
|
+
|
|
115
|
+
## Verifying callback deliveries
|
|
116
|
+
|
|
117
|
+
Every delivery is signed. Verify and parse it in one step — always against the
|
|
118
|
+
**raw request body bytes**:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from cubic import webhooks
|
|
122
|
+
|
|
123
|
+
@app.post("/hook") # any framework
|
|
124
|
+
async def hook(request):
|
|
125
|
+
result = webhooks.verify(await request.body(), request.headers,
|
|
126
|
+
secret=CUBIC_SIGNING_SECRET)
|
|
127
|
+
if result.status == "success":
|
|
128
|
+
handle(result.content) # CompletionResult | PolycubeResult
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
A bad or missing `X-Maxwell-Signature` raises `WebhookSignatureError`. Unlike
|
|
132
|
+
`create()`, an error-status delivery is returned (not raised) — it's an event
|
|
133
|
+
you inspect. Retried deliveries reuse the same body and signature and carry an
|
|
134
|
+
incrementing `X-Maxwell-Delivery-Attempt` header; deduplicate on
|
|
135
|
+
`result.request_id` if your handler isn't idempotent.
|
|
136
|
+
|
|
137
|
+
## Reading a cube's definition
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
cube = client.cubes.retrieve("cbe_a1B2c3D4e5F6g7") # latest
|
|
141
|
+
cube = client.cubes.retrieve("cbe_a1B2c3D4e5F6g7", version=5)
|
|
142
|
+
|
|
143
|
+
cube.system_instructions # the cube's system prompt
|
|
144
|
+
cube.user_prompt # the user prompt template
|
|
145
|
+
cube.variables # input schema — handy for pre-flight checks
|
|
146
|
+
cube.models # the model stack (provider, model, rank, role)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Definitions are owner-only: marketplace cubes you subscribe to can be *run*
|
|
150
|
+
but not read, and polycube definitions are not yet available on this endpoint.
|
|
151
|
+
|
|
152
|
+
## The model catalog
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
models = client.models.list() # cached in-process for 1h
|
|
156
|
+
anthropic = client.models.list(provider="anthropic")
|
|
157
|
+
|
|
158
|
+
m = client.models.retrieve("claude-3-5-haiku") # ModelNotFoundError suggests
|
|
159
|
+
m.context_window, m.input_per_1k, m.supports_tools # close matches on typos
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Useful for validating `models=` overrides before a run, populating model
|
|
163
|
+
pickers, and estimating cost. Lookups are explicit — the SDK never
|
|
164
|
+
auto-validates overrides against the cache; the server stays authoritative.
|
|
165
|
+
|
|
166
|
+
## Error handling
|
|
167
|
+
|
|
168
|
+
The SDK never returns a silent failure: pipeline errors that the API reports
|
|
169
|
+
inside an HTTP 200 are raised as typed exceptions too.
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from cubic import (
|
|
173
|
+
AuthenticationError, # bad/expired API key
|
|
174
|
+
CubeNotFoundError, # unknown ID, or not yours
|
|
175
|
+
MissingVariableError, # e.missing variable_name
|
|
176
|
+
InvalidRequestError, # bad parameters / polycube-inapplicable fields
|
|
177
|
+
InsufficientCreditsError, # e.required / e.balance / e.topup_allowed
|
|
178
|
+
RateLimitError, # capacity (auto-retried first)
|
|
179
|
+
ProviderError, # all model attempts failed; see e.attempt_errors
|
|
180
|
+
CompletionTimeoutError, # server-side execution deadline exceeded
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
try:
|
|
184
|
+
result = client.completions.create(cube_id="cbe_...", variables={...})
|
|
185
|
+
except MissingVariableError as e:
|
|
186
|
+
print(f"Provide the '{e.variable_name}' variable")
|
|
187
|
+
except InsufficientCreditsError as e:
|
|
188
|
+
print(f"Need {e.required} credits, have {e.balance}")
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Every exception carries `.error_code`, `.status_code`, `.request_id` (quote it
|
|
192
|
+
in support requests), and — for pipeline failures — `.result` with the full
|
|
193
|
+
parsed response.
|
|
194
|
+
|
|
195
|
+
## Retries and idempotency
|
|
196
|
+
|
|
197
|
+
Connection failures and capacity 429s are retried automatically with
|
|
198
|
+
exponential backoff (`max_retries=2` by default). For plain cubes the SDK
|
|
199
|
+
attaches a `client_request_id` idempotency key to every run, so retries can
|
|
200
|
+
never double-charge or double-execute; ambiguous failures (timeouts, 5xx) are
|
|
201
|
+
retried only when that key is present. Pass your own `client_request_id` to
|
|
202
|
+
make retries idempotent across process restarts too.
|
|
203
|
+
|
|
204
|
+
## Configuration
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
client = Cubic(
|
|
208
|
+
api_key="mxk_...", # or CUBIC_API_KEY
|
|
209
|
+
base_url="http://localhost:8010", # or CUBIC_BASE_URL (default: https://api.cubic.zone)
|
|
210
|
+
timeout=120.0,
|
|
211
|
+
max_retries=3,
|
|
212
|
+
)
|
|
213
|
+
```
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# Cubic Python SDK
|
|
2
|
+
|
|
3
|
+
Run your Cubes and Polycubes from any Python application with a single API key.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pip install cubic-sdk # installs the `cubic` import package
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
## Quickstart
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
from cubic import Cubic
|
|
13
|
+
|
|
14
|
+
client = Cubic(api_key="mxk_...") # or set CUBIC_API_KEY
|
|
15
|
+
|
|
16
|
+
result = client.completions.create(
|
|
17
|
+
cube_id="cbe_a1B2c3D4e5F6g7",
|
|
18
|
+
variables={"customer_name": "Ada", "issue": "billing"},
|
|
19
|
+
)
|
|
20
|
+
print(result.content)
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
`cube_id` accepts any public Cube ID — plain cubes and Polycubes share one ID
|
|
24
|
+
space (`cbe_…`, with legacy `prmt…`/`poly…` IDs still valid). You never need to
|
|
25
|
+
know which kind an ID is: the same call runs either, and `result.content` is
|
|
26
|
+
the delivered output for both.
|
|
27
|
+
|
|
28
|
+
## Running a completion
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
result = client.completions.create(
|
|
32
|
+
cube_id="cbe_a1B2c3D4e5F6g7",
|
|
33
|
+
variables={"customer_name": "Ada"},
|
|
34
|
+
|
|
35
|
+
# plain cubes only:
|
|
36
|
+
version=5, # pin a version (default: latest)
|
|
37
|
+
parameters={"temperature": 0.7}, # merged over the cube's parameters
|
|
38
|
+
models=[{"provider": "anthropic", "model_name": "claude-sonnet-4-5"}],
|
|
39
|
+
history=[{"role": "user", "content": "hi"}],
|
|
40
|
+
|
|
41
|
+
# both kinds:
|
|
42
|
+
test_mode=True, # no provider spend, no credit debit
|
|
43
|
+
metadata={"trace": "abc"},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
result.content # str | dict — the winning completion (or final node output)
|
|
47
|
+
result.kind # "cube" | "polycube"
|
|
48
|
+
result.metrics # tokens, cost, credits_charged, latency, cache hits
|
|
49
|
+
result.request_id # keep this for retrieval / support
|
|
50
|
+
result.is_partial # cube delivered content but some fallbacks failed
|
|
51
|
+
result.segments # polycube only: per-node outputs, metrics, errors
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Async client
|
|
55
|
+
|
|
56
|
+
`AsyncCubic` has the identical surface with awaitable methods:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from cubic import AsyncCubic
|
|
60
|
+
|
|
61
|
+
async with AsyncCubic(api_key="mxk_...") as client:
|
|
62
|
+
result = await client.completions.create(cube_id="cbe_...", variables={...})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Async execution (callbacks)
|
|
66
|
+
|
|
67
|
+
If the cube defines a callback URL — or you pass `callback_url=` — the run is
|
|
68
|
+
queued and delivered to your endpoint when done:
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
job = client.completions.create(cube_id="cbe_...", variables={...},
|
|
72
|
+
callback_url="https://you.example/hook")
|
|
73
|
+
job.is_queued # True
|
|
74
|
+
job.request_id # correlate with the X-Maxwell-Request-Id callback header
|
|
75
|
+
|
|
76
|
+
record = job.wait(timeout=120) # poll until persisted
|
|
77
|
+
record = client.completions.wait(job.request_id) # same, by id
|
|
78
|
+
record = client.completions.retrieve(job.request_id) # single poll
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`wait()` backs off from 0.5s to 4s between polls (fix it with
|
|
82
|
+
`poll_interval=`), raises `CompletionError` if the run finished with status
|
|
83
|
+
`error`, and `WaitTimeoutError` if nothing was persisted in time (the run
|
|
84
|
+
itself is unaffected). With `AsyncCubic`, `await job.wait()`.
|
|
85
|
+
|
|
86
|
+
## Verifying callback deliveries
|
|
87
|
+
|
|
88
|
+
Every delivery is signed. Verify and parse it in one step — always against the
|
|
89
|
+
**raw request body bytes**:
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
from cubic import webhooks
|
|
93
|
+
|
|
94
|
+
@app.post("/hook") # any framework
|
|
95
|
+
async def hook(request):
|
|
96
|
+
result = webhooks.verify(await request.body(), request.headers,
|
|
97
|
+
secret=CUBIC_SIGNING_SECRET)
|
|
98
|
+
if result.status == "success":
|
|
99
|
+
handle(result.content) # CompletionResult | PolycubeResult
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A bad or missing `X-Maxwell-Signature` raises `WebhookSignatureError`. Unlike
|
|
103
|
+
`create()`, an error-status delivery is returned (not raised) — it's an event
|
|
104
|
+
you inspect. Retried deliveries reuse the same body and signature and carry an
|
|
105
|
+
incrementing `X-Maxwell-Delivery-Attempt` header; deduplicate on
|
|
106
|
+
`result.request_id` if your handler isn't idempotent.
|
|
107
|
+
|
|
108
|
+
## Reading a cube's definition
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
cube = client.cubes.retrieve("cbe_a1B2c3D4e5F6g7") # latest
|
|
112
|
+
cube = client.cubes.retrieve("cbe_a1B2c3D4e5F6g7", version=5)
|
|
113
|
+
|
|
114
|
+
cube.system_instructions # the cube's system prompt
|
|
115
|
+
cube.user_prompt # the user prompt template
|
|
116
|
+
cube.variables # input schema — handy for pre-flight checks
|
|
117
|
+
cube.models # the model stack (provider, model, rank, role)
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Definitions are owner-only: marketplace cubes you subscribe to can be *run*
|
|
121
|
+
but not read, and polycube definitions are not yet available on this endpoint.
|
|
122
|
+
|
|
123
|
+
## The model catalog
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
models = client.models.list() # cached in-process for 1h
|
|
127
|
+
anthropic = client.models.list(provider="anthropic")
|
|
128
|
+
|
|
129
|
+
m = client.models.retrieve("claude-3-5-haiku") # ModelNotFoundError suggests
|
|
130
|
+
m.context_window, m.input_per_1k, m.supports_tools # close matches on typos
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Useful for validating `models=` overrides before a run, populating model
|
|
134
|
+
pickers, and estimating cost. Lookups are explicit — the SDK never
|
|
135
|
+
auto-validates overrides against the cache; the server stays authoritative.
|
|
136
|
+
|
|
137
|
+
## Error handling
|
|
138
|
+
|
|
139
|
+
The SDK never returns a silent failure: pipeline errors that the API reports
|
|
140
|
+
inside an HTTP 200 are raised as typed exceptions too.
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from cubic import (
|
|
144
|
+
AuthenticationError, # bad/expired API key
|
|
145
|
+
CubeNotFoundError, # unknown ID, or not yours
|
|
146
|
+
MissingVariableError, # e.missing variable_name
|
|
147
|
+
InvalidRequestError, # bad parameters / polycube-inapplicable fields
|
|
148
|
+
InsufficientCreditsError, # e.required / e.balance / e.topup_allowed
|
|
149
|
+
RateLimitError, # capacity (auto-retried first)
|
|
150
|
+
ProviderError, # all model attempts failed; see e.attempt_errors
|
|
151
|
+
CompletionTimeoutError, # server-side execution deadline exceeded
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
try:
|
|
155
|
+
result = client.completions.create(cube_id="cbe_...", variables={...})
|
|
156
|
+
except MissingVariableError as e:
|
|
157
|
+
print(f"Provide the '{e.variable_name}' variable")
|
|
158
|
+
except InsufficientCreditsError as e:
|
|
159
|
+
print(f"Need {e.required} credits, have {e.balance}")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Every exception carries `.error_code`, `.status_code`, `.request_id` (quote it
|
|
163
|
+
in support requests), and — for pipeline failures — `.result` with the full
|
|
164
|
+
parsed response.
|
|
165
|
+
|
|
166
|
+
## Retries and idempotency
|
|
167
|
+
|
|
168
|
+
Connection failures and capacity 429s are retried automatically with
|
|
169
|
+
exponential backoff (`max_retries=2` by default). For plain cubes the SDK
|
|
170
|
+
attaches a `client_request_id` idempotency key to every run, so retries can
|
|
171
|
+
never double-charge or double-execute; ambiguous failures (timeouts, 5xx) are
|
|
172
|
+
retried only when that key is present. Pass your own `client_request_id` to
|
|
173
|
+
make retries idempotent across process restarts too.
|
|
174
|
+
|
|
175
|
+
## Configuration
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
client = Cubic(
|
|
179
|
+
api_key="mxk_...", # or CUBIC_API_KEY
|
|
180
|
+
base_url="http://localhost:8010", # or CUBIC_BASE_URL (default: https://api.cubic.zone)
|
|
181
|
+
timeout=120.0,
|
|
182
|
+
max_retries=3,
|
|
183
|
+
)
|
|
184
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
# PyPI distribution name ("cubic" is taken by an unrelated bioimaging package);
|
|
7
|
+
# the import package remains `cubic`.
|
|
8
|
+
name = "cubic-sdk"
|
|
9
|
+
version = "0.3.2"
|
|
10
|
+
description = "Official Python SDK for Cubic — run your Cubes and Polycubes from any application."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.10"
|
|
13
|
+
license = { text = "MIT" }
|
|
14
|
+
authors = [{ name = "Cubic" }]
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx>=0.27",
|
|
17
|
+
"pydantic>=2.7",
|
|
18
|
+
]
|
|
19
|
+
keywords = ["cubic", "llm", "prompts", "sdk"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent",
|
|
25
|
+
"Programming Language :: Python :: 3",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Programming Language :: Python :: 3.13",
|
|
30
|
+
"Typing :: Typed",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://cubic.zone"
|
|
35
|
+
Documentation = "https://cubic.zone/docs"
|
|
36
|
+
Repository = "https://github.com/cubic-zone/cubic-python"
|
|
37
|
+
Changelog = "https://github.com/cubic-zone/cubic-python/blob/main/CHANGELOG.md"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = ["pytest>=8", "pytest-asyncio>=0.23"]
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.wheel]
|
|
43
|
+
packages = ["src/cubic"]
|
|
44
|
+
|
|
45
|
+
[tool.pytest.ini_options]
|
|
46
|
+
testpaths = ["tests"]
|
|
47
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Cubic Python SDK.
|
|
2
|
+
|
|
3
|
+
Quickstart::
|
|
4
|
+
|
|
5
|
+
from cubic import Cubic
|
|
6
|
+
|
|
7
|
+
client = Cubic(api_key="mxk_...")
|
|
8
|
+
result = client.completions.create(
|
|
9
|
+
cube_id="cbe_a1B2c3D4e5F6g7",
|
|
10
|
+
variables={"customer_name": "Ada"},
|
|
11
|
+
)
|
|
12
|
+
print(result.content)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from . import webhooks
|
|
16
|
+
from ._async_client import AsyncCubic
|
|
17
|
+
from ._client import Cubic
|
|
18
|
+
from ._exceptions import (
|
|
19
|
+
APIConnectionError,
|
|
20
|
+
APITimeoutError,
|
|
21
|
+
AuthenticationError,
|
|
22
|
+
CompletionError,
|
|
23
|
+
CompletionNotFoundError,
|
|
24
|
+
CompletionTimeoutError,
|
|
25
|
+
CubeNotFoundError,
|
|
26
|
+
CubicError,
|
|
27
|
+
InsufficientCreditsError,
|
|
28
|
+
InternalServerError,
|
|
29
|
+
InvalidRequestError,
|
|
30
|
+
MissingVariableError,
|
|
31
|
+
ModelNotFoundError,
|
|
32
|
+
NotFoundError,
|
|
33
|
+
PermissionDeniedError,
|
|
34
|
+
ProviderError,
|
|
35
|
+
RateLimitError,
|
|
36
|
+
VersionNotFoundError,
|
|
37
|
+
WaitTimeoutError,
|
|
38
|
+
WebhookSignatureError,
|
|
39
|
+
)
|
|
40
|
+
from ._version import __version__
|
|
41
|
+
from .types import (
|
|
42
|
+
AttemptError,
|
|
43
|
+
CompletionRecord,
|
|
44
|
+
CompletionResult,
|
|
45
|
+
Cube,
|
|
46
|
+
CubeModel,
|
|
47
|
+
Metrics,
|
|
48
|
+
Model,
|
|
49
|
+
PolycubeResult,
|
|
50
|
+
Segment,
|
|
51
|
+
SingleCompletion,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
__all__ = [
|
|
55
|
+
"Cubic",
|
|
56
|
+
"AsyncCubic",
|
|
57
|
+
"webhooks",
|
|
58
|
+
"__version__",
|
|
59
|
+
# exceptions
|
|
60
|
+
"CubicError",
|
|
61
|
+
"APIConnectionError",
|
|
62
|
+
"APITimeoutError",
|
|
63
|
+
"AuthenticationError",
|
|
64
|
+
"PermissionDeniedError",
|
|
65
|
+
"NotFoundError",
|
|
66
|
+
"ModelNotFoundError",
|
|
67
|
+
"CubeNotFoundError",
|
|
68
|
+
"VersionNotFoundError",
|
|
69
|
+
"CompletionNotFoundError",
|
|
70
|
+
"InvalidRequestError",
|
|
71
|
+
"InsufficientCreditsError",
|
|
72
|
+
"RateLimitError",
|
|
73
|
+
"CompletionTimeoutError",
|
|
74
|
+
"InternalServerError",
|
|
75
|
+
"CompletionError",
|
|
76
|
+
"MissingVariableError",
|
|
77
|
+
"ProviderError",
|
|
78
|
+
"WaitTimeoutError",
|
|
79
|
+
"WebhookSignatureError",
|
|
80
|
+
# types
|
|
81
|
+
"AttemptError",
|
|
82
|
+
"Metrics",
|
|
83
|
+
"SingleCompletion",
|
|
84
|
+
"CompletionResult",
|
|
85
|
+
"PolycubeResult",
|
|
86
|
+
"Segment",
|
|
87
|
+
"CompletionRecord",
|
|
88
|
+
"Cube",
|
|
89
|
+
"CubeModel",
|
|
90
|
+
"Model",
|
|
91
|
+
]
|