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.
@@ -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.
@@ -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