bee-sdk 0.1.1__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.
- bee_sdk-0.1.1/.gitignore +161 -0
- bee_sdk-0.1.1/PKG-INFO +182 -0
- bee_sdk-0.1.1/README.md +145 -0
- bee_sdk-0.1.1/bee_sdk/__init__.py +42 -0
- bee_sdk-0.1.1/bee_sdk/client.py +329 -0
- bee_sdk-0.1.1/bee_sdk/types.py +51 -0
- bee_sdk-0.1.1/pyproject.toml +83 -0
bee_sdk-0.1.1/.gitignore
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# Dependencies
|
|
2
|
+
node_modules/
|
|
3
|
+
.pnpm-store/
|
|
4
|
+
.yarn/
|
|
5
|
+
.pnp.*
|
|
6
|
+
|
|
7
|
+
# Build outputs
|
|
8
|
+
.next/
|
|
9
|
+
out/
|
|
10
|
+
dist/
|
|
11
|
+
build/
|
|
12
|
+
.turbo/
|
|
13
|
+
|
|
14
|
+
# Python
|
|
15
|
+
__pycache__/
|
|
16
|
+
*.pyc
|
|
17
|
+
*.pyo
|
|
18
|
+
*.pyd
|
|
19
|
+
.venv/
|
|
20
|
+
venv/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
*.egg
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.mypy_cache/
|
|
25
|
+
.ruff_cache/
|
|
26
|
+
|
|
27
|
+
# Environment files
|
|
28
|
+
.env
|
|
29
|
+
.env.*
|
|
30
|
+
!.env.example
|
|
31
|
+
|
|
32
|
+
# Auth-bearing config files — never commit registry tokens
|
|
33
|
+
.npmrc
|
|
34
|
+
**/.npmrc
|
|
35
|
+
|
|
36
|
+
# OS files
|
|
37
|
+
.DS_Store
|
|
38
|
+
Thumbs.db
|
|
39
|
+
*.swp
|
|
40
|
+
*.swo
|
|
41
|
+
*~
|
|
42
|
+
|
|
43
|
+
# Logs
|
|
44
|
+
logs/
|
|
45
|
+
*.log
|
|
46
|
+
npm-debug.log*
|
|
47
|
+
yarn-debug.log*
|
|
48
|
+
yarn-error.log*
|
|
49
|
+
pnpm-debug.log*
|
|
50
|
+
|
|
51
|
+
# Editor directories and files
|
|
52
|
+
.idea/
|
|
53
|
+
.vscode/
|
|
54
|
+
*.suo
|
|
55
|
+
*.ntvs*
|
|
56
|
+
*.njsproj
|
|
57
|
+
*.sln
|
|
58
|
+
*.sw?
|
|
59
|
+
|
|
60
|
+
# Cache
|
|
61
|
+
.cache/
|
|
62
|
+
.parcel-cache/
|
|
63
|
+
.eslintcache
|
|
64
|
+
.stylelintcache
|
|
65
|
+
|
|
66
|
+
# Testing
|
|
67
|
+
coverage/
|
|
68
|
+
.nyc_output/
|
|
69
|
+
|
|
70
|
+
# Secrets & credentials
|
|
71
|
+
*.pem
|
|
72
|
+
*.cert
|
|
73
|
+
*.key
|
|
74
|
+
*-sa-key.json
|
|
75
|
+
*credentials*.json
|
|
76
|
+
|
|
77
|
+
# ML checkpoints & model artifacts
|
|
78
|
+
autopilot_checkpoints/
|
|
79
|
+
quantum_autopilot_checkpoints/
|
|
80
|
+
bee_daemon_state/
|
|
81
|
+
bee_community/
|
|
82
|
+
*.pt
|
|
83
|
+
*.bin
|
|
84
|
+
*.safetensors
|
|
85
|
+
*.gguf
|
|
86
|
+
|
|
87
|
+
# Archived/deprecated training runs — kept locally for reference, never pushed
|
|
88
|
+
data/archive/
|
|
89
|
+
|
|
90
|
+
# Deprecated adapter checkpoints (SmolLM2-360M Cell base) — superseded
|
|
91
|
+
# 2026-05-07 by the migration to google/gemma-4-E4B-it. Kept locally
|
|
92
|
+
# for audit trail (per docs/governance/current.md §35 migration entry);
|
|
93
|
+
# never repushed. Binary .safetensors are large (~MB each) and the
|
|
94
|
+
# registry at registry/adapter_releases.json already records the metadata
|
|
95
|
+
# (state=deprecated). Re-train on the current Cell base before populating
|
|
96
|
+
# this tree.
|
|
97
|
+
data/adapters/bee-cell/tier1/
|
|
98
|
+
|
|
99
|
+
# Stale eval-report runs (SmolLM2-360M / SmolLM2-1.7B) from the killed
|
|
100
|
+
# 2026-05-07 promotion eval. Superseded by eval runs against the current
|
|
101
|
+
# Cell base once adapters are retrained on Gemma 4 E4B. Keep on disk for
|
|
102
|
+
# audit trail; don't re-push.
|
|
103
|
+
data/eval_reports/matrix/full_cell_2026-05-07/
|
|
104
|
+
data/eval_reports/matrix/smoke_2026-05-07/
|
|
105
|
+
|
|
106
|
+
# FAISS indexes (regenerable)
|
|
107
|
+
*.faiss
|
|
108
|
+
.vercel
|
|
109
|
+
.env*.local
|
|
110
|
+
|
|
111
|
+
# Claude Code local agent state — settings are operator-local but
|
|
112
|
+
# rule files are repo-wide steering and SHOULD be tracked so every
|
|
113
|
+
# session inherits them. See `~/.claude/CLAUDE.md` Evidence-First
|
|
114
|
+
# rule which documents `.claude/rules/*.md` as the canonical
|
|
115
|
+
# location for agent steering rules.
|
|
116
|
+
.claude/*
|
|
117
|
+
!.claude/rules/
|
|
118
|
+
!.claude/rules/**
|
|
119
|
+
|
|
120
|
+
# Bee Mobile is bare React Native CLI now (no Expo). The android/ and
|
|
121
|
+
# ios/ directories ARE source-controlled — only their build outputs and
|
|
122
|
+
# generated junk are excluded. Granular per-platform excludes live in
|
|
123
|
+
# apps/mobile/.gitignore (from the @react-native-community/cli template).
|
|
124
|
+
|
|
125
|
+
# Android release keystore — NEVER commit. Public fingerprints (SHA-256
|
|
126
|
+
# / SHA-1) ARE committed at apps/mobile/keystore/*.fingerprints.txt so
|
|
127
|
+
# anyone can verify a published APK is signed by the canonical Bee key.
|
|
128
|
+
# Back up the keystore off-machine.
|
|
129
|
+
apps/mobile/keystore/*.keystore
|
|
130
|
+
apps/mobile/keystore/*.jks
|
|
131
|
+
apps/mobile/keystores/
|
|
132
|
+
apps/api/tsconfig.tsbuildinfo
|
|
133
|
+
|
|
134
|
+
# APK release artifacts — never commit binary releases. They live on
|
|
135
|
+
# Vercel Blob; the manifest at apps/web/public/.well-known/bee-android-
|
|
136
|
+
# release.json is the source of truth referenced by /download.
|
|
137
|
+
apps/web/public/download/*.apk
|
|
138
|
+
apps/web/public/download/*.aab
|
|
139
|
+
|
|
140
|
+
# Folded-in legacy directories — fully superseded by tracked siblings
|
|
141
|
+
# (apps/web absorbed apps/api + apps/workspace + apps/portal.old;
|
|
142
|
+
# packages/core supersedes packages/shared; bee/static/ for static/;
|
|
143
|
+
# infra/db/migrations/ for supabase/; docs/marketing/ for marketplace/;
|
|
144
|
+
# workers/ for the loose scripts/colab_train.py + kaggle/lightning).
|
|
145
|
+
# pnpm-workspace.yaml separately excludes these from the workspace
|
|
146
|
+
# scan; this gitignore block is a belt-and-suspenders so if any tool
|
|
147
|
+
# ever recreates them locally, they don't pollute `git status`.
|
|
148
|
+
apps/api/
|
|
149
|
+
apps/hf-space/
|
|
150
|
+
apps/portal.old/
|
|
151
|
+
apps/workspace/
|
|
152
|
+
packages/shared/
|
|
153
|
+
marketplace/
|
|
154
|
+
static/
|
|
155
|
+
supabase/
|
|
156
|
+
scripts/colab_train.py
|
|
157
|
+
scripts/colab_train.ipynb
|
|
158
|
+
scripts/kaggle_online_train.py
|
|
159
|
+
scripts/launch_lightning_job.py
|
|
160
|
+
scripts/lightning_train.py
|
|
161
|
+
scripts/training-run-schema.md
|
bee_sdk-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bee-sdk
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Official Python client for the Bee Intelligence Engine — domain-specialized LoRA-routed LLM by CUI Labs.
|
|
5
|
+
Project-URL: Homepage, https://bee.cuilabs.io
|
|
6
|
+
Project-URL: Documentation, https://bee.cuilabs.io/docs/sdks
|
|
7
|
+
Project-URL: Repository, https://github.com/cuilabs/bee
|
|
8
|
+
Project-URL: Issues, https://github.com/cuilabs/bee-community/issues
|
|
9
|
+
Project-URL: Changelog, https://bee.cuilabs.io/changelog
|
|
10
|
+
Project-URL: Hugging Face, https://huggingface.co/cuilabs
|
|
11
|
+
Author-email: "CUI Labs (Pte.) Ltd." <engineering@cuilabs.io>
|
|
12
|
+
License: Apache-2.0
|
|
13
|
+
Keywords: ai,bee,blockchain,cybersecurity,domain-experts,huggingface,llm,lora,mcp,quantum
|
|
14
|
+
Classifier: Development Status :: 4 - Beta
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Provides-Extra: async
|
|
29
|
+
Requires-Dist: httpx>=0.27; extra == 'async'
|
|
30
|
+
Provides-Extra: dev
|
|
31
|
+
Requires-Dist: httpx>=0.27; extra == 'dev'
|
|
32
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
34
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
35
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# bee-sdk
|
|
39
|
+
|
|
40
|
+
Official Python client for the **Bee Intelligence Engine** — a domain-specialized LLM by [CUI Labs](https://bee.cuilabs.io) routing per-domain LoRA adapters over a verified open-weight base.
|
|
41
|
+
|
|
42
|
+
SDK version: `0.1.1` (pre-release).
|
|
43
|
+
|
|
44
|
+
> **Status:** functional sync + async client (stdlib + optional `httpx`).
|
|
45
|
+
> The SDK targets the Bee `/chat/completions` API contract on
|
|
46
|
+
> production via the public gateway `https://bee.cuilabs.io/bee` — this
|
|
47
|
+
> is the default and it is where API-key auth, plan / per-tier allowance
|
|
48
|
+
> enforcement and usage metering happen. Do **not** point `BEE_API_URL`
|
|
49
|
+
> at the raw Modal app URL: that bypasses billing and a `bee_sk_` key
|
|
50
|
+
> is rejected there (the backend only trusts Supabase JWTs / the
|
|
51
|
+
> static `BEE_API_KEYS` env, not customer-issued keys). Override
|
|
52
|
+
> `BEE_API_URL` only for a self-hosted Bee Enclave or staging.
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
The PyPI `cuilabs` organisation is currently pending approval; until it
|
|
57
|
+
lands, install directly from GitHub:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# From GitHub (recommended while PyPI approval is pending)
|
|
61
|
+
pip install "git+https://github.com/cuilabs/bee.git#subdirectory=sdks/python"
|
|
62
|
+
|
|
63
|
+
# With the optional async client (adds httpx)
|
|
64
|
+
pip install "git+https://github.com/cuilabs/bee.git#subdirectory=sdks/python" httpx
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Once PyPI approval lands, the canonical install is:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
pip install bee-sdk # sync client (stdlib only — zero deps)
|
|
71
|
+
pip install bee-sdk[async] # async client (adds httpx)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Install + quickstart on the marketing site:
|
|
75
|
+
[bee.cuilabs.io/docs/sdks](https://bee.cuilabs.io/docs/sdks).
|
|
76
|
+
|
|
77
|
+
## Quick start
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from bee_sdk import Bee
|
|
81
|
+
|
|
82
|
+
bee = Bee() # reads BEE_API_URL + BEE_API_KEY from env
|
|
83
|
+
print(bee.chat("Explain Shor's algorithm at NISQ depth", domain="quantum"))
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Streaming
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
for chunk in bee.chat_stream("Write a Rust fibonacci function", domain="programming"):
|
|
90
|
+
print(chunk, end="", flush=True)
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Async
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import asyncio
|
|
97
|
+
from bee_sdk import AsyncBee
|
|
98
|
+
|
|
99
|
+
async def main():
|
|
100
|
+
client = AsyncBee()
|
|
101
|
+
text = await client.chat("Audit this contract for re-entrancy", domain="blockchain")
|
|
102
|
+
print(text)
|
|
103
|
+
|
|
104
|
+
asyncio.run(main())
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Multi-turn
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from bee_sdk import Bee, ChatMessage
|
|
111
|
+
|
|
112
|
+
bee = Bee()
|
|
113
|
+
resp = bee.chat_messages(
|
|
114
|
+
[
|
|
115
|
+
ChatMessage(role="system", content="You are a senior security auditor."),
|
|
116
|
+
ChatMessage(role="user", content="Review this nginx config for hardening gaps:\n\n..."),
|
|
117
|
+
],
|
|
118
|
+
domain="cybersecurity",
|
|
119
|
+
max_tokens=1024,
|
|
120
|
+
)
|
|
121
|
+
print(resp.content)
|
|
122
|
+
print(resp.usage, resp.interaction_id)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Feedback loop
|
|
126
|
+
|
|
127
|
+
```python
|
|
128
|
+
resp = bee.chat_messages([...], domain="ai")
|
|
129
|
+
if user_likes_answer:
|
|
130
|
+
bee.feedback(resp.interaction_id, rating="up")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Domains
|
|
134
|
+
|
|
135
|
+
The `domain=` parameter selects which LoRA adapter Bee routes through. Tier-1 domains:
|
|
136
|
+
|
|
137
|
+
| domain | what it's tuned for |
|
|
138
|
+
|---|---|
|
|
139
|
+
| `general` | balanced, no specialization |
|
|
140
|
+
| `programming` | code generation, refactoring, debugging |
|
|
141
|
+
| `ai` | ML/AI papers, training, evaluation |
|
|
142
|
+
| `cybersecurity` | threat modelling, audits, defensive analysis |
|
|
143
|
+
| `quantum` | NISQ-aware quantum computing, Qiskit |
|
|
144
|
+
| `fintech` | payments, risk, compliance |
|
|
145
|
+
| `blockchain` | smart contract audits, protocol design |
|
|
146
|
+
| `infrastructure` | systems, networking, devops |
|
|
147
|
+
| `research` | literature review, paper critique |
|
|
148
|
+
| `business` | strategy, GTM, ops |
|
|
149
|
+
|
|
150
|
+
Adapters live at [`cuilabs/bee-cell`](https://huggingface.co/cuilabs/bee-cell) on branches `<domain>-<UTC-timestamp>`.
|
|
151
|
+
|
|
152
|
+
## Environment variables
|
|
153
|
+
|
|
154
|
+
| var | purpose |
|
|
155
|
+
|---|---|
|
|
156
|
+
| `BEE_API_URL` | Endpoint override. Defaults to the public gateway `https://bee.cuilabs.io/bee` (where auth + billing + metering run). Set this **only** for a self-hosted Bee Enclave or a staging environment — never the raw Modal app URL (that bypasses billing and rejects `bee_sk_` keys). |
|
|
157
|
+
| `BEE_API_KEY` | Bearer token (also accepts `BEE_PORTAL_API_KEY`) |
|
|
158
|
+
|
|
159
|
+
## Errors
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from bee_sdk import BeeAPIError, RateLimitError, BeeError
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
bee.chat("...", domain="quantum")
|
|
166
|
+
except RateLimitError as e: # 429 after retries
|
|
167
|
+
...
|
|
168
|
+
except BeeAPIError as e: # other HTTP errors
|
|
169
|
+
print(e.status, e.body)
|
|
170
|
+
except BeeError: # network / timeout
|
|
171
|
+
...
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
The sync client retries 429/5xx with exponential backoff (max 4 attempts).
|
|
175
|
+
|
|
176
|
+
## Versioning
|
|
177
|
+
|
|
178
|
+
`bee-sdk` follows the Bee API surface in `bee/server.py`. Breaking API changes bump the **minor** version pre-1.0; the SDK is currently **0.1.1** and the API is `v1`.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
Apache-2.0 © 2026 CUI Labs (Pte.) Ltd.
|
bee_sdk-0.1.1/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# bee-sdk
|
|
2
|
+
|
|
3
|
+
Official Python client for the **Bee Intelligence Engine** — a domain-specialized LLM by [CUI Labs](https://bee.cuilabs.io) routing per-domain LoRA adapters over a verified open-weight base.
|
|
4
|
+
|
|
5
|
+
SDK version: `0.1.1` (pre-release).
|
|
6
|
+
|
|
7
|
+
> **Status:** functional sync + async client (stdlib + optional `httpx`).
|
|
8
|
+
> The SDK targets the Bee `/chat/completions` API contract on
|
|
9
|
+
> production via the public gateway `https://bee.cuilabs.io/bee` — this
|
|
10
|
+
> is the default and it is where API-key auth, plan / per-tier allowance
|
|
11
|
+
> enforcement and usage metering happen. Do **not** point `BEE_API_URL`
|
|
12
|
+
> at the raw Modal app URL: that bypasses billing and a `bee_sk_` key
|
|
13
|
+
> is rejected there (the backend only trusts Supabase JWTs / the
|
|
14
|
+
> static `BEE_API_KEYS` env, not customer-issued keys). Override
|
|
15
|
+
> `BEE_API_URL` only for a self-hosted Bee Enclave or staging.
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
The PyPI `cuilabs` organisation is currently pending approval; until it
|
|
20
|
+
lands, install directly from GitHub:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# From GitHub (recommended while PyPI approval is pending)
|
|
24
|
+
pip install "git+https://github.com/cuilabs/bee.git#subdirectory=sdks/python"
|
|
25
|
+
|
|
26
|
+
# With the optional async client (adds httpx)
|
|
27
|
+
pip install "git+https://github.com/cuilabs/bee.git#subdirectory=sdks/python" httpx
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Once PyPI approval lands, the canonical install is:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pip install bee-sdk # sync client (stdlib only — zero deps)
|
|
34
|
+
pip install bee-sdk[async] # async client (adds httpx)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Install + quickstart on the marketing site:
|
|
38
|
+
[bee.cuilabs.io/docs/sdks](https://bee.cuilabs.io/docs/sdks).
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
from bee_sdk import Bee
|
|
44
|
+
|
|
45
|
+
bee = Bee() # reads BEE_API_URL + BEE_API_KEY from env
|
|
46
|
+
print(bee.chat("Explain Shor's algorithm at NISQ depth", domain="quantum"))
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Streaming
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
for chunk in bee.chat_stream("Write a Rust fibonacci function", domain="programming"):
|
|
53
|
+
print(chunk, end="", flush=True)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Async
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import asyncio
|
|
60
|
+
from bee_sdk import AsyncBee
|
|
61
|
+
|
|
62
|
+
async def main():
|
|
63
|
+
client = AsyncBee()
|
|
64
|
+
text = await client.chat("Audit this contract for re-entrancy", domain="blockchain")
|
|
65
|
+
print(text)
|
|
66
|
+
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Multi-turn
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from bee_sdk import Bee, ChatMessage
|
|
74
|
+
|
|
75
|
+
bee = Bee()
|
|
76
|
+
resp = bee.chat_messages(
|
|
77
|
+
[
|
|
78
|
+
ChatMessage(role="system", content="You are a senior security auditor."),
|
|
79
|
+
ChatMessage(role="user", content="Review this nginx config for hardening gaps:\n\n..."),
|
|
80
|
+
],
|
|
81
|
+
domain="cybersecurity",
|
|
82
|
+
max_tokens=1024,
|
|
83
|
+
)
|
|
84
|
+
print(resp.content)
|
|
85
|
+
print(resp.usage, resp.interaction_id)
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Feedback loop
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
resp = bee.chat_messages([...], domain="ai")
|
|
92
|
+
if user_likes_answer:
|
|
93
|
+
bee.feedback(resp.interaction_id, rating="up")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Domains
|
|
97
|
+
|
|
98
|
+
The `domain=` parameter selects which LoRA adapter Bee routes through. Tier-1 domains:
|
|
99
|
+
|
|
100
|
+
| domain | what it's tuned for |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `general` | balanced, no specialization |
|
|
103
|
+
| `programming` | code generation, refactoring, debugging |
|
|
104
|
+
| `ai` | ML/AI papers, training, evaluation |
|
|
105
|
+
| `cybersecurity` | threat modelling, audits, defensive analysis |
|
|
106
|
+
| `quantum` | NISQ-aware quantum computing, Qiskit |
|
|
107
|
+
| `fintech` | payments, risk, compliance |
|
|
108
|
+
| `blockchain` | smart contract audits, protocol design |
|
|
109
|
+
| `infrastructure` | systems, networking, devops |
|
|
110
|
+
| `research` | literature review, paper critique |
|
|
111
|
+
| `business` | strategy, GTM, ops |
|
|
112
|
+
|
|
113
|
+
Adapters live at [`cuilabs/bee-cell`](https://huggingface.co/cuilabs/bee-cell) on branches `<domain>-<UTC-timestamp>`.
|
|
114
|
+
|
|
115
|
+
## Environment variables
|
|
116
|
+
|
|
117
|
+
| var | purpose |
|
|
118
|
+
|---|---|
|
|
119
|
+
| `BEE_API_URL` | Endpoint override. Defaults to the public gateway `https://bee.cuilabs.io/bee` (where auth + billing + metering run). Set this **only** for a self-hosted Bee Enclave or a staging environment — never the raw Modal app URL (that bypasses billing and rejects `bee_sk_` keys). |
|
|
120
|
+
| `BEE_API_KEY` | Bearer token (also accepts `BEE_PORTAL_API_KEY`) |
|
|
121
|
+
|
|
122
|
+
## Errors
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
from bee_sdk import BeeAPIError, RateLimitError, BeeError
|
|
126
|
+
|
|
127
|
+
try:
|
|
128
|
+
bee.chat("...", domain="quantum")
|
|
129
|
+
except RateLimitError as e: # 429 after retries
|
|
130
|
+
...
|
|
131
|
+
except BeeAPIError as e: # other HTTP errors
|
|
132
|
+
print(e.status, e.body)
|
|
133
|
+
except BeeError: # network / timeout
|
|
134
|
+
...
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The sync client retries 429/5xx with exponential backoff (max 4 attempts).
|
|
138
|
+
|
|
139
|
+
## Versioning
|
|
140
|
+
|
|
141
|
+
`bee-sdk` follows the Bee API surface in `bee/server.py`. Breaking API changes bump the **minor** version pre-1.0; the SDK is currently **0.1.1** and the API is `v1`.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
Apache-2.0 © 2026 CUI Labs (Pte.) Ltd.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Bee Python SDK — official client for the Bee Intelligence Engine API.
|
|
2
|
+
|
|
3
|
+
pip install bee-sdk
|
|
4
|
+
|
|
5
|
+
from bee_sdk import Bee
|
|
6
|
+
|
|
7
|
+
bee = Bee() # reads BEE_API_URL + BEE_API_KEY from env
|
|
8
|
+
print(bee.chat("Explain quantum error correction", domain="quantum"))
|
|
9
|
+
|
|
10
|
+
# Streaming
|
|
11
|
+
for chunk in bee.chat_stream("Write a fibonacci function", domain="programming"):
|
|
12
|
+
print(chunk, end="", flush=True)
|
|
13
|
+
|
|
14
|
+
# Async
|
|
15
|
+
import asyncio
|
|
16
|
+
async def main():
|
|
17
|
+
client = Bee.async_client()
|
|
18
|
+
result = await client.chat("Audit this code for SQL injection", domain="cybersecurity")
|
|
19
|
+
print(result)
|
|
20
|
+
asyncio.run(main())
|
|
21
|
+
|
|
22
|
+
The SDK calls the Bee FastAPI surface (see bee/server.py for the full
|
|
23
|
+
endpoint catalogue). For MCP-tool integration (Claude Desktop, Cursor,
|
|
24
|
+
etc.) see bee/mcp_server.py — the SDK and the MCP server are independent
|
|
25
|
+
surfaces over the same model.
|
|
26
|
+
"""
|
|
27
|
+
from .client import Bee, AsyncBee, BeeError, RateLimitError, BeeAPIError
|
|
28
|
+
from .types import ChatMessage, ChatResponse, Domain, ModelTier
|
|
29
|
+
|
|
30
|
+
__version__ = "0.1.1"
|
|
31
|
+
__all__ = [
|
|
32
|
+
"Bee",
|
|
33
|
+
"AsyncBee",
|
|
34
|
+
"BeeError",
|
|
35
|
+
"RateLimitError",
|
|
36
|
+
"BeeAPIError",
|
|
37
|
+
"ChatMessage",
|
|
38
|
+
"ChatResponse",
|
|
39
|
+
"Domain",
|
|
40
|
+
"ModelTier",
|
|
41
|
+
"__version__",
|
|
42
|
+
]
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
"""Bee SDK client.
|
|
2
|
+
|
|
3
|
+
Two flavours:
|
|
4
|
+
- `Bee` synchronous, uses urllib stdlib only (zero deps)
|
|
5
|
+
- `AsyncBee` async, uses httpx (optional dep — `pip install bee-sdk[async]`)
|
|
6
|
+
|
|
7
|
+
Both expose the same surface:
|
|
8
|
+
|
|
9
|
+
.chat(message, domain=..., max_tokens=...) → str
|
|
10
|
+
.chat_messages(messages, ...) → ChatResponse
|
|
11
|
+
.chat_stream(message, ...) → Iterator[str]
|
|
12
|
+
.feedback(interaction_id, rating) → None
|
|
13
|
+
.domains() → list[str]
|
|
14
|
+
.next_domain() → dict (admin)
|
|
15
|
+
.training_runs(limit=20) → list[dict]
|
|
16
|
+
"""
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
import json
|
|
20
|
+
import os
|
|
21
|
+
import time
|
|
22
|
+
import urllib.error
|
|
23
|
+
import urllib.request
|
|
24
|
+
from typing import Any, AsyncIterator, Iterator, Optional
|
|
25
|
+
|
|
26
|
+
from .types import ChatMessage, ChatResponse, Domain
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# Public OpenAI-compatible proxy. MUST be the bee.cuilabs.io/bee gateway,
|
|
30
|
+
# never the raw Modal app URL — the gateway is where API-key auth, plan /
|
|
31
|
+
# per-tier allowance enforcement and usage metering happen. Pointing at
|
|
32
|
+
# Modal directly bypasses billing entirely and a bee_sk_ key 401s there
|
|
33
|
+
# (the backend only knows Supabase JWTs / the static BEE_API_KEYS env).
|
|
34
|
+
DEFAULT_BASE_URL = "https://bee.cuilabs.io/bee"
|
|
35
|
+
DEFAULT_TIMEOUT = 60
|
|
36
|
+
DEFAULT_RETRIES = 3
|
|
37
|
+
RETRYABLE_STATUS = {429, 500, 502, 503, 504}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class BeeError(Exception):
|
|
41
|
+
"""Base error class for the SDK."""
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class BeeAPIError(BeeError):
|
|
45
|
+
def __init__(self, status: int, body: str) -> None:
|
|
46
|
+
super().__init__(f"HTTP {status}: {body[:300]}")
|
|
47
|
+
self.status = status
|
|
48
|
+
self.body = body
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class RateLimitError(BeeAPIError):
|
|
52
|
+
"""Raised on persistent 429 after retries exhausted."""
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class _BaseClient:
|
|
56
|
+
def __init__(
|
|
57
|
+
self,
|
|
58
|
+
base_url: Optional[str] = None,
|
|
59
|
+
api_key: Optional[str] = None,
|
|
60
|
+
timeout: int = DEFAULT_TIMEOUT,
|
|
61
|
+
retries: int = DEFAULT_RETRIES,
|
|
62
|
+
) -> None:
|
|
63
|
+
self.base_url = (base_url or os.environ.get("BEE_API_URL") or DEFAULT_BASE_URL).rstrip("/")
|
|
64
|
+
self.api_key = api_key or os.environ.get("BEE_API_KEY") or os.environ.get("BEE_PORTAL_API_KEY")
|
|
65
|
+
self.timeout = timeout
|
|
66
|
+
self.retries = retries
|
|
67
|
+
|
|
68
|
+
def _headers(self, extra: Optional[dict] = None) -> dict:
|
|
69
|
+
h = {
|
|
70
|
+
"Content-Type": "application/json",
|
|
71
|
+
"Accept": "application/json",
|
|
72
|
+
"User-Agent": "bee-sdk/0.1.1",
|
|
73
|
+
}
|
|
74
|
+
if self.api_key:
|
|
75
|
+
h["Authorization"] = f"Bearer {self.api_key}"
|
|
76
|
+
if extra:
|
|
77
|
+
h.update(extra)
|
|
78
|
+
return h
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Bee(_BaseClient):
|
|
82
|
+
"""Synchronous client. Zero non-stdlib dependencies."""
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def async_client(cls, **kwargs) -> "AsyncBee":
|
|
86
|
+
"""Convenience factory for the async client."""
|
|
87
|
+
return AsyncBee(**kwargs)
|
|
88
|
+
|
|
89
|
+
# ── HTTP plumbing ───────────────────────────────────────────────────
|
|
90
|
+
def _request(self, method: str, path: str, body: Optional[dict] = None) -> dict:
|
|
91
|
+
url = f"{self.base_url}{path}"
|
|
92
|
+
data = json.dumps(body).encode() if body is not None else None
|
|
93
|
+
last_err: Optional[Exception] = None
|
|
94
|
+
for attempt in range(self.retries + 1):
|
|
95
|
+
req = urllib.request.Request(url, data=data, headers=self._headers(), method=method)
|
|
96
|
+
try:
|
|
97
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
98
|
+
raw = resp.read().decode("utf-8")
|
|
99
|
+
return json.loads(raw) if raw else {}
|
|
100
|
+
except urllib.error.HTTPError as e:
|
|
101
|
+
body_text = e.read().decode(errors="replace")
|
|
102
|
+
if e.code in RETRYABLE_STATUS and attempt < self.retries:
|
|
103
|
+
last_err = e
|
|
104
|
+
time.sleep(min(2 ** attempt, 8))
|
|
105
|
+
continue
|
|
106
|
+
if e.code == 429:
|
|
107
|
+
raise RateLimitError(e.code, body_text)
|
|
108
|
+
raise BeeAPIError(e.code, body_text)
|
|
109
|
+
except (urllib.error.URLError, TimeoutError, ConnectionError) as e:
|
|
110
|
+
last_err = e
|
|
111
|
+
if attempt < self.retries:
|
|
112
|
+
time.sleep(min(2 ** attempt, 8))
|
|
113
|
+
continue
|
|
114
|
+
raise BeeError(f"network error after {self.retries + 1} attempts: {e}") from e
|
|
115
|
+
raise BeeError(f"unreachable: {last_err}")
|
|
116
|
+
|
|
117
|
+
# ── Public API ──────────────────────────────────────────────────────
|
|
118
|
+
def chat(
|
|
119
|
+
self,
|
|
120
|
+
message: str,
|
|
121
|
+
domain: Domain = "general",
|
|
122
|
+
max_tokens: int = 512,
|
|
123
|
+
temperature: float = 0.3,
|
|
124
|
+
system: Optional[str] = None,
|
|
125
|
+
) -> str:
|
|
126
|
+
"""Single-turn chat. Returns the assistant text only.
|
|
127
|
+
|
|
128
|
+
For multi-turn or detailed response metadata, use `chat_messages`.
|
|
129
|
+
"""
|
|
130
|
+
msgs: list[ChatMessage] = []
|
|
131
|
+
if system:
|
|
132
|
+
msgs.append(ChatMessage(role="system", content=system))
|
|
133
|
+
msgs.append(ChatMessage(role="user", content=message))
|
|
134
|
+
return self.chat_messages(msgs, domain=domain, max_tokens=max_tokens, temperature=temperature).content
|
|
135
|
+
|
|
136
|
+
def chat_messages(
|
|
137
|
+
self,
|
|
138
|
+
messages: list[ChatMessage],
|
|
139
|
+
domain: Domain = "general",
|
|
140
|
+
max_tokens: int = 512,
|
|
141
|
+
temperature: float = 0.3,
|
|
142
|
+
) -> ChatResponse:
|
|
143
|
+
# Switch domain first (the Bee API has explicit /domain/switch).
|
|
144
|
+
try:
|
|
145
|
+
self._request("POST", "/domain/switch", {"domain": domain})
|
|
146
|
+
except BeeAPIError:
|
|
147
|
+
# Domain switch is best-effort — if the server hasn't loaded
|
|
148
|
+
# that domain's adapter, fall through and serve from current.
|
|
149
|
+
pass
|
|
150
|
+
body = {
|
|
151
|
+
"model": "bee-cell",
|
|
152
|
+
"messages": [{"role": m.role, "content": m.content} for m in messages],
|
|
153
|
+
"max_tokens": max_tokens,
|
|
154
|
+
"temperature": temperature,
|
|
155
|
+
}
|
|
156
|
+
out = self._request("POST", "/chat/completions", body)
|
|
157
|
+
choice = (out.get("choices") or [{}])[0]
|
|
158
|
+
msg = choice.get("message", {})
|
|
159
|
+
return ChatResponse(
|
|
160
|
+
id=out.get("id", ""),
|
|
161
|
+
model=out.get("model", ""),
|
|
162
|
+
content=msg.get("content", ""),
|
|
163
|
+
role=msg.get("role", "assistant"),
|
|
164
|
+
finish_reason=choice.get("finish_reason"),
|
|
165
|
+
usage=out.get("usage", {}),
|
|
166
|
+
interaction_id=out.get("interaction_id"),
|
|
167
|
+
raw=out,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
def chat_stream(
|
|
171
|
+
self,
|
|
172
|
+
message: str,
|
|
173
|
+
domain: Domain = "general",
|
|
174
|
+
max_tokens: int = 512,
|
|
175
|
+
temperature: float = 0.3,
|
|
176
|
+
system: Optional[str] = None,
|
|
177
|
+
) -> Iterator[str]:
|
|
178
|
+
"""Stream tokens as they're generated.
|
|
179
|
+
|
|
180
|
+
Falls back to a single-shot response if the server doesn't yet
|
|
181
|
+
emit SSE chunks (current /chat/completions doesn't stream;
|
|
182
|
+
this method preserves the API for when streaming lands).
|
|
183
|
+
"""
|
|
184
|
+
try:
|
|
185
|
+
self._request("POST", "/domain/switch", {"domain": domain})
|
|
186
|
+
except BeeAPIError:
|
|
187
|
+
pass
|
|
188
|
+
msgs = []
|
|
189
|
+
if system:
|
|
190
|
+
msgs.append({"role": "system", "content": system})
|
|
191
|
+
msgs.append({"role": "user", "content": message})
|
|
192
|
+
body = {
|
|
193
|
+
"model": "bee-cell",
|
|
194
|
+
"messages": msgs,
|
|
195
|
+
"max_tokens": max_tokens,
|
|
196
|
+
"temperature": temperature,
|
|
197
|
+
"stream": True,
|
|
198
|
+
}
|
|
199
|
+
# Try SSE first; if the server returns plain JSON, yield it whole.
|
|
200
|
+
url = f"{self.base_url}/chat/completions"
|
|
201
|
+
req = urllib.request.Request(
|
|
202
|
+
url, data=json.dumps(body).encode(), headers=self._headers(), method="POST",
|
|
203
|
+
)
|
|
204
|
+
try:
|
|
205
|
+
with urllib.request.urlopen(req, timeout=self.timeout) as resp:
|
|
206
|
+
ct = resp.headers.get("content-type", "")
|
|
207
|
+
if "text/event-stream" in ct:
|
|
208
|
+
for line in resp:
|
|
209
|
+
s = line.decode().strip()
|
|
210
|
+
if not s or not s.startswith("data:"):
|
|
211
|
+
continue
|
|
212
|
+
payload = s[5:].strip()
|
|
213
|
+
if payload == "[DONE]":
|
|
214
|
+
return
|
|
215
|
+
try:
|
|
216
|
+
d = json.loads(payload)
|
|
217
|
+
delta = (d.get("choices") or [{}])[0].get("delta", {})
|
|
218
|
+
if "content" in delta:
|
|
219
|
+
yield delta["content"]
|
|
220
|
+
except json.JSONDecodeError:
|
|
221
|
+
continue
|
|
222
|
+
else:
|
|
223
|
+
# Server didn't honour stream=True — yield full response.
|
|
224
|
+
out = json.loads(resp.read().decode())
|
|
225
|
+
content = (out.get("choices") or [{}])[0].get("message", {}).get("content", "")
|
|
226
|
+
if content:
|
|
227
|
+
yield content
|
|
228
|
+
except urllib.error.HTTPError as e:
|
|
229
|
+
raise BeeAPIError(e.code, e.read().decode(errors="replace"))
|
|
230
|
+
|
|
231
|
+
def feedback(self, interaction_id: str, rating: str, comment: Optional[str] = None) -> None:
|
|
232
|
+
"""Submit thumbs-up/down feedback. rating: 'up' | 'down'."""
|
|
233
|
+
self._request("POST", "/feedback", {
|
|
234
|
+
"interaction_id": interaction_id,
|
|
235
|
+
"rating": rating,
|
|
236
|
+
"comment": comment,
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
def domains(self) -> list[str]:
|
|
240
|
+
"""List the domains Bee knows about."""
|
|
241
|
+
return self._request("GET", "/models").get("domains", [])
|
|
242
|
+
|
|
243
|
+
def health(self) -> dict:
|
|
244
|
+
return self._request("GET", "/health")
|
|
245
|
+
|
|
246
|
+
def adapters(self) -> dict:
|
|
247
|
+
"""Currently-loaded LoRA adapters per domain (added 2026-04-28)."""
|
|
248
|
+
return self._request("GET", "/adapters")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class AsyncBee(_BaseClient):
|
|
252
|
+
"""Async client — requires httpx. `pip install bee-sdk[async]`."""
|
|
253
|
+
|
|
254
|
+
def __init__(self, *args, **kwargs):
|
|
255
|
+
super().__init__(*args, **kwargs)
|
|
256
|
+
try:
|
|
257
|
+
import httpx # noqa: F401
|
|
258
|
+
except ImportError as e:
|
|
259
|
+
raise ImportError(
|
|
260
|
+
"AsyncBee requires httpx. Install with `pip install bee-sdk[async]`."
|
|
261
|
+
) from e
|
|
262
|
+
|
|
263
|
+
async def chat(
|
|
264
|
+
self,
|
|
265
|
+
message: str,
|
|
266
|
+
domain: Domain = "general",
|
|
267
|
+
max_tokens: int = 512,
|
|
268
|
+
temperature: float = 0.3,
|
|
269
|
+
system: Optional[str] = None,
|
|
270
|
+
) -> str:
|
|
271
|
+
import httpx
|
|
272
|
+
msgs: list[dict] = []
|
|
273
|
+
if system:
|
|
274
|
+
msgs.append({"role": "system", "content": system})
|
|
275
|
+
msgs.append({"role": "user", "content": message})
|
|
276
|
+
async with httpx.AsyncClient(timeout=self.timeout, headers=self._headers()) as cl:
|
|
277
|
+
try:
|
|
278
|
+
await cl.post(f"{self.base_url}/domain/switch", json={"domain": domain})
|
|
279
|
+
except httpx.HTTPError:
|
|
280
|
+
pass
|
|
281
|
+
r = await cl.post(f"{self.base_url}/chat/completions", json={
|
|
282
|
+
"model": "bee-cell",
|
|
283
|
+
"messages": msgs,
|
|
284
|
+
"max_tokens": max_tokens,
|
|
285
|
+
"temperature": temperature,
|
|
286
|
+
})
|
|
287
|
+
r.raise_for_status()
|
|
288
|
+
out = r.json()
|
|
289
|
+
return (out.get("choices") or [{}])[0].get("message", {}).get("content", "")
|
|
290
|
+
|
|
291
|
+
async def chat_stream(
|
|
292
|
+
self,
|
|
293
|
+
message: str,
|
|
294
|
+
domain: Domain = "general",
|
|
295
|
+
max_tokens: int = 512,
|
|
296
|
+
temperature: float = 0.3,
|
|
297
|
+
system: Optional[str] = None,
|
|
298
|
+
) -> AsyncIterator[str]:
|
|
299
|
+
import httpx
|
|
300
|
+
msgs: list[dict] = []
|
|
301
|
+
if system:
|
|
302
|
+
msgs.append({"role": "system", "content": system})
|
|
303
|
+
msgs.append({"role": "user", "content": message})
|
|
304
|
+
async with httpx.AsyncClient(timeout=self.timeout, headers=self._headers()) as cl:
|
|
305
|
+
try:
|
|
306
|
+
await cl.post(f"{self.base_url}/domain/switch", json={"domain": domain})
|
|
307
|
+
except httpx.HTTPError:
|
|
308
|
+
pass
|
|
309
|
+
async with cl.stream("POST", f"{self.base_url}/chat/completions", json={
|
|
310
|
+
"model": "bee-cell",
|
|
311
|
+
"messages": msgs,
|
|
312
|
+
"max_tokens": max_tokens,
|
|
313
|
+
"temperature": temperature,
|
|
314
|
+
"stream": True,
|
|
315
|
+
}) as resp:
|
|
316
|
+
async for line in resp.aiter_lines():
|
|
317
|
+
s = line.strip()
|
|
318
|
+
if not s or not s.startswith("data:"):
|
|
319
|
+
continue
|
|
320
|
+
payload = s[5:].strip()
|
|
321
|
+
if payload == "[DONE]":
|
|
322
|
+
return
|
|
323
|
+
try:
|
|
324
|
+
d = json.loads(payload)
|
|
325
|
+
delta = (d.get("choices") or [{}])[0].get("delta", {})
|
|
326
|
+
if "content" in delta:
|
|
327
|
+
yield delta["content"]
|
|
328
|
+
except json.JSONDecodeError:
|
|
329
|
+
continue
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Public types for the Bee SDK.
|
|
2
|
+
|
|
3
|
+
Mirrors the JSON shape of the Bee FastAPI surface (`bee/server.py`).
|
|
4
|
+
Kept as plain dataclasses — no pydantic dependency on the SDK so it's
|
|
5
|
+
lightweight to install.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Literal, Optional
|
|
11
|
+
|
|
12
|
+
# Tier-1 domains, mirror of bee/domains.py.
|
|
13
|
+
Domain = Literal[
|
|
14
|
+
"general",
|
|
15
|
+
"programming",
|
|
16
|
+
"ai",
|
|
17
|
+
"cybersecurity",
|
|
18
|
+
"quantum",
|
|
19
|
+
"fintech",
|
|
20
|
+
"blockchain",
|
|
21
|
+
"infrastructure",
|
|
22
|
+
"research",
|
|
23
|
+
"business",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
# Trainable tier names, mirror of bee/tiers.py.
|
|
27
|
+
ModelTier = Literal["cell", "brood", "comb", "buzz", "hive", "swarm", "enclave", "ignite"]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class ChatMessage:
|
|
32
|
+
role: Literal["system", "user", "assistant"]
|
|
33
|
+
content: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class ChatResponse:
|
|
38
|
+
"""Response shape from POST /chat/completions.
|
|
39
|
+
|
|
40
|
+
Mirrors the OpenAI ChatCompletion shape because that's what the
|
|
41
|
+
Bee API emits — preserves drop-in compatibility for callers that
|
|
42
|
+
already know the OpenAI SDK.
|
|
43
|
+
"""
|
|
44
|
+
id: str
|
|
45
|
+
model: str
|
|
46
|
+
content: str
|
|
47
|
+
role: str = "assistant"
|
|
48
|
+
finish_reason: Optional[str] = None
|
|
49
|
+
usage: dict = field(default_factory=dict)
|
|
50
|
+
interaction_id: Optional[str] = None
|
|
51
|
+
raw: dict = field(default_factory=dict)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling>=1.21"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "bee-sdk"
|
|
7
|
+
version = "0.1.1"
|
|
8
|
+
description = "Official Python client for the Bee Intelligence Engine — domain-specialized LoRA-routed LLM by CUI Labs."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "Apache-2.0" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "CUI Labs (Pte.) Ltd.", email = "engineering@cuilabs.io" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"bee",
|
|
17
|
+
"llm",
|
|
18
|
+
"ai",
|
|
19
|
+
"lora",
|
|
20
|
+
"domain-experts",
|
|
21
|
+
"quantum",
|
|
22
|
+
"cybersecurity",
|
|
23
|
+
"blockchain",
|
|
24
|
+
"mcp",
|
|
25
|
+
"huggingface",
|
|
26
|
+
]
|
|
27
|
+
classifiers = [
|
|
28
|
+
"Development Status :: 4 - Beta",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: Apache Software License",
|
|
31
|
+
"Operating System :: OS Independent",
|
|
32
|
+
"Programming Language :: Python :: 3",
|
|
33
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
34
|
+
"Programming Language :: Python :: 3.10",
|
|
35
|
+
"Programming Language :: Python :: 3.11",
|
|
36
|
+
"Programming Language :: Python :: 3.12",
|
|
37
|
+
"Programming Language :: Python :: 3.13",
|
|
38
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
39
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
40
|
+
"Typing :: Typed",
|
|
41
|
+
]
|
|
42
|
+
dependencies = []
|
|
43
|
+
|
|
44
|
+
[project.optional-dependencies]
|
|
45
|
+
async = ["httpx>=0.27"]
|
|
46
|
+
dev = [
|
|
47
|
+
"pytest>=8",
|
|
48
|
+
"pytest-asyncio>=0.23",
|
|
49
|
+
"ruff>=0.5",
|
|
50
|
+
"mypy>=1.10",
|
|
51
|
+
"httpx>=0.27",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
[project.urls]
|
|
55
|
+
Homepage = "https://bee.cuilabs.io"
|
|
56
|
+
Documentation = "https://bee.cuilabs.io/docs/sdks"
|
|
57
|
+
Repository = "https://github.com/cuilabs/bee"
|
|
58
|
+
Issues = "https://github.com/cuilabs/bee-community/issues"
|
|
59
|
+
Changelog = "https://bee.cuilabs.io/changelog"
|
|
60
|
+
"Hugging Face" = "https://huggingface.co/cuilabs"
|
|
61
|
+
|
|
62
|
+
[tool.hatch.build.targets.wheel]
|
|
63
|
+
packages = ["bee_sdk"]
|
|
64
|
+
|
|
65
|
+
[tool.hatch.build.targets.sdist]
|
|
66
|
+
include = [
|
|
67
|
+
"bee_sdk/",
|
|
68
|
+
"README.md",
|
|
69
|
+
"pyproject.toml",
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
[tool.ruff]
|
|
73
|
+
line-length = 100
|
|
74
|
+
target-version = "py39"
|
|
75
|
+
|
|
76
|
+
[tool.ruff.lint]
|
|
77
|
+
select = ["E", "F", "I", "UP", "B", "SIM"]
|
|
78
|
+
ignore = ["E501"]
|
|
79
|
+
|
|
80
|
+
[tool.mypy]
|
|
81
|
+
python_version = "3.10"
|
|
82
|
+
strict = true
|
|
83
|
+
warn_unused_ignores = true
|