laya-system-one 1.0.0 → 1.1.0-alpha.1

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.
package/README.md CHANGED
@@ -1,322 +1,299 @@
1
- # Laya System-One ⚡
2
-
3
- [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
4
- [![Runtime](https://img.shields.io/badge/Runtime-Node.js%20%7C%20Bun%20%7C%20Browser-green.svg)]()
5
- [![TypeSafe Jev](https://img.shields.io/badge/Wire%20Protocol-TypeSafe%20Jev%20Compatible-orange.svg)]()
6
-
7
- > **Self-contained, ultra-fast System 1 decision engine with WebGPU and WASM SIMD acceleration. Drop-in, 100% wire-compatible replacement for the TypeSafe Jev API (`POST /v1/systemone`).**
8
-
9
- Runs entirely on your local machine with **zero Python**, **zero PyTorch**, and **zero external network requests** at runtime. The INT8 multilingual model (~324 MB) is embedded directly within the package, enabling secure, air-gapped corporate deployments with sub-20ms inference latency.
10
-
11
- ---
12
-
13
- ## 🌟 Why Laya System-One?
14
-
15
- - 🔒 **100% Offline & Air-Gapped:** Zero external calls to Hugging Face or cloud APIs at runtime. Ideal for secure corporate intranets, edge servers, and privacy-sensitive workflows.
16
- - ⚡ **Sub-20ms Latency:** Executes non-autoregressive decision classification in ~15ms on modern CPUs via native C++/SIMD and WebGPU.
17
- - 🔄 **TypeSafe Jev Wire-Compatible:** Drop-in emulation of TypeSafe Jev's `POST /v1/systemone` endpoint. Any existing Jev client or SDK can connect immediately simply by changing the base URL.
18
- - 🌍 **True Multilingual Understanding:** Built on multilingual representations supporting over 100 languages (English, Portuguese, Spanish, German, French, Chinese, Japanese, etc.) out-of-the-box.
19
- - 💻 **Universal JavaScript Support:** Works seamlessly across **Node.js** (>=18), **Bun**, **Deno**, and modern web browsers.
20
- - 📦 **Dual Operation Modes:** Run as an independent local HTTP daemon via the CLI (`npx laya-system-one`) or import in-memory into your application process for zero network overhead.
21
-
22
- ---
23
-
24
- ## 📦 Installation
25
-
26
- ```bash
27
- # Using npm
28
- npm install laya-system-one
29
-
30
- # Using bun
31
- bun add laya-system-one
32
-
33
- # Using pnpm
34
- pnpm add laya-system-one
35
- ```
36
-
37
- ---
38
-
39
- ## 🚀 Quick Start
40
-
41
- ### 1. Launch HTTP Microservice via CLI
42
-
43
- To spin up a TypeSafe Jev-compatible server on port `8080`:
44
-
45
- ```bash
46
- npx laya-system-one --port 8080
47
- ```
48
-
49
- #### CLI Options:
50
-
51
- | Flag | Environment Variable | Default | Description |
52
- | :--- | :--- | :--- | :--- |
53
- | `--port <number>` | `PORT` | `8080` | Port to bind the HTTP server |
54
- | `--host <string>` | `HOST` | `0.0.0.0` | Host address to bind |
55
- | `--device <type>` | `DEVICE` | `auto` | Execution backend: `auto`, `webgpu`, `wasm`, `cpu` |
56
- | `--api-key <token>`| `LAYA_API_KEY` | *(none)* | Require Bearer authentication on `/v1/systemone` |
57
-
58
- Example with authentication enabled:
59
-
60
- ```bash
61
- npx laya-system-one --port 8080 --api-key secret-token-xyz
62
- ```
63
-
64
- ---
65
-
66
- ### 2. In-Process Programmatic Usage (Zero Network Latency)
67
-
68
- You can evaluate states directly in memory inside your Node.js or Bun backend:
69
-
70
- ```javascript
71
- import { Laya } from 'laya-system-one';
72
-
73
- // 1. Initialize engine (device: 'auto' | 'webgpu' | 'wasm' | 'cpu')
74
- const laya = await Laya.load({ device: 'auto' });
75
-
76
- // 2. Define state (string, object, or array)
77
- const state = {
78
- customer_id: 'cust_9821',
79
- message: 'We were charged twice on our March invoice. Please refund the duplicate amount or we will cancel our plan.'
80
- };
81
-
82
- // 3. Define typed questions (choice, score, noul)
83
- const questions = {
84
- department: {
85
- type: 'choice',
86
- instructions: 'Which team should resolve this customer inquiry?',
87
- criteria: {
88
- billing: 'Invoices, refunds, and duplicate charges',
89
- tech_support: 'Software bugs, outages, and error messages',
90
- sales: 'Upgrades, plan changes, and enterprise contracts'
91
- }
92
- },
93
- urgency: {
94
- type: 'score',
95
- instructions: 'Assess the urgency level of this inquiry.',
96
- criteria: ['Low / routine', 'Moderate', 'Critical / blocking / angry']
97
- },
98
- churn_risk: {
99
- type: 'noul',
100
- instructions: 'Does this message present an explicit risk of customer churn?',
101
- threshold: 0.5
102
- }
103
- };
104
-
105
- // 4. Evaluate state
106
- const result = await laya.predict(state, questions);
107
-
108
- console.log(result.answers.department.choice); // -> "billing"
109
- console.log(result.answers.department.confidence); // -> 1.0 (100%)
110
- console.log(result.answers.urgency.score); // -> 1.95 (High urgency)
111
- console.log(result.answers.churn_risk.noul); // -> 0.968 (96.8% probability)
112
- console.log(result.answers.churn_risk.decision); // -> true (Passed threshold 0.5)
113
- ```
114
-
115
- ---
116
-
117
- ### 3. Programmatic HTTP Server
118
-
119
- Spin up the HTTP server inside your existing JavaScript application:
120
-
121
- ```javascript
122
- import { serve } from 'laya-system-one';
123
-
124
- const { url, close } = await serve({
125
- host: '127.0.0.1',
126
- port: 8080,
127
- apiKey: 'optional-bearer-key'
128
- });
129
-
130
- console.log(`Laya Jev server running at ${url}/v1/systemone`);
131
-
132
- // To stop gracefully later:
133
- // close();
134
- ```
135
-
136
- ---
137
-
138
- ## 📡 HTTP API Reference (TypeSafe Jev Wire Compatible)
139
-
140
- ### Evaluation Endpoint
141
-
142
- ```http
143
- POST /v1/systemone
144
- Host: localhost:8080
145
- Content-Type: application/json
146
- Authorization: Bearer <API_KEY> [Optional unless configured]
147
- ```
148
-
149
- ### Request Body
150
-
151
- | Parameter | Type | Required | Description |
152
- | :--- | :--- | :--- | :--- |
153
- | `state` | `string` \| `object` \| `array` | **Yes** | The context, text, or structured data being evaluated. |
154
- | `questions` | `Record<string, Question>` | **Yes** | Map of question keys to typed question objects. |
155
- | `model` | `string` | No | Model name (defaults to `laya-multilingual`, echoed back). |
156
-
157
- ---
158
-
159
- ### Question Specifications
160
-
161
- #### 1. `choice` Question
162
- Multi-class classification between distinct options.
163
-
164
- ```json
165
- {
166
- "type": "choice",
167
- "instructions": "Which department should handle this ticket?",
168
- "criteria": {
169
- "billing": "Invoices and credit card transactions",
170
- "technical": "Software bugs and service disruptions"
171
- }
172
- }
173
- ```
174
-
175
- #### 2. `score` Question
176
- Continuous ordinal scoring along an ordered scale of criteria levels.
177
-
178
- ```json
179
- {
180
- "type": "score",
181
- "instructions": "Rate the severity of the issue.",
182
- "criteria": [
183
- "Minor cosmetic issue",
184
- "Degraded functionality",
185
- "Critical full service outage"
186
- ]
187
- }
188
- ```
189
-
190
- #### 3. `noul` Question
191
- Calibrated binary verification (0.0 to 1.0).
192
-
193
- ```json
194
- {
195
- "type": "noul",
196
- "instructions": "Does the user explicitly request a refund?",
197
- "threshold": 0.6
198
- }
199
- ```
200
-
201
- ---
202
-
203
- ### Example cURL Request
204
-
205
- ```bash
206
- curl -X POST http://localhost:8080/v1/systemone \
207
- -H "Content-Type: application/json" \
208
- -d '{
209
- "state": {
210
- "text": "Fui cobrado duas vezes na minha fatura. Reembolsem imediatamente."
211
- },
212
- "questions": {
213
- "dept": {
214
- "type": "choice",
215
- "instructions": "Which department should respond?",
216
- "criteria": {
217
- "billing": "Refunds, invoices, and payments",
218
- "support": "Technical and product questions"
219
- }
220
- },
221
- "urgency": {
222
- "type": "score",
223
- "instructions": "Urgency rating",
224
- "criteria": ["Low", "Medium", "High"]
225
- },
226
- "refund_demanded": {
227
- "type": "noul",
228
- "instructions": "Is the customer requesting a refund?",
229
- "threshold": 0.5
230
- }
231
- }
232
- }'
233
- ```
234
-
235
- ### Example HTTP Response (`200 OK`)
236
-
237
- ```json
238
- {
239
- "model": "laya-multilingual",
240
- "answers": {
241
- "dept": {
242
- "type": "choice",
243
- "choice": "billing",
244
- "probabilities": {
245
- "billing": 1.0,
246
- "support": 0.0
247
- },
248
- "confidence": 1.0
249
- },
250
- "urgency": {
251
- "type": "score",
252
- "score": 1.9482,
253
- "legend": {
254
- "0": "Low",
255
- "1": "Medium",
256
- "2": "High"
257
- },
258
- "probabilities": {
259
- "0": 0.0011,
260
- "1": 0.0496,
261
- "2": 0.9493
262
- },
263
- "confidence": 0.9493
264
- },
265
- "refund_demanded": {
266
- "type": "noul",
267
- "noul": 0.9852,
268
- "confidence": 0.9852,
269
- "threshold": 0.5,
270
- "decision": true
271
- }
272
- },
273
- "usage": {
274
- "input_tokens": 82,
275
- "output_tokens": 12
276
- }
277
- }
278
- ```
279
-
280
- ### Healthcheck Endpoint
281
-
282
- ```http
283
- GET /health
284
- ```
285
-
286
- **Response (`200 OK`):**
287
- ```json
288
- {
289
- "status": "ok",
290
- "model": "laya-multilingual",
291
- "version": "1.0.0",
292
- "protocol": "TypeSafe Jev /v1/systemone compatible"
293
- }
294
- ```
295
-
296
- ### HTTP Error Codes
297
-
298
- - `401 Unauthorized`: Returned when `--api-key` is configured on the server and the `Authorization: Bearer <key>` header is missing or invalid.
299
- - `422 Unprocessable Entity`: Returned when the JSON body is invalid or missing required `state` / `questions` fields.
300
-
301
- ---
302
-
303
- ## ⚡ Hardware Acceleration Architecture
304
-
305
- `laya-system-one` includes a hybrid native runtime that maximizes throughput based on the active runtime:
306
-
307
- | Environment | Primary Provider | Fallback Provider | Typical Inference Latency |
308
- | :--- | :--- | :--- | :--- |
309
- | **Node.js** | Native C++ (`cpu`) | WebGPU (`webgpu`) | ~15 ms / query |
310
- | **Bun** | WebAssembly SIMD (`wasm`) | WebGPU (`webgpu`) | ~25 ms / query |
311
- | **Browser** | WebGPU (`navigator.gpu`) | WebAssembly SIMD (`wasm`) | ~18 ms / query |
312
-
313
- ---
314
-
315
- ## 📄 License & Attribution
316
-
317
- - **License:** [Apache-2.0](LICENSE)
318
- - **Author:** [Italo Almeida](https://github.com/italoalmeida0)
319
- - **GitHub Repository:** [https://github.com/italoalmeida0/laya-system-one](https://github.com/italoalmeida0/laya-system-one)
320
-
321
- ### Upstream Attribution
322
- This project incorporates and builds upon the foundational research and model architecture of **Laya** by [Convai Innovations](https://github.com/NandhaKishorM/laya) (licensed under Apache-2.0). All appropriate copyright notices and license requirements are preserved in compliance with Section 4 of the Apache License 2.0.
1
+ # Laya System-One
2
+
3
+ **System 1 decision engine** — a multilingual INT8 transformer that answers typed questions (choice / score / yes-no) about any text, in milliseconds, offline.
4
+
5
+ Wire-compatible with the **TypeSafe Jev** `/v1/systemone` protocol: send state + typed questions, get structured decisions back.
6
+
7
+ ```bash
8
+ npm install laya-system-one
9
+ ```
10
+
11
+ ```js
12
+ import { Laya } from 'laya-system-one';
13
+
14
+ const laya = await Laya.load(); // model is acquired on first use (once)
15
+ const out = await laya.predict(
16
+ 'We were billed twice on the March invoice and want a refund.',
17
+ {
18
+ department: {
19
+ type: 'choice',
20
+ instructions: 'Which department should handle this?',
21
+ criteria: { billing: 'refunds and invoices', tech: 'bugs', sales: 'upgrades' }
22
+ },
23
+ churn: { type: 'noul', instructions: 'Is the user at churn risk?', threshold: 0.5 },
24
+ severity: { type: 'score', instructions: 'Urgency?', criteria: ['low', 'mid', 'high'] }
25
+ }
26
+ );
27
+
28
+ console.log(out.answers.department.choice); // → "billing"
29
+ ```
30
+
31
+ Or run it as a service:
32
+
33
+ ```bash
34
+ npx laya-system-one --port 8080
35
+ curl -X POST http://localhost:8080/v1/systemone \
36
+ -H "Content-Type: application/json" \
37
+ -d '{"state":"I was charged twice and need a refund.","questions":{"dept":{"type":"choice","instructions":"Which department?","criteria":{"billing":"refunds","tech":"bugs"}}}}'
38
+ ```
39
+
40
+ ---
41
+
42
+ ## What it actually is
43
+
44
+ A single ONNX checkpoint (`model.onnx`, ~324 MB, INT8) trained to score answer options for three question types, plus a small runtime that:
45
+
46
+ 1. renders the state and the question options into a prompt,
47
+ 2. runs one forward pass,
48
+ 3. turns the logits into calibrated probabilities.
49
+
50
+ The runtime is JavaScript (Node/Bun/browser) and executes that forward pass in exactly two ways — pick one with `--backend` or `LAYA_BACKEND`:
51
+
52
+ | backend | what it is | when to use |
53
+ |---|---|---|
54
+ | `native` **(default)** | the `laya-serve` binary bundled in the package (Rust: Axum + tokenizers + ONNX Runtime, statically linked) | fastest path, zero system dependencies, any OS |
55
+ | `wasm` | pure-Rust `tract` compiled to WASM, bundled in the package | browsers and extreme portability |
56
+
57
+ There are **no external runtime dependencies**: no `onnxruntime-*`, no `@huggingface/transformers`, nothing to download besides the model. The tokenizer itself is a pure-JS BPE implementation (`src/bpe-tokenizer.js`) verified token-for-token against the reference `tokenizers` crate that the native binary links — so both backends see exactly the same input ids.
58
+
59
+ > **Note on WebGPU:** earlier versions advertised WebGPU acceleration. Measured reality: ONNX Runtime's Node WebGPU execution provider falls back to CPU per-op with large overhead — it is consistently *slower* than the native backend. WebGPU is not used server-side.
60
+
61
+ ---
62
+
63
+ ## How the heavy pieces arrive
64
+
65
+ The entry package is small on purpose (**~8.5 MB**): source, tokenizer and the
66
+ wasm fallback. The binaries (150+ MB for every platform) and the model
67
+ (324 MB) are published as separate `@sys-one` packages and installed as
68
+ `optionalDependencies`, so npm fetches only what the machine needs:
69
+
70
+ | package | selected by | size |
71
+ | --- | --- | --- |
72
+ | `@sys-one/laya-serve-<os>-<arch>` | npm's `os`/`cpu` fields | 8–26 MB |
73
+ | `@sys-one/laya-serve-universal` | fallback for exotic platforms | ~76 MB |
74
+ | `@sys-one/laya-model-chunk-00` … `-12` | always (all 13) | ~24 MB each |
75
+
76
+ A `linux/x64` machine installs `laya-serve-linux-x64` and nothing else;
77
+ a `linux/ppc64` machine matches none of the specific packages and gets the
78
+ universal one. No package installs all of them.
79
+
80
+ Two things worth knowing about how the selection works, because both were
81
+ verified by experiment rather than assumed:
82
+
83
+ - **`libc` cannot select.** The field exists but is unreliable (undocumented
84
+ shorthand, and real packages have shipped bugs where `--libc=glibc` pulls
85
+ the musl build too). So the glibc and musl builds of a Linux arch travel
86
+ **together** in one package, and the loader picks at runtime.
87
+ - **Exclusions are AND-ed.** `os: ["!darwin", "!win32"]` means "not both",
88
+ not "either", so the complement of the specific packages cannot be spelled
89
+ out. The universal package lists the exotic cpus and OSes explicitly
90
+ instead: skipped wherever a specific package applies, selected everywhere
91
+ else.
92
+
93
+ The model is assembled from its chunk packages on first use and verified
94
+ against `models/model.manifest.json` (sha256 of the model *and* of every
95
+ chunk), written atomically — a killed process cannot leave a corrupt model
96
+ behind, the next run retries. The order is:
97
+
98
+ 1. `LAYA_MODEL_PATH` — explicit path to a `model.onnx` (file or directory);
99
+ 2. a `model.onnx` already present in the package's `models/` directory;
100
+ 3. `~/.cache/laya-system-one/model.onnx` — previously assembled copy;
101
+ 4. the installed `@sys-one/laya-model-chunk-*` packages (the normal path);
102
+ 5. the npm registry, if the chunks were not installed as dependencies;
103
+ 6. the GitHub Release asset — **opt-in only** (`LAYA_ALLOW_GITHUB_FALLBACK=1`),
104
+ kept for compatibility with 1.0.0 installs.
105
+
106
+ ### Offline / air-gapped installs
107
+
108
+ ```bash
109
+ LAYA_PREFETCH_MODEL=1 npm install laya-system-one # fetch during install
110
+ # or point at a model you already have:
111
+ LAYA_MODEL_PATH=/opt/models/model.onnx
112
+ # or drop chunk files somewhere and point at them:
113
+ LAYA_MODEL_CHUNKS_DIR=/opt/models/chunks
114
+ ```
115
+
116
+ ---
117
+
118
+ ## API
119
+
120
+ ### `Laya.load(options)` → `laya`
121
+
122
+ | option | default | description |
123
+ |---|---|---|
124
+ | `backend` | `'native'` | `native` \| `wasm` (or env `LAYA_BACKEND`) |
125
+ | `modelDir` | `<package>/models` | where `model.onnx` and `tokenizer.json` live |
126
+ | `apiKey` | `null` | Bearer token required by the HTTP layer |
127
+ | `port` / `host` | `0` / `127.0.0.1` | where the native server binds |
128
+
129
+ ### `laya.predict(state, questions, model?)` → `Promise<Answer>`
130
+
131
+ `state` is a string, object or array (serialized as JSON). `questions` is a map of question definitions:
132
+
133
+ | type | `criteria` | answer |
134
+ |---|---|---|
135
+ | `choice` | object (label → meaning) or array | `{ type: 'choice', choice, probabilities, confidence }` |
136
+ | `score` | array of ordered levels | `{ type: 'score', score, legend, probabilities, confidence }` |
137
+ | `noul` | optional `{false, true}` text | `{ type: 'noul', noul, confidence, threshold?, decision? }` |
138
+
139
+ `noul` returns `noul` ∈ [0,1] (probability of *true*). With a `threshold`, a boolean `decision` is added. All fields round to 4 decimals.
140
+
141
+ ### HTTP server
142
+
143
+ ```js
144
+ import { serve } from 'laya-system-one';
145
+ const srv = await serve({ port: 8080, apiKey: process.env.LAYA_API_KEY });
146
+ console.log(srv.url); // http://localhost:8080
147
+ await srv.close(); // releases the engine and any child process
148
+ ```
149
+
150
+ | endpoint | method | body | response |
151
+ |---|---|---|---|
152
+ | `/v1/systemone` | `POST` | `{ state, questions, model? }` | `{ model, answers, usage }` |
153
+ | `/health` | `GET` | – | `{ status, model, backend, protocol }` |
154
+
155
+ Errors: `401` missing/invalid API key, `422` invalid payload, `404` unknown route, `413` body over 4 MB.
156
+
157
+ > The `native` backend runs its own internal HTTP server for the engine;
158
+ > `serve()` keeps it on a private loopback port and never exposes it.
159
+
160
+ ---
161
+
162
+ ## CLI
163
+
164
+ ```bash
165
+ npx laya-system-one --port 8080 --backend native
166
+ ```
167
+
168
+ ```
169
+ --port <number> HTTP port (default 8080, or PORT env)
170
+ --host <string> bind address (default 0.0.0.0, or HOST env)
171
+ --backend <type> native | wasm (default native)
172
+ --api-key <string> require Bearer token auth
173
+ --help / --version
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Environment variables
179
+
180
+ | variable | effect |
181
+ |---|---|
182
+ | `LAYA_BACKEND` | `native` \| `wasm` |
183
+ | `LAYA_MODEL_PATH` | explicit `model.onnx` (file or directory) |
184
+ | `LAYA_MODEL_CHUNKS_DIR` | directory with chunk files / chunk packages |
185
+ | `LAYA_MODEL_URL` | override the download URL of the model asset |
186
+ | `LAYA_CACHE_DIR` | where downloaded models are cached |
187
+ | `LAYA_PREFETCH_MODEL` | `1` = fetch the model during `npm install` |
188
+ | `LAYA_SKIP_MODEL_DOWNLOAD` | `1` = never download, never hint |
189
+ | `LAYA_API_KEY` / `API_KEY` | require `Authorization: Bearer <key>` |
190
+ | `LAYA_SERVE_BIN` | explicit path to the `laya-serve` binary |
191
+
192
+ ---
193
+
194
+ ## Performance
195
+
196
+ `npm run bench` measures it on your own hardware and writes a JSON report.
197
+
198
+ Measured by CI on every platform we ship a binary for (the bundled
199
+ `laya-serve` binary, 4 questions per call). Shared GitHub runners vary
200
+ ~20% between runs, so treat these as orders of magnitude, not promises -
201
+ run `npm run bench` on your own hardware for exact numbers:
202
+
203
+ | platform | init | cold question | warm avg (4 q) | warm p50 / p95 | 1 q per call | throughput |
204
+ |---|---|---|---|---|---|---|
205
+ | **linux-arm64** (musl) | 1.9 s | 247 ms | 154 ms | 143 / 205 | 36 ms | 28.1 q/s |
206
+ | **linux-arm64** (glibc) | 1.6 s | 184 ms | 145 ms | 142 / 163 | 40 ms | 24.9 q/s |
207
+ | **win-x64** | 1.7 s | 175 ms | 149 ms | 142 / 175 | 40 ms | 24.8 q/s |
208
+ | **win-arm64** | 1.4 s | 231 ms | 178 ms | 174 / 204 | 47 ms | 21.2 q/s |
209
+ | **mac-arm64** | 1.3 s | 268 ms | 220 ms | 218 / 250 | 51 ms | 19.5 q/s |
210
+ | **linux-x64** (musl) | 2.3 s | 387 ms | 253 ms | 239 / 311 | 59 ms | 16.9 q/s |
211
+ | **linux-x64** (glibc) | 2.5 s | 331 ms | 264 ms | 259 / 322 | 63 ms | 15.8 q/s |
212
+ | **mac-x64** (Intel, ORT 1.23) | 2.9 s | 387 ms | 360 ms | 356 / 398 | 84 ms | 11.9 q/s |
213
+
214
+ `init` is loading the model, `cold` is the very first question (warmup and
215
+ arena allocation), and the warm numbers are the sustained latency.
216
+
217
+ The `wasm` fallback is ~100x slower by design: `tract` specializes the whole
218
+ model per input shape, so it runs a fixed padded shape (see
219
+ `LayaEngine.padForWasm`) and pays for every position - about 6 s per question
220
+ in the small bucket, 1.8 s to load. It exists for browsers and exotic
221
+ platforms, not for throughput.
222
+
223
+ **If you are on a platform we ship a binary for and you see the wasm backend
224
+ being used, that is a bug** - the install is broken. The tests fail on
225
+ purpose in that situation (`LAYA_ALLOW_WASM_FALLBACK=1` is the only way to
226
+ accept the fallback).
227
+
228
+ ## Size
229
+
230
+ | component | size |
231
+ |---|---|
232
+ | npm package | ~88 MB (native binaries for 6 platforms + WASM + tokenizer) |
233
+ | model asset | ~324 MB (acquired once, cached, checksum-verified) |
234
+ | disk after install + model | ~560 MB |
235
+ | Docker (Debian slim + Bun + package) | ~450 MB image |
236
+
237
+ ---
238
+
239
+ ## Requirements
240
+
241
+ | | |
242
+ |---|---|
243
+ | **Node.js** | ≥ 18.17 (zero runtime dependencies) |
244
+ | **Bun** | ≥ 1.0 (fully supported) |
245
+ | **Browsers** | WASM backend (no install required) |
246
+ | **OS** | Linux (glibc + musl/Alpine), macOS (arm64), Windows (x64 + arm64) |
247
+ | **Docker** | works on Debian slim, Ubuntu, Alpine |
248
+
249
+ The `native` backend needs nothing installed: the binary is statically linked and ships in the package (for musl/Alpine it ships as a single self-extracting bundle with its own `lib/`).
250
+
251
+ ---
252
+
253
+ ## Storage
254
+
255
+ | what | where | size |
256
+ |---|---|---|
257
+ | package (code + binaries) | `node_modules/laya-system-one` | ~88 MB unpacked |
258
+ | model asset | `models/model.onnx` or `~/.cache/laya-system-one/` | ~324 MB |
259
+ | chunk packages | `dist/release/model-chunks/` (release artifacts) | ~324 MB |
260
+
261
+ The model is written to the package directory when it is writable, otherwise to the user cache — so global installs (`npm i -g`) and read-only containers work out of the box.
262
+
263
+ ---
264
+
265
+ ## Development
266
+
267
+ ```bash
268
+ npm install
269
+ npm test # unit + packaging (fast, offline)
270
+ npm run test:integration # real model + tokenizer
271
+ npm run test:e2e # HTTP protocol + CLI + lifecycle
272
+ npm run test:install # pack + install into a clean dir + run
273
+ npm run smoke # one-shot human-readable verification
274
+ npm run lint # syntax + packaging + docs consistency gate
275
+ npm run tokenizer:diff # prove the JS tokenizer == the native binary's
276
+ ```
277
+
278
+ The model-distribution pipeline (the reason the 324 MB asset can live on npm):
279
+
280
+ ```bash
281
+ npm run model:chunks # split models/model.onnx into chunk packages
282
+ npm run model:assemble # reassemble from the chunks (byte-identical)
283
+ npm run model:verify # checksum the model against the manifest
284
+ npm run model:publish # publish the chunk packages to npm
285
+ ```
286
+
287
+ CI runs the whole matrix (OS × Node) plus model-backed integration tests on every push — see `.github/workflows/ci.yml`.
288
+
289
+ ## Migrating from 1.0.0
290
+
291
+ 1.0.0 on npm was a different codebase (ONNX Runtime only, model embedded, single language). The `/v1/systemone` wire protocol is unchanged, so HTTP clients keep working. Node API changes:
292
+
293
+ - `predict(state, questions)` takes a *map* of questions (1.0.0 took a single question and returned a bare value);
294
+ - `server.js`/`client.js` were replaced by `serve()` + `Laya.load()`;
295
+ - the model is no longer embedded in the package — it is acquired on first use (see above).
296
+
297
+ ## License
298
+
299
+ Apache-2.0 — see [LICENSE](LICENSE).