qubrid 0.1.0__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.
- qubrid-0.1.0/.github/workflows/test.yml +26 -0
- qubrid-0.1.0/.gitignore +9 -0
- qubrid-0.1.0/AGENTS.md +27 -0
- qubrid-0.1.0/CHANGELOG.md +26 -0
- qubrid-0.1.0/LICENSE +21 -0
- qubrid-0.1.0/PKG-INFO +190 -0
- qubrid-0.1.0/README.md +158 -0
- qubrid-0.1.0/examples/audio.py +19 -0
- qubrid-0.1.0/examples/images_video_embeddings.py +27 -0
- qubrid-0.1.0/examples/quickstart_chat.py +27 -0
- qubrid-0.1.0/examples/vision_ocr.py +18 -0
- qubrid-0.1.0/pyproject.toml +64 -0
- qubrid-0.1.0/src/qubrid/__init__.py +56 -0
- qubrid-0.1.0/src/qubrid/__version__.py +3 -0
- qubrid-0.1.0/src/qubrid/_http.py +131 -0
- qubrid-0.1.0/src/qubrid/_types.py +195 -0
- qubrid-0.1.0/src/qubrid/_utils.py +51 -0
- qubrid-0.1.0/src/qubrid/client.py +159 -0
- qubrid-0.1.0/src/qubrid/exceptions.py +141 -0
- qubrid-0.1.0/src/qubrid/resources/__init__.py +0 -0
- qubrid-0.1.0/src/qubrid/resources/audio.py +221 -0
- qubrid-0.1.0/src/qubrid/resources/chat.py +219 -0
- qubrid-0.1.0/src/qubrid/resources/extra.py +463 -0
- qubrid-0.1.0/src/qubrid/resources/vision_ocr.py +171 -0
- qubrid-0.1.0/tests/__init__.py +0 -0
- qubrid-0.1.0/tests/conftest.py +51 -0
- qubrid-0.1.0/tests/test_audio.py +92 -0
- qubrid-0.1.0/tests/test_chat.py +152 -0
- qubrid-0.1.0/tests/test_extra.py +139 -0
- qubrid-0.1.0/tests/test_vision_ocr.py +97 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.9", "3.11", "3.12"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
- uses: actions/setup-python@v5
|
|
17
|
+
with:
|
|
18
|
+
python-version: ${{ matrix.python-version }}
|
|
19
|
+
- name: Install
|
|
20
|
+
run: pip install -e ".[dev]"
|
|
21
|
+
- name: Lint
|
|
22
|
+
run: ruff check src tests examples
|
|
23
|
+
- name: Format check
|
|
24
|
+
run: ruff format --check src tests examples
|
|
25
|
+
- name: Tests
|
|
26
|
+
run: python -m pytest tests/ -q
|
qubrid-0.1.0/.gitignore
ADDED
qubrid-0.1.0/AGENTS.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
First-party Python SDK for Qubrid AI inference (`qubrid` on PyPI). Standalone: `httpx` + `pydantic` only — never add an `openai` dependency.
|
|
4
|
+
|
|
5
|
+
## Commands
|
|
6
|
+
|
|
7
|
+
- Install (editable + dev): `pip install -e ".[dev]"`
|
|
8
|
+
- Tests: `python -m pytest tests/ -q` (27 tests, `respx`-mocked HTTP — no network/key needed)
|
|
9
|
+
- Lint: `ruff check src tests` · Format: `ruff format src tests`
|
|
10
|
+
- `Optional[...]` is deliberate (py39 runtime compat for pydantic); `UP006/7/35/45` are silenced in `pyproject.toml` — do not "modernize" to `X | None`.
|
|
11
|
+
|
|
12
|
+
## Architecture
|
|
13
|
+
|
|
14
|
+
- `src/qubrid/client.py` — `QubridClient` / `AsyncQubridClient` (env `QUBRID_API_KEY`, `base_url` + `legacy_base_url`, `timeout`, `max_retries`). Owns one `httpx` client; resources are thin wrappers over it.
|
|
15
|
+
- `src/qubrid/_http.py` — retry-with-backoff on 429/5xx (honors `Retry-After`), error mapping, SSE `data:` parsing. Sync/async parity lives here.
|
|
16
|
+
- `src/qubrid/_types.py` — pydantic models mirroring `QubridAI-Inc/docs` `api-reference/*/openapi.json`. Model IDs are plain `str` (catalog churns).
|
|
17
|
+
- Resources: `chat.py` (`POST /chat/completions`, streaming + not), `vision_ocr.py` (same path, docs-example defaults), `audio.py` (TTS on `/v1`, STT multipart auto-routed to legacy `/api/v1/qubridai`), `extra.py` (embeddings/images/video).
|
|
18
|
+
- `src/qubrid/exceptions.py` — `QubridError` + typed subclasses carrying `status_code/code/type/param/request_id/body`.
|
|
19
|
+
|
|
20
|
+
## Gotchas (verified vs docs repo + examples repo, 2026-09-08)
|
|
21
|
+
|
|
22
|
+
- Two live prefixes: canonical `https://platform.qubrid.com/v1`, legacy `.../api/v1/qubridai` for STT **only**. Do not mix field names across them.
|
|
23
|
+
- Embeddings has **no** Qubrid OpenAPI spec — `embeddings.create` is an explicitly experimental OpenAI-compatible passthrough (user-approved). Flag shape changes.
|
|
24
|
+
- Image-edits has no OpenAPI file (quickstarts only): local files → multipart, remote URLs → JSON `images:[...]`. Video accepts both `url` and `video_url` (`resolved_url`).
|
|
25
|
+
- No REST API exists for RAG / fine-tuning / GPU provisioning / models listing (UI-only) — do not invent endpoints; raise/flag instead.
|
|
26
|
+
- `api-reference/endpoint/` (`/plants`) is a Mintlify template placeholder, not real.
|
|
27
|
+
- OCR default `tencent/HunyuanOCR` (live-verified 2026-09-08; examples' `Hunyuan/Hunyuan-OCR-1B` 404s `model_not_found`).
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to the `qubrid` package. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
4
|
+
|
|
5
|
+
## [0.1.0] — 2026-09-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial standalone SDK (`httpx` + `pydantic`, no `openai` dependency).
|
|
9
|
+
- `QubridClient` / `AsyncQubridClient` with `QUBRID_API_KEY` env fallback, `base_url` + `legacy_base_url` overrides, configurable timeout/retries.
|
|
10
|
+
- `chat.completions.create` (streaming SSE generator + non-streaming), live-verified vs `api-reference/chat/openapi.json`.
|
|
11
|
+
- `vision.analyze`, `ocr.extract` thin wrappers over `/chat/completions`, live-verified (OCR default corrected to `tencent/HunyuanOCR` — examples' `Hunyuan/Hunyuan-OCR-1B` 404s).
|
|
12
|
+
- `audio.transcriptions.create` (STT multipart, auto-routed to legacy `/api/v1/qubridai/audio/transcribe`), live-verified.
|
|
13
|
+
- `audio.speech.create` (TTS `POST /audio/generations`), `images.generate` / `images.edit`, `videos.generate` (accepts both `url` and `video_url`), `embeddings.create` — retained per-spec but **not working live** (see Fixed/Unverified below).
|
|
14
|
+
- Typed errors (`AuthenticationError`, `RateLimitError`, `InvalidRequestError`, … with `code`/`request_id`/`body`), retry-with-backoff on 429/5xx.
|
|
15
|
+
- Mocked test suite (`respx`), README quickstart + per-category examples.
|
|
16
|
+
|
|
17
|
+
### Live-verification findings (2026-09-08 smoke test, ~$0.0005 spend)
|
|
18
|
+
- Fixed: OCR default `Hunyuan/Hunyuan-OCR-1B` → `tencent/HunyuanOCR` (former is 404 `model_not_found` live; latter verified 200 with usage 1699/17).
|
|
19
|
+
- Unverified: TTS returns `400 content_policy_violation` on all tested inputs including the docs' own example sentence; `images/generations` returns `405 Method Not Allowed` despite exact spec match (`images.edit`, `videos.generate` deliberately untested, same family); `POST /v1/embeddings` is 404 with empty body (endpoint doesn't exist).
|
|
20
|
+
|
|
21
|
+
### Final verification (2026-09-08, v0.1.0 polish)
|
|
22
|
+
- Live-verified end-to-end against the real Qubrid API: chat (streaming + non-streaming), vision, OCR (corrected default `tencent/HunyuanOCR`), STT.
|
|
23
|
+
- Confirmed non-functional: embeddings (endpoint doesn't exist, 404 with empty body); images/video generation (`405 Method Not Allowed` despite exact OpenAPI spec match — likely account/region routing issue on Qubrid's side; siblings untested by decision); TTS (reachable, request shape accepted, but safety filter rejected all tested inputs including Qubrid's own documented example sentence).
|
|
24
|
+
- Clean-room install verified: fresh venv, non-editable `pip install`, import + live quickstart call succeeded from outside the source tree.
|
|
25
|
+
- Total live-testing spend across the full verification effort: ~$0.0006.
|
|
26
|
+
- Docs-only fixes: streaming example guards against `None` deltas; README notes Windows console unicode quirk (`PYTHONIOENCODING=utf-8`). No core SDK logic changed.
|
qubrid-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Qubrid AI Community SDK
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
qubrid-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: qubrid
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: First-party Python SDK for Qubrid AI GPU-accelerated inference (chat, vision, OCR, TTS, STT, images, video, embeddings)
|
|
5
|
+
Project-URL: Homepage, https://platform.qubrid.com
|
|
6
|
+
Project-URL: Documentation, https://github.com/QubridAI-Inc/docs
|
|
7
|
+
Project-URL: Repository, https://github.com/QubridAI-Inc/qubrid-examples
|
|
8
|
+
Project-URL: Changelog, https://github.com/Abhi-shek26/python-sdk-buildup/blob/main/CHANGELOG.md
|
|
9
|
+
Author-email: Qubrid AI Community SDK <support@qubrid.com>
|
|
10
|
+
License: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: ai,chat,inference,llm,qubrid,sdk,stt,tts,vision
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: httpx<1,>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
29
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
30
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# Qubrid Python SDK — `qubrid`
|
|
34
|
+
|
|
35
|
+
First-party, standalone Python client for [Qubrid AI](https://platform.qubrid.com) GPU-accelerated inference. No `openai` dependency (`httpx` + `pydantic` only).
|
|
36
|
+
|
|
37
|
+
**Docs truth:** endpoint shapes verified against [`QubridAI-Inc/docs`](https://github.com/QubridAI-Inc/docs) (`api-reference/*/openapi.json`) and runnable payloads in [`QubridAI-Inc/qubrid-examples`](https://github.com/QubridAI-Inc/qubrid-examples).
|
|
38
|
+
|
|
39
|
+
| Resource | Endpoint | Base |
|
|
40
|
+
|---|---|---|
|
|
41
|
+
| `client.chat.completions` | `POST /chat/completions` (OpenAI-compatible, `stream` supported) | `…/v1` |
|
|
42
|
+
| `client.vision` | same `POST /chat/completions` (image_url parts) | `…/v1` |
|
|
43
|
+
| `client.ocr` | same `POST /chat/completions` (default `tencent/HunyuanOCR`, live-verified) | `…/v1` |
|
|
44
|
+
| `client.audio.speech` (TTS) | `POST /audio/generations` `{model,text,voice,language_type}` | `…/v1` |
|
|
45
|
+
| `client.audio.transcriptions` (STT) | `POST /audio/transcribe` multipart `{file,model}` | `…/api/v1/qubridai` (legacy — auto-routed) |
|
|
46
|
+
| `client.images` | `POST /images/generations`, `POST /images/edits` | `…/v1` |
|
|
47
|
+
| `client.videos` | `POST /videos/generations` | `…/v1` |
|
|
48
|
+
| `client.embeddings` | `POST /embeddings` — ⚠️ **EXPERIMENTAL**, no Qubrid OpenAPI spec | `…/v1` |
|
|
49
|
+
|
|
50
|
+
**Not in the SDK (no documented REST API — UI-only):** RAG (beta upload + Chat UI), no-code Fine-Tuning studio, GPU Instances/Clusters/Bare-Metal provisioning, models listing (`GET /models`). Model IDs are plain `str` (catalog of 100+ changes often — never an enum).
|
|
51
|
+
|
|
52
|
+
## Live verification status (smoke-tested 2026-09-08)
|
|
53
|
+
|
|
54
|
+
Confirmed working end-to-end: chat (+ streaming), vision, OCR (default `tencent/HunyuanOCR`), STT.
|
|
55
|
+
|
|
56
|
+
- **TTS — unverified:** endpoint reachable, shape accepted, but the safety filter rejected every tested input (`400 content_policy_violation`), including the docs' own example sentence.
|
|
57
|
+
- **Images / video — currently unroutable:** `POST /v1/images/generations` returns `405 Method Not Allowed` despite matching the OpenAPI spec exactly; `images.edit` / `videos.generate` untested (same family, deliberately not probed). Code retained per-spec; treat as unstable.
|
|
58
|
+
- **Embeddings — doesn't exist:** `POST /v1/embeddings` returns 404 with an empty body. The method is kept as an experimental stub only.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install qubrid
|
|
64
|
+
# or from source:
|
|
65
|
+
pip install -e ".[dev]"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Requires Python 3.9+, `QUBRID_API_KEY` from https://platform.qubrid.com/api-keys:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
export QUBRID_API_KEY="qb_..."
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Quickstart
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from qubrid import QubridClient
|
|
78
|
+
|
|
79
|
+
client = QubridClient() # reads QUBRID_API_KEY
|
|
80
|
+
|
|
81
|
+
resp = client.chat.completions.create(
|
|
82
|
+
model="openai/gpt-oss-120b",
|
|
83
|
+
messages=[{"role": "user", "content": "Summarize this ticket into next steps."}],
|
|
84
|
+
max_tokens=4096,
|
|
85
|
+
temperature=0.7,
|
|
86
|
+
)
|
|
87
|
+
print(resp.content)
|
|
88
|
+
print(resp.usage.total_tokens)
|
|
89
|
+
|
|
90
|
+
# Streaming
|
|
91
|
+
for chunk in client.chat.completions.create(
|
|
92
|
+
model="deepseek-ai/DeepSeek-V3.2",
|
|
93
|
+
messages=[{"role": "user", "content": "Write a haiku about GPUs."}],
|
|
94
|
+
stream=True,
|
|
95
|
+
):
|
|
96
|
+
print(chunk.delta, end="", flush=True)
|
|
97
|
+
|
|
98
|
+
# Async
|
|
99
|
+
import asyncio
|
|
100
|
+
from qubrid import AsyncQubridClient
|
|
101
|
+
|
|
102
|
+
async def main():
|
|
103
|
+
async with AsyncQubridClient() as c:
|
|
104
|
+
r = await c.chat.completions.create(
|
|
105
|
+
model="openai/gpt-oss-120b", messages=[{"role": "user", "content": "Hi"}])
|
|
106
|
+
print(r.content)
|
|
107
|
+
|
|
108
|
+
asyncio.run(main())
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## One example per category
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# Vision (Qwen/Qwen3-VL-Plus + image_url parts)
|
|
115
|
+
v = client.vision.analyze(
|
|
116
|
+
prompt="What is in this image?",
|
|
117
|
+
image_urls="https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg",
|
|
118
|
+
)
|
|
119
|
+
print(v.content)
|
|
120
|
+
|
|
121
|
+
# OCR (default tencent/HunyuanOCR — live-verified; examples' Hunyuan/Hunyuan-OCR-1B 404s)
|
|
122
|
+
o = client.ocr.extract(image_urls="https://example.com/receipt.jpg")
|
|
123
|
+
print(o.content)
|
|
124
|
+
|
|
125
|
+
# TTS → hosted mp3 URL
|
|
126
|
+
s = client.audio.speech.create(text="Today is a wonderful day to build something people love!",
|
|
127
|
+
voice="Cherry", language_type="Auto")
|
|
128
|
+
print(s.url)
|
|
129
|
+
|
|
130
|
+
# STT (multipart upload, auto-routed to legacy prefix)
|
|
131
|
+
t = client.audio.transcriptions.create(model="openai/whisper-large-v3", file="clip.wav")
|
|
132
|
+
print(t.text)
|
|
133
|
+
|
|
134
|
+
# Images
|
|
135
|
+
img = client.images.generate(model="p-image", prompt="a lighthouse at dusk",
|
|
136
|
+
aspect_ratio="16:9", output_format="webp")
|
|
137
|
+
print(img.data[0].url)
|
|
138
|
+
edit = client.images.edit(model="p-image-edit", prompt="add a red flag",
|
|
139
|
+
image="https://example.com/in.png")
|
|
140
|
+
|
|
141
|
+
# Video (only documented model: p-video, ≤10s)
|
|
142
|
+
vid = client.videos.generate(model="p-video", prompt="ocean waves", duration=5)
|
|
143
|
+
print(vid.url)
|
|
144
|
+
|
|
145
|
+
# Embeddings — EXPERIMENTAL passthrough (no Qubrid spec; may change)
|
|
146
|
+
e = client.embeddings.create(model="BAAI/bge-large-en-v1.5", input="hello world")
|
|
147
|
+
print(e.data[0].embedding[:4])
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
More in [`examples/`](examples/).
|
|
151
|
+
|
|
152
|
+
## Configuration
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
QubridClient(
|
|
156
|
+
api_key="qb_...", # or QUBRID_API_KEY env
|
|
157
|
+
base_url="https://platform.qubrid.com/v1", # override canonical
|
|
158
|
+
legacy_base_url="https://platform.qubrid.com/api/v1/qubridai", # STT only
|
|
159
|
+
timeout=60.0, # seconds; per-call `timeout=` also supported
|
|
160
|
+
max_retries=2, # retry-with-backoff on 429/5xx
|
|
161
|
+
)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## Errors
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from qubrid import QubridError, AuthenticationError, RateLimitError
|
|
168
|
+
|
|
169
|
+
try:
|
|
170
|
+
client.chat.completions.create(model="m", messages=[])
|
|
171
|
+
except AuthenticationError:
|
|
172
|
+
... # bad/missing key (401)
|
|
173
|
+
except RateLimitError as e:
|
|
174
|
+
print(e.status_code, e.code, e.request_id, e.body)
|
|
175
|
+
except QubridError as e:
|
|
176
|
+
... # base class: InvalidRequestError, NotFoundError,
|
|
177
|
+
# PermissionDeniedError, QuotaExceededError, ServerError, APIConnectionError
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Docs & flags
|
|
181
|
+
|
|
182
|
+
- STT lives on the legacy prefix — the client handles it; only override `legacy_base_url` for staging/mocks.
|
|
183
|
+
- Live-verified 2026-09-08: runnable examples' `Hunyuan/Hunyuan-OCR-1B` returns 404 `model_not_found`; the working id is `tencent/HunyuanOCR` (the SDK default).
|
|
184
|
+
- Video quickstart JS reads `data[0].video_url` while the spec says `url` — both accepted (`VideoData.resolved_url`).
|
|
185
|
+
- Image-edit has no OpenAPI file (quickstarts only); local files → multipart, remote URLs → JSON `images:[...]`.
|
|
186
|
+
- Anything under `api-reference/endpoint/` (`/plants`) is a Mintlify template placeholder, not a real API.
|
|
187
|
+
|
|
188
|
+
## Known environment quirks
|
|
189
|
+
|
|
190
|
+
- **Garbled unicode on Windows terminals:** model output containing emoji, accented characters, or other non-ASCII text may render as `�` in default Windows consoles (e.g. `cmd.exe` / PowerShell with a legacy code page). This is a console-encoding artifact, not an SDK bug — the returned strings are correct. If affected, run with `PYTHONIOENCODING=utf-8` or use a UTF-8-capable terminal (e.g. Windows Terminal).
|
qubrid-0.1.0/README.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Qubrid Python SDK — `qubrid`
|
|
2
|
+
|
|
3
|
+
First-party, standalone Python client for [Qubrid AI](https://platform.qubrid.com) GPU-accelerated inference. No `openai` dependency (`httpx` + `pydantic` only).
|
|
4
|
+
|
|
5
|
+
**Docs truth:** endpoint shapes verified against [`QubridAI-Inc/docs`](https://github.com/QubridAI-Inc/docs) (`api-reference/*/openapi.json`) and runnable payloads in [`QubridAI-Inc/qubrid-examples`](https://github.com/QubridAI-Inc/qubrid-examples).
|
|
6
|
+
|
|
7
|
+
| Resource | Endpoint | Base |
|
|
8
|
+
|---|---|---|
|
|
9
|
+
| `client.chat.completions` | `POST /chat/completions` (OpenAI-compatible, `stream` supported) | `…/v1` |
|
|
10
|
+
| `client.vision` | same `POST /chat/completions` (image_url parts) | `…/v1` |
|
|
11
|
+
| `client.ocr` | same `POST /chat/completions` (default `tencent/HunyuanOCR`, live-verified) | `…/v1` |
|
|
12
|
+
| `client.audio.speech` (TTS) | `POST /audio/generations` `{model,text,voice,language_type}` | `…/v1` |
|
|
13
|
+
| `client.audio.transcriptions` (STT) | `POST /audio/transcribe` multipart `{file,model}` | `…/api/v1/qubridai` (legacy — auto-routed) |
|
|
14
|
+
| `client.images` | `POST /images/generations`, `POST /images/edits` | `…/v1` |
|
|
15
|
+
| `client.videos` | `POST /videos/generations` | `…/v1` |
|
|
16
|
+
| `client.embeddings` | `POST /embeddings` — ⚠️ **EXPERIMENTAL**, no Qubrid OpenAPI spec | `…/v1` |
|
|
17
|
+
|
|
18
|
+
**Not in the SDK (no documented REST API — UI-only):** RAG (beta upload + Chat UI), no-code Fine-Tuning studio, GPU Instances/Clusters/Bare-Metal provisioning, models listing (`GET /models`). Model IDs are plain `str` (catalog of 100+ changes often — never an enum).
|
|
19
|
+
|
|
20
|
+
## Live verification status (smoke-tested 2026-09-08)
|
|
21
|
+
|
|
22
|
+
Confirmed working end-to-end: chat (+ streaming), vision, OCR (default `tencent/HunyuanOCR`), STT.
|
|
23
|
+
|
|
24
|
+
- **TTS — unverified:** endpoint reachable, shape accepted, but the safety filter rejected every tested input (`400 content_policy_violation`), including the docs' own example sentence.
|
|
25
|
+
- **Images / video — currently unroutable:** `POST /v1/images/generations` returns `405 Method Not Allowed` despite matching the OpenAPI spec exactly; `images.edit` / `videos.generate` untested (same family, deliberately not probed). Code retained per-spec; treat as unstable.
|
|
26
|
+
- **Embeddings — doesn't exist:** `POST /v1/embeddings` returns 404 with an empty body. The method is kept as an experimental stub only.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install qubrid
|
|
32
|
+
# or from source:
|
|
33
|
+
pip install -e ".[dev]"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Requires Python 3.9+, `QUBRID_API_KEY` from https://platform.qubrid.com/api-keys:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
export QUBRID_API_KEY="qb_..."
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Quickstart
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from qubrid import QubridClient
|
|
46
|
+
|
|
47
|
+
client = QubridClient() # reads QUBRID_API_KEY
|
|
48
|
+
|
|
49
|
+
resp = client.chat.completions.create(
|
|
50
|
+
model="openai/gpt-oss-120b",
|
|
51
|
+
messages=[{"role": "user", "content": "Summarize this ticket into next steps."}],
|
|
52
|
+
max_tokens=4096,
|
|
53
|
+
temperature=0.7,
|
|
54
|
+
)
|
|
55
|
+
print(resp.content)
|
|
56
|
+
print(resp.usage.total_tokens)
|
|
57
|
+
|
|
58
|
+
# Streaming
|
|
59
|
+
for chunk in client.chat.completions.create(
|
|
60
|
+
model="deepseek-ai/DeepSeek-V3.2",
|
|
61
|
+
messages=[{"role": "user", "content": "Write a haiku about GPUs."}],
|
|
62
|
+
stream=True,
|
|
63
|
+
):
|
|
64
|
+
print(chunk.delta, end="", flush=True)
|
|
65
|
+
|
|
66
|
+
# Async
|
|
67
|
+
import asyncio
|
|
68
|
+
from qubrid import AsyncQubridClient
|
|
69
|
+
|
|
70
|
+
async def main():
|
|
71
|
+
async with AsyncQubridClient() as c:
|
|
72
|
+
r = await c.chat.completions.create(
|
|
73
|
+
model="openai/gpt-oss-120b", messages=[{"role": "user", "content": "Hi"}])
|
|
74
|
+
print(r.content)
|
|
75
|
+
|
|
76
|
+
asyncio.run(main())
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## One example per category
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Vision (Qwen/Qwen3-VL-Plus + image_url parts)
|
|
83
|
+
v = client.vision.analyze(
|
|
84
|
+
prompt="What is in this image?",
|
|
85
|
+
image_urls="https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg",
|
|
86
|
+
)
|
|
87
|
+
print(v.content)
|
|
88
|
+
|
|
89
|
+
# OCR (default tencent/HunyuanOCR — live-verified; examples' Hunyuan/Hunyuan-OCR-1B 404s)
|
|
90
|
+
o = client.ocr.extract(image_urls="https://example.com/receipt.jpg")
|
|
91
|
+
print(o.content)
|
|
92
|
+
|
|
93
|
+
# TTS → hosted mp3 URL
|
|
94
|
+
s = client.audio.speech.create(text="Today is a wonderful day to build something people love!",
|
|
95
|
+
voice="Cherry", language_type="Auto")
|
|
96
|
+
print(s.url)
|
|
97
|
+
|
|
98
|
+
# STT (multipart upload, auto-routed to legacy prefix)
|
|
99
|
+
t = client.audio.transcriptions.create(model="openai/whisper-large-v3", file="clip.wav")
|
|
100
|
+
print(t.text)
|
|
101
|
+
|
|
102
|
+
# Images
|
|
103
|
+
img = client.images.generate(model="p-image", prompt="a lighthouse at dusk",
|
|
104
|
+
aspect_ratio="16:9", output_format="webp")
|
|
105
|
+
print(img.data[0].url)
|
|
106
|
+
edit = client.images.edit(model="p-image-edit", prompt="add a red flag",
|
|
107
|
+
image="https://example.com/in.png")
|
|
108
|
+
|
|
109
|
+
# Video (only documented model: p-video, ≤10s)
|
|
110
|
+
vid = client.videos.generate(model="p-video", prompt="ocean waves", duration=5)
|
|
111
|
+
print(vid.url)
|
|
112
|
+
|
|
113
|
+
# Embeddings — EXPERIMENTAL passthrough (no Qubrid spec; may change)
|
|
114
|
+
e = client.embeddings.create(model="BAAI/bge-large-en-v1.5", input="hello world")
|
|
115
|
+
print(e.data[0].embedding[:4])
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
More in [`examples/`](examples/).
|
|
119
|
+
|
|
120
|
+
## Configuration
|
|
121
|
+
|
|
122
|
+
```python
|
|
123
|
+
QubridClient(
|
|
124
|
+
api_key="qb_...", # or QUBRID_API_KEY env
|
|
125
|
+
base_url="https://platform.qubrid.com/v1", # override canonical
|
|
126
|
+
legacy_base_url="https://platform.qubrid.com/api/v1/qubridai", # STT only
|
|
127
|
+
timeout=60.0, # seconds; per-call `timeout=` also supported
|
|
128
|
+
max_retries=2, # retry-with-backoff on 429/5xx
|
|
129
|
+
)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Errors
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
from qubrid import QubridError, AuthenticationError, RateLimitError
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
client.chat.completions.create(model="m", messages=[])
|
|
139
|
+
except AuthenticationError:
|
|
140
|
+
... # bad/missing key (401)
|
|
141
|
+
except RateLimitError as e:
|
|
142
|
+
print(e.status_code, e.code, e.request_id, e.body)
|
|
143
|
+
except QubridError as e:
|
|
144
|
+
... # base class: InvalidRequestError, NotFoundError,
|
|
145
|
+
# PermissionDeniedError, QuotaExceededError, ServerError, APIConnectionError
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Docs & flags
|
|
149
|
+
|
|
150
|
+
- STT lives on the legacy prefix — the client handles it; only override `legacy_base_url` for staging/mocks.
|
|
151
|
+
- Live-verified 2026-09-08: runnable examples' `Hunyuan/Hunyuan-OCR-1B` returns 404 `model_not_found`; the working id is `tencent/HunyuanOCR` (the SDK default).
|
|
152
|
+
- Video quickstart JS reads `data[0].video_url` while the spec says `url` — both accepted (`VideoData.resolved_url`).
|
|
153
|
+
- Image-edit has no OpenAPI file (quickstarts only); local files → multipart, remote URLs → JSON `images:[...]`.
|
|
154
|
+
- Anything under `api-reference/endpoint/` (`/plants`) is a Mintlify template placeholder, not a real API.
|
|
155
|
+
|
|
156
|
+
## Known environment quirks
|
|
157
|
+
|
|
158
|
+
- **Garbled unicode on Windows terminals:** model output containing emoji, accented characters, or other non-ASCII text may render as `�` in default Windows consoles (e.g. `cmd.exe` / PowerShell with a legacy code page). This is a console-encoding artifact, not an SDK bug — the returned strings are correct. If affected, run with `PYTHONIOENCODING=utf-8` or use a UTF-8-capable terminal (e.g. Windows Terminal).
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""TTS + STT examples."""
|
|
2
|
+
|
|
3
|
+
from qubrid import QubridClient
|
|
4
|
+
|
|
5
|
+
client = QubridClient()
|
|
6
|
+
|
|
7
|
+
speech = client.audio.speech.create(
|
|
8
|
+
model="qwen3-tts-flash",
|
|
9
|
+
text="Today is a wonderful day to build something people love!",
|
|
10
|
+
voice="Cherry",
|
|
11
|
+
language_type="Auto",
|
|
12
|
+
)
|
|
13
|
+
print(speech.url)
|
|
14
|
+
|
|
15
|
+
transcript = client.audio.transcriptions.create(
|
|
16
|
+
model="openai/whisper-large-v3",
|
|
17
|
+
file="clip.wav", # local path, file object, or (filename, bytes) tuple
|
|
18
|
+
)
|
|
19
|
+
print(transcript.text)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Images, video, embeddings (experimental) examples."""
|
|
2
|
+
|
|
3
|
+
from qubrid import QubridClient
|
|
4
|
+
|
|
5
|
+
client = QubridClient()
|
|
6
|
+
|
|
7
|
+
img = client.images.generate(
|
|
8
|
+
model="p-image",
|
|
9
|
+
prompt="a lighthouse at dusk",
|
|
10
|
+
aspect_ratio="16:9",
|
|
11
|
+
output_format="webp",
|
|
12
|
+
)
|
|
13
|
+
print(img.data[0].url)
|
|
14
|
+
|
|
15
|
+
edit = client.images.edit(
|
|
16
|
+
model="p-image-edit",
|
|
17
|
+
prompt="add a red flag",
|
|
18
|
+
image="https://example.com/in.png", # remote URL → JSON; local path → multipart
|
|
19
|
+
)
|
|
20
|
+
print(edit.data[0].url)
|
|
21
|
+
|
|
22
|
+
vid = client.videos.generate(model="p-video", prompt="ocean waves at sunset", duration=5)
|
|
23
|
+
print(vid.url)
|
|
24
|
+
|
|
25
|
+
# EXPERIMENTAL: no Qubrid OpenAPI spec for embeddings; OpenAI-compatible passthrough.
|
|
26
|
+
emb = client.embeddings.create(model="BAAI/bge-large-en-v1.5", input="hello world")
|
|
27
|
+
print(emb.data[0].embedding[:4])
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"""Qubrid SDK quickstart — chat (non-streaming + streaming)."""
|
|
2
|
+
|
|
3
|
+
from qubrid import QubridClient
|
|
4
|
+
|
|
5
|
+
client = QubridClient() # QUBRID_API_KEY
|
|
6
|
+
|
|
7
|
+
resp = client.chat.completions.create(
|
|
8
|
+
model="openai/gpt-oss-120b",
|
|
9
|
+
messages=[
|
|
10
|
+
{"role": "user", "content": "Summarize this support ticket into bullet-point next steps."}
|
|
11
|
+
],
|
|
12
|
+
max_tokens=4096,
|
|
13
|
+
temperature=0.7,
|
|
14
|
+
top_p=1,
|
|
15
|
+
)
|
|
16
|
+
print(resp.content)
|
|
17
|
+
|
|
18
|
+
for chunk in client.chat.completions.create(
|
|
19
|
+
model="openai/gpt-oss-120b",
|
|
20
|
+
messages=[{"role": "user", "content": "Write a haiku about GPUs."}],
|
|
21
|
+
stream=True,
|
|
22
|
+
):
|
|
23
|
+
# Some SSE events carry no text (e.g. role-only first chunk) — skip them
|
|
24
|
+
# so the output doesn't contain literal "None"s.
|
|
25
|
+
if chunk.delta:
|
|
26
|
+
print(chunk.delta, end="", flush=True)
|
|
27
|
+
print()
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Vision + OCR examples (docs-example payloads)."""
|
|
2
|
+
|
|
3
|
+
from qubrid import QubridClient
|
|
4
|
+
|
|
5
|
+
client = QubridClient()
|
|
6
|
+
|
|
7
|
+
vision = client.vision.analyze(
|
|
8
|
+
model="Qwen/Qwen3-VL-Plus",
|
|
9
|
+
prompt="What is in this image? Describe the main elements.",
|
|
10
|
+
image_urls="https://cdn.britannica.com/61/93061-050-99147DCE/Statue-of-Liberty-Island-New-York-Bay.jpg",
|
|
11
|
+
)
|
|
12
|
+
print(vision.content)
|
|
13
|
+
|
|
14
|
+
ocr = client.ocr.extract(
|
|
15
|
+
image_urls="https://example.com/receipt.jpg", # replace with a real image URL
|
|
16
|
+
# model="tencent/HunyuanOCR", # default (live-verified)
|
|
17
|
+
)
|
|
18
|
+
print(ocr.content)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "qubrid"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "First-party Python SDK for Qubrid AI GPU-accelerated inference (chat, vision, OCR, TTS, STT, images, video, embeddings)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Qubrid AI Community SDK", email = "support@qubrid.com" }]
|
|
13
|
+
keywords = ["qubrid", "ai", "llm", "inference", "sdk", "chat", "vision", "tts", "stt"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"License :: OSI Approved :: MIT License",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.9",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"httpx>=0.27,<1",
|
|
27
|
+
"pydantic>=2.0",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://platform.qubrid.com"
|
|
32
|
+
Documentation = "https://github.com/QubridAI-Inc/docs"
|
|
33
|
+
Repository = "https://github.com/QubridAI-Inc/qubrid-examples"
|
|
34
|
+
Changelog = "https://github.com/Abhi-shek26/python-sdk-buildup/blob/main/CHANGELOG.md"
|
|
35
|
+
|
|
36
|
+
[project.optional-dependencies]
|
|
37
|
+
dev = [
|
|
38
|
+
"pytest>=8",
|
|
39
|
+
"pytest-asyncio>=0.23",
|
|
40
|
+
"respx>=0.22",
|
|
41
|
+
"mypy>=1.0",
|
|
42
|
+
"ruff>=0.4",
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["src/qubrid"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|
|
50
|
+
asyncio_mode = "auto"
|
|
51
|
+
|
|
52
|
+
[tool.ruff]
|
|
53
|
+
target-version = "py39"
|
|
54
|
+
line-length = 100
|
|
55
|
+
|
|
56
|
+
[tool.ruff.lint]
|
|
57
|
+
# Optional[...] kept deliberately: `X | None` breaks at runtime on Python 3.9
|
|
58
|
+
# for pydantic-evaluated annotations, so silence pyupgrade rewrites.
|
|
59
|
+
ignore = ["UP006", "UP007", "UP035", "UP045", "PYI034"]
|
|
60
|
+
|
|
61
|
+
[tool.mypy]
|
|
62
|
+
python_version = "3.9"
|
|
63
|
+
strict = false
|
|
64
|
+
ignore_missing_imports = true
|