laya-system-one 1.1.0-alpha.1 → 1.2.0
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 +393 -221
- package/models/model.manifest.json +15 -14
- package/models/rl_agent_config.json +26 -26
- package/package.json +25 -23
- package/src/agent.js +2 -0
- package/src/laya-native.js +31 -6
- package/src/server.js +194 -179
package/README.md
CHANGED
|
@@ -1,299 +1,471 @@
|
|
|
1
|
-
# Laya System-One
|
|
1
|
+
# Laya System-One ⚡
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
+
[]()
|
|
5
|
+
[]()
|
|
6
|
+
[]()
|
|
7
|
+
[](https://github.com/NandhaKishorM/laya)
|
|
4
8
|
|
|
5
|
-
|
|
9
|
+
> **A fast, self-contained decision engine. Give it any text and a set of typed questions, and it answers them — offline, in milliseconds, in over 100 languages. Drop-in compatible with the TypeSafe Jev API (`POST /v1/systemone`).**
|
|
10
|
+
|
|
11
|
+
Runs entirely on your machine. No Python, no PyTorch, no API keys, no cloud
|
|
12
|
+
calls at inference time. One `npm install` and it works.
|
|
13
|
+
|
|
14
|
+
> **Built on [Laya](https://github.com/NandhaKishorM/laya)** by
|
|
15
|
+
> [Convai Innovations](https://huggingface.co/convaiinnovations) — a
|
|
16
|
+
> community project. The model is theirs; this package makes it run in
|
|
17
|
+
> Node.js, Bun and the browser with no Python in the loop. See
|
|
18
|
+
> [Credits](#-credits).
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🌟 Why Laya System-One?
|
|
23
|
+
|
|
24
|
+
- 🔒 **100% Offline:** Nothing leaves your machine. Ideal for corporate
|
|
25
|
+
intranets, edge servers and privacy-sensitive workflows.
|
|
26
|
+
- ⚡ **Fast:** ~40 ms per question on a warm engine, measured on every platform
|
|
27
|
+
we ship for.
|
|
28
|
+
- 🔄 **TypeSafe Jev Compatible:** Drop-in `POST /v1/systemone`. Point an
|
|
29
|
+
existing Jev client at it and it just works.
|
|
30
|
+
- 🌍 **Multilingual:** Understands English, Portuguese, Spanish, German,
|
|
31
|
+
French, Chinese, Japanese and 100+ more, out of the box.
|
|
32
|
+
- 💻 **Node.js, Bun and Browsers:** Native binary on Node and Bun, WebAssembly
|
|
33
|
+
in the browser.
|
|
34
|
+
- 📦 **Zero Dependencies:** `dependencies` is empty. Nothing to compile,
|
|
35
|
+
nothing to install system-wide, nothing to keep patched.
|
|
36
|
+
- 🧩 **Two Ways to Run It:** As a local HTTP service via the CLI, or in-process
|
|
37
|
+
for zero network overhead.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 📦 Installation
|
|
6
42
|
|
|
7
43
|
```bash
|
|
44
|
+
# npm
|
|
8
45
|
npm install laya-system-one
|
|
46
|
+
|
|
47
|
+
# bun
|
|
48
|
+
bun add laya-system-one
|
|
49
|
+
|
|
50
|
+
# pnpm
|
|
51
|
+
pnpm add laya-system-one
|
|
9
52
|
```
|
|
10
53
|
|
|
11
|
-
|
|
12
|
-
|
|
54
|
+
The right engine for your machine is installed automatically. The model
|
|
55
|
+
(~324 MB) is fetched once on first use and cached.
|
|
13
56
|
|
|
14
|
-
|
|
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
|
-
);
|
|
57
|
+
---
|
|
27
58
|
|
|
28
|
-
|
|
29
|
-
```
|
|
59
|
+
## 🚀 Quick Start
|
|
30
60
|
|
|
31
|
-
|
|
61
|
+
### 1. Launch the HTTP service
|
|
32
62
|
|
|
33
63
|
```bash
|
|
34
64
|
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
65
|
```
|
|
39
66
|
|
|
40
|
-
|
|
67
|
+
| Flag | Env | Default | Description |
|
|
68
|
+
| :--- | :--- | :--- | :--- |
|
|
69
|
+
| `--port <number>` | `PORT` | `8080` | Port to bind |
|
|
70
|
+
| `--host <string>` | `HOST` | `0.0.0.0` | Address to bind |
|
|
71
|
+
| `--backend <type>` | `LAYA_BACKEND` | `native` | `native` or `wasm` |
|
|
72
|
+
| `--api-key <token>` | `LAYA_API_KEY` | *(none)* | Require Bearer auth on `/v1/systemone` |
|
|
41
73
|
|
|
42
|
-
|
|
74
|
+
With authentication:
|
|
43
75
|
|
|
44
|
-
|
|
76
|
+
```bash
|
|
77
|
+
npx laya-system-one --port 8080 --api-key secret-token-xyz
|
|
78
|
+
```
|
|
45
79
|
|
|
46
|
-
|
|
47
|
-
2. runs one forward pass,
|
|
48
|
-
3. turns the logits into calibrated probabilities.
|
|
80
|
+
### 2. Use it in-process (zero network overhead)
|
|
49
81
|
|
|
50
|
-
|
|
82
|
+
```javascript
|
|
83
|
+
import { Laya } from 'laya-system-one';
|
|
51
84
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
85
|
+
// 1. Initialize the engine
|
|
86
|
+
const laya = await Laya.load();
|
|
87
|
+
|
|
88
|
+
// 2. Define the state (string, object, or array)
|
|
89
|
+
const state = {
|
|
90
|
+
customer_id: 'cust_9821',
|
|
91
|
+
message: 'We were charged twice on our March invoice. Please refund the duplicate amount or we will cancel our plan.'
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// 3. Define typed questions
|
|
95
|
+
const questions = {
|
|
96
|
+
department: {
|
|
97
|
+
type: 'choice',
|
|
98
|
+
instructions: 'Which team should resolve this customer inquiry?',
|
|
99
|
+
criteria: {
|
|
100
|
+
billing: 'Invoices, refunds, and duplicate charges',
|
|
101
|
+
tech_support: 'Software bugs, outages, and error messages',
|
|
102
|
+
sales: 'Upgrades, plan changes, and enterprise contracts'
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
urgency: {
|
|
106
|
+
type: 'score',
|
|
107
|
+
instructions: 'Assess the urgency level of this inquiry.',
|
|
108
|
+
criteria: ['Low / routine', 'Moderate', 'Critical / blocking / angry']
|
|
109
|
+
},
|
|
110
|
+
churn_risk: {
|
|
111
|
+
type: 'noul',
|
|
112
|
+
instructions: 'Does this message present an explicit risk of customer churn?',
|
|
113
|
+
threshold: 0.5
|
|
114
|
+
}
|
|
115
|
+
};
|
|
56
116
|
|
|
57
|
-
|
|
117
|
+
// 4. Evaluate
|
|
118
|
+
const result = await laya.predict(state, questions);
|
|
58
119
|
|
|
59
|
-
|
|
120
|
+
console.log(result.answers.department.choice); // -> "billing"
|
|
121
|
+
console.log(result.answers.department.confidence); // -> 1.0
|
|
122
|
+
console.log(result.answers.urgency.score); // -> 1.95
|
|
123
|
+
console.log(result.answers.churn_risk.noul); // -> 0.968
|
|
124
|
+
console.log(result.answers.churn_risk.decision); // -> true
|
|
125
|
+
```
|
|
60
126
|
|
|
61
|
-
|
|
127
|
+
### 3. Serve it from inside your app
|
|
62
128
|
|
|
63
|
-
|
|
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
|
|
129
|
+
```javascript
|
|
130
|
+
import { serve } from 'laya-system-one';
|
|
107
131
|
|
|
108
|
-
|
|
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
|
-
```
|
|
132
|
+
const srv = await serve({ host: '127.0.0.1', port: 8080, apiKey: 'optional-key' });
|
|
115
133
|
|
|
116
|
-
|
|
134
|
+
console.log(`Laya server running at ${srv.url}/v1/systemone`);
|
|
117
135
|
|
|
118
|
-
|
|
136
|
+
// later:
|
|
137
|
+
await srv.close();
|
|
138
|
+
```
|
|
119
139
|
|
|
120
|
-
### `Laya.load(options)`
|
|
140
|
+
### `Laya.load(options)` options
|
|
121
141
|
|
|
122
|
-
|
|
|
123
|
-
|
|
124
|
-
| `backend` | `'native'` | `native`
|
|
125
|
-
| `modelDir` |
|
|
126
|
-
| `
|
|
142
|
+
| Option | Default | Description |
|
|
143
|
+
| :--- | :--- | :--- |
|
|
144
|
+
| `backend` | `'native'` | `native` (bundled Rust server) or `wasm` |
|
|
145
|
+
| `modelDir` | the package's `models/` | where `model.onnx` and `tokenizer.json` live |
|
|
146
|
+
| `maxLen` | `2048` | token budget for the state — raise it for long documents (max 8192, see [Long inputs](#long-inputs)) |
|
|
147
|
+
| `apiKey` | `null` | require a Bearer token on the HTTP layer |
|
|
127
148
|
| `port` / `host` | `0` / `127.0.0.1` | where the native server binds |
|
|
149
|
+
| `threads` | `0` | inference threads (`0` = runtime default) |
|
|
128
150
|
|
|
129
|
-
|
|
151
|
+
---
|
|
130
152
|
|
|
131
|
-
|
|
153
|
+
## 📡 HTTP API Reference (TypeSafe Jev compatible)
|
|
132
154
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
155
|
+
```http
|
|
156
|
+
POST /v1/systemone
|
|
157
|
+
Host: localhost:8080
|
|
158
|
+
Content-Type: application/json
|
|
159
|
+
Authorization: Bearer <API_KEY> [optional unless configured]
|
|
160
|
+
```
|
|
138
161
|
|
|
139
|
-
|
|
162
|
+
| Parameter | Type | Required | Description |
|
|
163
|
+
| :--- | :--- | :--- | :--- |
|
|
164
|
+
| `state` | `string` \| `object` \| `array` | **Yes** | The context or text being evaluated. |
|
|
165
|
+
| `questions` | `Record<string, Question>` | **Yes** | Map of question keys to typed questions. |
|
|
166
|
+
| `model` | `string` | No | Model name (defaults to `laya-multilingual`, echoed back). |
|
|
140
167
|
|
|
141
|
-
###
|
|
168
|
+
### Question types
|
|
142
169
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
**`choice`** — pick one of several options:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"type": "choice",
|
|
175
|
+
"instructions": "Which department should handle this ticket?",
|
|
176
|
+
"criteria": {
|
|
177
|
+
"billing": "Invoices and credit card transactions",
|
|
178
|
+
"technical": "Software bugs and service disruptions"
|
|
179
|
+
}
|
|
180
|
+
}
|
|
148
181
|
```
|
|
149
182
|
|
|
150
|
-
|
|
151
|
-
|---|---|---|---|
|
|
152
|
-
| `/v1/systemone` | `POST` | `{ state, questions, model? }` | `{ model, answers, usage }` |
|
|
153
|
-
| `/health` | `GET` | – | `{ status, model, backend, protocol }` |
|
|
183
|
+
**`score`** — place on an ordered scale:
|
|
154
184
|
|
|
155
|
-
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"type": "score",
|
|
188
|
+
"instructions": "Rate the severity of the issue.",
|
|
189
|
+
"criteria": ["Minor cosmetic issue", "Degraded functionality", "Critical full service outage"]
|
|
190
|
+
}
|
|
191
|
+
```
|
|
156
192
|
|
|
157
|
-
|
|
158
|
-
> `serve()` keeps it on a private loopback port and never exposes it.
|
|
193
|
+
**`noul`** — calibrated yes/no probability:
|
|
159
194
|
|
|
160
|
-
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"type": "noul",
|
|
198
|
+
"instructions": "Does the user explicitly request a refund?",
|
|
199
|
+
"threshold": 0.6
|
|
200
|
+
}
|
|
201
|
+
```
|
|
161
202
|
|
|
162
|
-
|
|
203
|
+
### Example request
|
|
163
204
|
|
|
164
205
|
```bash
|
|
165
|
-
|
|
206
|
+
curl -X POST http://localhost:8080/v1/systemone \
|
|
207
|
+
-H "Content-Type: application/json" \
|
|
208
|
+
-d '{
|
|
209
|
+
"state": { "text": "Fui cobrado duas vezes na minha fatura. Reembolsem imediatamente." },
|
|
210
|
+
"questions": {
|
|
211
|
+
"dept": {
|
|
212
|
+
"type": "choice",
|
|
213
|
+
"instructions": "Which department should respond?",
|
|
214
|
+
"criteria": { "billing": "Refunds, invoices, and payments", "support": "Technical and product questions" }
|
|
215
|
+
},
|
|
216
|
+
"urgency": {
|
|
217
|
+
"type": "score",
|
|
218
|
+
"instructions": "Urgency rating",
|
|
219
|
+
"criteria": ["Low", "Medium", "High"]
|
|
220
|
+
},
|
|
221
|
+
"refund_demanded": {
|
|
222
|
+
"type": "noul",
|
|
223
|
+
"instructions": "Is the customer requesting a refund?",
|
|
224
|
+
"threshold": 0.5
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}'
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### Example response
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{
|
|
234
|
+
"model": "laya-multilingual",
|
|
235
|
+
"answers": {
|
|
236
|
+
"dept": {
|
|
237
|
+
"type": "choice",
|
|
238
|
+
"choice": "billing",
|
|
239
|
+
"probabilities": { "billing": 1.0, "support": 0.0 },
|
|
240
|
+
"confidence": 1.0
|
|
241
|
+
},
|
|
242
|
+
"urgency": {
|
|
243
|
+
"type": "score",
|
|
244
|
+
"score": 1.9482,
|
|
245
|
+
"legend": { "0": "Low", "1": "Medium", "2": "High" },
|
|
246
|
+
"probabilities": { "0": 0.0011, "1": 0.0496, "2": 0.9493 },
|
|
247
|
+
"confidence": 0.9493
|
|
248
|
+
},
|
|
249
|
+
"refund_demanded": {
|
|
250
|
+
"type": "noul",
|
|
251
|
+
"noul": 0.9852,
|
|
252
|
+
"confidence": 0.9852,
|
|
253
|
+
"threshold": 0.5,
|
|
254
|
+
"decision": true
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
"usage": { "input_tokens": 82, "output_tokens": 12 }
|
|
258
|
+
}
|
|
166
259
|
```
|
|
167
260
|
|
|
261
|
+
### Healthcheck
|
|
262
|
+
|
|
263
|
+
```http
|
|
264
|
+
GET /health
|
|
168
265
|
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"status": "ok",
|
|
270
|
+
"model": "laya-multilingual",
|
|
271
|
+
"version": "1.1.0",
|
|
272
|
+
"protocol": "TypeSafe Jev /v1/systemone compatible"
|
|
273
|
+
}
|
|
174
274
|
```
|
|
175
275
|
|
|
276
|
+
### Error codes
|
|
277
|
+
|
|
278
|
+
- `401 Unauthorized` — API key configured, header missing or wrong.
|
|
279
|
+
- `422 Unprocessable Entity` — invalid JSON, or `state`/`questions` missing.
|
|
280
|
+
- `404 Not Found` — unknown route.
|
|
281
|
+
- `413 Payload Too Large` — body over 4 MB.
|
|
282
|
+
|
|
176
283
|
---
|
|
177
284
|
|
|
178
|
-
##
|
|
285
|
+
## ⚙️ Backends
|
|
286
|
+
|
|
287
|
+
| Backend | How it runs | When to use |
|
|
288
|
+
| :--- | :--- | :--- |
|
|
289
|
+
| **`native`** *(default)* | A self-contained Rust server bundled with the package | The normal choice. Fastest, nothing to install. |
|
|
290
|
+
| **`wasm`** | Pure Rust compiled to WebAssembly, also bundled | Browsers, or platforms with no native build. |
|
|
179
291
|
|
|
180
|
-
|
|
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 |
|
|
292
|
+
Both ship inside the package — nothing is compiled or downloaded at install
|
|
293
|
+
time. Switch with `--backend wasm` or `LAYA_BACKEND=wasm`.
|
|
191
294
|
|
|
192
295
|
---
|
|
193
296
|
|
|
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 |
|
|
297
|
+
## 📊 Performance
|
|
236
298
|
|
|
237
|
-
|
|
299
|
+
Measured on real hardware, on every platform we ship a binary for, with the
|
|
300
|
+
default `native` backend. Run `npm run bench` to measure your own machine.
|
|
238
301
|
|
|
239
|
-
|
|
302
|
+
| Platform | Load | First answer | Warm (4 q/call) | Per question |
|
|
303
|
+
| :--- | ---: | ---: | ---: | ---: |
|
|
304
|
+
| macOS arm64 | 1.3 s | 268 ms | 220 ms | 51 ms |
|
|
305
|
+
| Windows arm64 | 1.4 s | 231 ms | 178 ms | 47 ms |
|
|
306
|
+
| Linux arm64 | 1.6 s | 184 ms | 145 ms | 40 ms |
|
|
307
|
+
| Windows x64 | 1.7 s | 175 ms | 149 ms | 40 ms |
|
|
308
|
+
| Linux arm64 (musl) | 1.9 s | 247 ms | 154 ms | 36 ms |
|
|
309
|
+
| Linux x64 (musl) | 2.3 s | 387 ms | 253 ms | 59 ms |
|
|
310
|
+
| Linux x64 | 2.5 s | 331 ms | 264 ms | 63 ms |
|
|
311
|
+
| macOS x64 | 2.9 s | 387 ms | 360 ms | 84 ms |
|
|
240
312
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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 |
|
|
313
|
+
`Load` is reading the model into memory. `First answer` includes warmup. The
|
|
314
|
+
warm numbers are sustained latency. Shared CI runners vary by ~20% between
|
|
315
|
+
runs, so treat these as orders of magnitude rather than exact figures.
|
|
248
316
|
|
|
249
|
-
The `
|
|
317
|
+
The `wasm` backend is roughly 100x slower — it exists so browsers and unusual
|
|
318
|
+
platforms work at all, not for throughput.
|
|
250
319
|
|
|
251
|
-
|
|
320
|
+
### Long inputs
|
|
252
321
|
|
|
253
|
-
|
|
322
|
+
The model reads up to **8,192 tokens**, but it ships with a conservative
|
|
323
|
+
**2,048-token** budget so it stays usable on weak machines. The budget is a
|
|
324
|
+
cap, not a cost: **short inputs are unaffected by raising it** — a 74-token
|
|
325
|
+
question answers in ~90 ms whatever the limit is, because the work follows the
|
|
326
|
+
input's real length.
|
|
254
327
|
|
|
255
|
-
|
|
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 |
|
|
328
|
+
Raise it when your inputs are long documents:
|
|
260
329
|
|
|
261
|
-
|
|
330
|
+
```bash
|
|
331
|
+
LAYA_MAX_LEN=8192 npx laya-system-one --port 8080
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```js
|
|
335
|
+
const laya = await Laya.load({ maxLen: 8192 });
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
Measured on one machine (Windows arm64, `native` backend), by input length:
|
|
339
|
+
|
|
340
|
+
| tokens | default (2048) | `maxLen: 8192` |
|
|
341
|
+
| ---: | ---: | ---: |
|
|
342
|
+
| 74 | 88 ms | 88 ms |
|
|
343
|
+
| 1,000 | 0.9 s | 0.9 s |
|
|
344
|
+
| 2,000 | 6.4 s | 6.4 s |
|
|
345
|
+
| 4,000 | 9.2 s *(truncated)* | 21.3 s |
|
|
346
|
+
| 8,000 | 9.2 s *(truncated)* | 190 s |
|
|
347
|
+
|
|
348
|
+
Two things worth knowing before you raise it:
|
|
349
|
+
|
|
350
|
+
- **Accuracy degrades with length.** Upstream measured 16–18 of 20 requests
|
|
351
|
+
correct up to about 4,000 tokens, and 8–17 of 20 beyond that. Check your own
|
|
352
|
+
data — long-document accuracy is not something to assume.
|
|
353
|
+
- **Cost grows steeply.** Past ~2,000 tokens the time climbs faster than the
|
|
354
|
+
input does (attention is quadratic). 8,000 tokens is minutes, not seconds,
|
|
355
|
+
on a CPU. If you routinely handle documents that long, truncate them
|
|
356
|
+
yourself to the part that matters, or run the upstream Python package on a
|
|
357
|
+
GPU.
|
|
358
|
+
|
|
359
|
+
Truncation is the real risk of leaving it at the default: a long message gets
|
|
360
|
+
cut off and the answer can be wrong rather than slow. On a ~3,000-token input
|
|
361
|
+
the shipped default answered `sales` where the full text answers `billing`.
|
|
262
362
|
|
|
263
363
|
---
|
|
264
364
|
|
|
265
|
-
##
|
|
365
|
+
## 🧾 Environment variables
|
|
366
|
+
|
|
367
|
+
| Variable | Effect |
|
|
368
|
+
| :--- | :--- |
|
|
369
|
+
| `LAYA_BACKEND` | `native` or `wasm` |
|
|
370
|
+
| `LAYA_MAX_LEN` | token budget for the state (default 2048, max 8192) |
|
|
371
|
+
| `LAYA_MODEL_PATH` | Use a `model.onnx` you already have (file or directory) |
|
|
372
|
+
| `LAYA_MODEL_CHUNKS_DIR` | Directory holding the model chunks |
|
|
373
|
+
| `LAYA_MODEL_URL` | Override where the model is downloaded from |
|
|
374
|
+
| `LAYA_CACHE_DIR` | Where the model is cached |
|
|
375
|
+
| `LAYA_PREFETCH_MODEL` | `1` = download the model during `npm install` |
|
|
376
|
+
| `LAYA_SKIP_MODEL_DOWNLOAD` | `1` = never download, never prompt |
|
|
377
|
+
| `LAYA_API_KEY` / `API_KEY` | Require `Authorization: Bearer <key>` |
|
|
378
|
+
| `LAYA_SERVE_BIN` | Use a specific `laya-serve` binary |
|
|
379
|
+
|
|
380
|
+
**Offline or air-gapped:**
|
|
266
381
|
|
|
267
382
|
```bash
|
|
268
|
-
npm install
|
|
269
|
-
|
|
270
|
-
|
|
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
|
|
383
|
+
LAYA_PREFETCH_MODEL=1 npm install laya-system-one # fetch during install
|
|
384
|
+
LAYA_MODEL_PATH=/opt/models/model.onnx # or bring your own copy
|
|
385
|
+
LAYA_MODEL_CHUNKS_DIR=/opt/models/chunks # or a directory of chunks
|
|
276
386
|
```
|
|
277
387
|
|
|
278
|
-
|
|
388
|
+
---
|
|
389
|
+
|
|
390
|
+
## 💻 Requirements
|
|
391
|
+
|
|
392
|
+
| | |
|
|
393
|
+
| :--- | :--- |
|
|
394
|
+
| **Node.js** | ≥ 18.17 |
|
|
395
|
+
| **Bun** | ≥ 1.0 |
|
|
396
|
+
| **Browsers** | The `wasm` backend |
|
|
397
|
+
| **OS** | Linux (glibc and musl/Alpine), macOS (arm64 and x64), Windows (x64 and arm64) |
|
|
398
|
+
| **Docker** | Debian, Ubuntu, Alpine |
|
|
399
|
+
|
|
400
|
+
No runtime dependencies. The right native binary for your machine is installed
|
|
401
|
+
automatically — nothing to compile, no system packages to add.
|
|
402
|
+
|
|
403
|
+
---
|
|
404
|
+
|
|
405
|
+
## 📥 What gets installed
|
|
406
|
+
|
|
407
|
+
The package itself is small; the heavy parts arrive as dependencies npm picks
|
|
408
|
+
for your platform, so you only download what you can run.
|
|
409
|
+
|
|
410
|
+
| | Size |
|
|
411
|
+
| :--- | ---: |
|
|
412
|
+
| `laya-system-one` (code, tokenizer, wasm engine) | ~8.5 MB |
|
|
413
|
+
| The one native binary for your platform | 8–26 MB |
|
|
414
|
+
| The 13 model chunks | ~235 MB total |
|
|
415
|
+
| The model on disk, after the first run | ~324 MB |
|
|
416
|
+
|
|
417
|
+
The model is written next to the package when that directory is writable, and
|
|
418
|
+
to your user cache otherwise — so `npm i -g` and read-only containers work
|
|
419
|
+
without extra configuration. Every copy is checksum-verified, and a run that
|
|
420
|
+
is killed mid-download leaves nothing corrupt behind.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## 🛠️ Development
|
|
279
425
|
|
|
280
426
|
```bash
|
|
281
|
-
npm
|
|
282
|
-
npm run
|
|
283
|
-
npm run
|
|
284
|
-
npm run
|
|
427
|
+
npm install
|
|
428
|
+
npm run check # lint, unit, packaging, integration, e2e + install rehearsal
|
|
429
|
+
npm run check:quick # the same minus the model-backed suites
|
|
430
|
+
npm run test:rehearsal # install from a local registry and use it, Node + Bun
|
|
431
|
+
npm run bench # measure on this machine
|
|
432
|
+
npm run lint # syntax + packaging + docs consistency
|
|
285
433
|
```
|
|
286
434
|
|
|
287
|
-
CI
|
|
288
|
-
|
|
289
|
-
|
|
435
|
+
CI builds every platform, proves each binary answers 10 questions, and uploads
|
|
436
|
+
the packages as artifacts. `verify-published.yml` installs a published version
|
|
437
|
+
from the real registry on every platform — Node and Bun, including Alpine for
|
|
438
|
+
musl — and runs a real inference.
|
|
290
439
|
|
|
291
|
-
|
|
440
|
+
---
|
|
292
441
|
|
|
293
|
-
|
|
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).
|
|
442
|
+
## 🙏 Credits
|
|
296
443
|
|
|
297
|
-
|
|
444
|
+
**This package would not exist without
|
|
445
|
+
[Laya](https://github.com/NandhaKishorM/laya).** It is a community project by
|
|
446
|
+
[Convai Innovations](https://huggingface.co/convaiinnovations) — the model,
|
|
447
|
+
the architecture, the training method and the wire protocol are all theirs.
|
|
448
|
+
What this package adds is a way to run it where Python is not an option:
|
|
449
|
+
Node.js, Bun and the browser.
|
|
298
450
|
|
|
299
|
-
|
|
451
|
+
| | |
|
|
452
|
+
| :--- | :--- |
|
|
453
|
+
| **Upstream project** | [NandhaKishorM/laya](https://github.com/NandhaKishorM/laya) |
|
|
454
|
+
| **Model** | [`convaiinnovations/laya-multilingual`](https://huggingface.co/convaiinnovations/laya-multilingual) (mmBERT-base, 322M params) |
|
|
455
|
+
| **Other checkpoints** | [`convaiinnovations/laya`](https://huggingface.co/convaiinnovations/laya) (English), [`laya-typed-decisions`](https://huggingface.co/convaiinnovations/laya-typed-decisions) |
|
|
456
|
+
| **Demo** | [Hugging Face Space](https://huggingface.co/spaces/convaiinnovations/laya-demo) |
|
|
457
|
+
| **Method** | RLCD — reinforcement learning against strictly proper scoring rules |
|
|
458
|
+
| **License** | Apache-2.0 (upstream and this package alike) |
|
|
459
|
+
|
|
460
|
+
If you find this useful, the credit belongs upstream — star
|
|
461
|
+
[their repository](https://github.com/NandhaKishorM/laya) and consider
|
|
462
|
+
[supporting the author](https://www.buymeacoffee.com/nandakishorm).
|
|
463
|
+
|
|
464
|
+
## 📄 License
|
|
465
|
+
|
|
466
|
+
[Apache-2.0](LICENSE) — the same license as the upstream project.
|
|
467
|
+
|
|
468
|
+
- **This package:** [Italo Almeida](https://github.com/italoalmeida0) —
|
|
469
|
+
[laya-system-one](https://github.com/italoalmeida0/laya-system-one)
|
|
470
|
+
- **Model & upstream:** Convai Innovations —
|
|
471
|
+
[NandhaKishorM/laya](https://github.com/NandhaKishorM/laya)
|