laya-system-one 1.1.0 → 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 +471 -384
- package/models/model.manifest.json +2 -1
- package/models/rl_agent_config.json +26 -26
- package/package.json +12 -10
- package/src/agent.js +2 -0
- package/src/laya-native.js +31 -6
package/README.md
CHANGED
|
@@ -1,384 +1,471 @@
|
|
|
1
|
-
# Laya System-One ⚡
|
|
2
|
-
|
|
3
|
-
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
-
[]()
|
|
5
|
-
[]()
|
|
6
|
-
[]()
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
|
145
|
-
|
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
"
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
-
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
310
|
-
|
|
|
311
|
-
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
to
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
-
|
|
384
|
-
|
|
1
|
+
# Laya System-One ⚡
|
|
2
|
+
|
|
3
|
+
[](https://opensource.org/licenses/Apache-2.0)
|
|
4
|
+
[]()
|
|
5
|
+
[]()
|
|
6
|
+
[]()
|
|
7
|
+
[](https://github.com/NandhaKishorM/laya)
|
|
8
|
+
|
|
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
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# npm
|
|
45
|
+
npm install laya-system-one
|
|
46
|
+
|
|
47
|
+
# bun
|
|
48
|
+
bun add laya-system-one
|
|
49
|
+
|
|
50
|
+
# pnpm
|
|
51
|
+
pnpm add laya-system-one
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The right engine for your machine is installed automatically. The model
|
|
55
|
+
(~324 MB) is fetched once on first use and cached.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🚀 Quick Start
|
|
60
|
+
|
|
61
|
+
### 1. Launch the HTTP service
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx laya-system-one --port 8080
|
|
65
|
+
```
|
|
66
|
+
|
|
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` |
|
|
73
|
+
|
|
74
|
+
With authentication:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
npx laya-system-one --port 8080 --api-key secret-token-xyz
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2. Use it in-process (zero network overhead)
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
import { Laya } from 'laya-system-one';
|
|
84
|
+
|
|
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
|
+
};
|
|
116
|
+
|
|
117
|
+
// 4. Evaluate
|
|
118
|
+
const result = await laya.predict(state, questions);
|
|
119
|
+
|
|
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
|
+
```
|
|
126
|
+
|
|
127
|
+
### 3. Serve it from inside your app
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
import { serve } from 'laya-system-one';
|
|
131
|
+
|
|
132
|
+
const srv = await serve({ host: '127.0.0.1', port: 8080, apiKey: 'optional-key' });
|
|
133
|
+
|
|
134
|
+
console.log(`Laya server running at ${srv.url}/v1/systemone`);
|
|
135
|
+
|
|
136
|
+
// later:
|
|
137
|
+
await srv.close();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `Laya.load(options)` options
|
|
141
|
+
|
|
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 |
|
|
148
|
+
| `port` / `host` | `0` / `127.0.0.1` | where the native server binds |
|
|
149
|
+
| `threads` | `0` | inference threads (`0` = runtime default) |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 📡 HTTP API Reference (TypeSafe Jev compatible)
|
|
154
|
+
|
|
155
|
+
```http
|
|
156
|
+
POST /v1/systemone
|
|
157
|
+
Host: localhost:8080
|
|
158
|
+
Content-Type: application/json
|
|
159
|
+
Authorization: Bearer <API_KEY> [optional unless configured]
|
|
160
|
+
```
|
|
161
|
+
|
|
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). |
|
|
167
|
+
|
|
168
|
+
### Question types
|
|
169
|
+
|
|
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
|
+
}
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
**`score`** — place on an ordered scale:
|
|
184
|
+
|
|
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
|
+
```
|
|
192
|
+
|
|
193
|
+
**`noul`** — calibrated yes/no probability:
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"type": "noul",
|
|
198
|
+
"instructions": "Does the user explicitly request a refund?",
|
|
199
|
+
"threshold": 0.6
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Example request
|
|
204
|
+
|
|
205
|
+
```bash
|
|
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
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Healthcheck
|
|
262
|
+
|
|
263
|
+
```http
|
|
264
|
+
GET /health
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
```json
|
|
268
|
+
{
|
|
269
|
+
"status": "ok",
|
|
270
|
+
"model": "laya-multilingual",
|
|
271
|
+
"version": "1.1.0",
|
|
272
|
+
"protocol": "TypeSafe Jev /v1/systemone compatible"
|
|
273
|
+
}
|
|
274
|
+
```
|
|
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
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
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. |
|
|
291
|
+
|
|
292
|
+
Both ship inside the package — nothing is compiled or downloaded at install
|
|
293
|
+
time. Switch with `--backend wasm` or `LAYA_BACKEND=wasm`.
|
|
294
|
+
|
|
295
|
+
---
|
|
296
|
+
|
|
297
|
+
## 📊 Performance
|
|
298
|
+
|
|
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.
|
|
301
|
+
|
|
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 |
|
|
312
|
+
|
|
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.
|
|
316
|
+
|
|
317
|
+
The `wasm` backend is roughly 100x slower — it exists so browsers and unusual
|
|
318
|
+
platforms work at all, not for throughput.
|
|
319
|
+
|
|
320
|
+
### Long inputs
|
|
321
|
+
|
|
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.
|
|
327
|
+
|
|
328
|
+
Raise it when your inputs are long documents:
|
|
329
|
+
|
|
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`.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
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:**
|
|
381
|
+
|
|
382
|
+
```bash
|
|
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
|
|
386
|
+
```
|
|
387
|
+
|
|
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
|
|
425
|
+
|
|
426
|
+
```bash
|
|
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
|
|
433
|
+
```
|
|
434
|
+
|
|
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.
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
## 🙏 Credits
|
|
443
|
+
|
|
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.
|
|
450
|
+
|
|
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)
|
|
@@ -103,5 +103,6 @@
|
|
|
103
103
|
"npm": "https://registry.npmjs.org",
|
|
104
104
|
"github": "https://github.com/italoalmeida0/laya-system-one/releases/download/v1.1.0/model.onnx",
|
|
105
105
|
"githubLegacy": "https://github.com/italoalmeida0/laya-system-one/releases/download/v1.0.0/model.onnx"
|
|
106
|
-
}
|
|
106
|
+
},
|
|
107
|
+
"modelVersion": "1.1.0"
|
|
107
108
|
}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
{
|
|
2
|
-
"encoder": "jhu-clsp/mmBERT-base",
|
|
3
|
-
"head_layers": 2,
|
|
4
|
-
"max_len":
|
|
5
|
-
"head_max_len": 256,
|
|
6
|
-
"max_prefixes": 6,
|
|
7
|
-
"act_costs": {
|
|
8
|
-
"escalate": 0.5
|
|
9
|
-
},
|
|
10
|
-
"cost_wrong_act": 3
|
|
11
|
-
"amp_dtype": "bf16",
|
|
12
|
-
"model_name": "rl-agent",
|
|
13
|
-
"temperature": [
|
|
14
|
-
1
|
|
15
|
-
1
|
|
16
|
-
1
|
|
17
|
-
],
|
|
18
|
-
"temperature_by_options": {},
|
|
19
|
-
"training": {
|
|
20
|
-
"updates": 15987,
|
|
21
|
-
"epochs_completed": 4,
|
|
22
|
-
"hours": 4.97,
|
|
23
|
-
"world_size": 1,
|
|
24
|
-
"fine_tuned_from_checkpoint": false
|
|
25
|
-
}
|
|
26
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"encoder": "jhu-clsp/mmBERT-base",
|
|
3
|
+
"head_layers": 2,
|
|
4
|
+
"max_len": 2048,
|
|
5
|
+
"head_max_len": 256,
|
|
6
|
+
"max_prefixes": 6,
|
|
7
|
+
"act_costs": {
|
|
8
|
+
"escalate": 0.5
|
|
9
|
+
},
|
|
10
|
+
"cost_wrong_act": 3,
|
|
11
|
+
"amp_dtype": "bf16",
|
|
12
|
+
"model_name": "rl-agent",
|
|
13
|
+
"temperature": [
|
|
14
|
+
1,
|
|
15
|
+
1,
|
|
16
|
+
1
|
|
17
|
+
],
|
|
18
|
+
"temperature_by_options": {},
|
|
19
|
+
"training": {
|
|
20
|
+
"updates": 15987,
|
|
21
|
+
"epochs_completed": 4,
|
|
22
|
+
"hours": 4.97,
|
|
23
|
+
"world_size": 1,
|
|
24
|
+
"fine_tuned_from_checkpoint": false
|
|
25
|
+
}
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "laya-system-one",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Self-contained System 1 decision engine: multilingual INT8 transformer served by a zero-dependency native binary (or the bundled pure-Rust WASM engine). TypeSafe Jev wire-compatible (/v1/systemone). Runs offline in Node.js, Bun and browsers.",
|
|
5
5
|
"main": "./src/index.js",
|
|
6
6
|
"module": "./src/index.js",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"test:all": "npm run lint && npm run test:unit && npm run test:packaging && npm run test:integration && npm run test:e2e",
|
|
33
33
|
"lint": "node ./tools/lint.js",
|
|
34
34
|
"bundle": "node ./tools/make-bundle.js",
|
|
35
|
-
"model:chunks": "node ./tools/model-chunks.js build --chunk-mb 24
|
|
35
|
+
"model:chunks": "node ./tools/model-chunks.js build --chunk-mb 24",
|
|
36
36
|
"model:assemble": "node ./tools/model-chunks.js assemble",
|
|
37
37
|
"model:verify": "node ./tools/model-chunks.js verify",
|
|
38
38
|
"model:publish": "node ./tools/model-chunks.js publish",
|
|
@@ -51,7 +51,9 @@
|
|
|
51
51
|
"check": "node ./tools/local-check.js",
|
|
52
52
|
"check:quick": "node ./tools/local-check.js --quick",
|
|
53
53
|
"quick:check": "node ./tools/quick-check.js",
|
|
54
|
-
"preflight": "node ./tools/preflight-publish.js"
|
|
54
|
+
"preflight": "node ./tools/preflight-publish.js",
|
|
55
|
+
"model:export": "python ./tools/export-model.py",
|
|
56
|
+
"model:diff": "node ./tools/model-diff.js"
|
|
55
57
|
},
|
|
56
58
|
"repository": {
|
|
57
59
|
"type": "git",
|
|
@@ -79,13 +81,13 @@
|
|
|
79
81
|
"homepage": "https://github.com/italoalmeida0/laya-system-one#readme",
|
|
80
82
|
"dependencies": {},
|
|
81
83
|
"optionalDependencies": {
|
|
82
|
-
"@sys-one/laya-serve-darwin-arm64": "1.
|
|
83
|
-
"@sys-one/laya-serve-darwin-x64": "1.
|
|
84
|
-
"@sys-one/laya-serve-win32-x64": "1.
|
|
85
|
-
"@sys-one/laya-serve-win32-arm64": "1.
|
|
86
|
-
"@sys-one/laya-serve-linux-x64": "1.
|
|
87
|
-
"@sys-one/laya-serve-linux-arm64": "1.
|
|
88
|
-
"@sys-one/laya-serve-universal": "1.
|
|
84
|
+
"@sys-one/laya-serve-darwin-arm64": "1.2.0",
|
|
85
|
+
"@sys-one/laya-serve-darwin-x64": "1.2.0",
|
|
86
|
+
"@sys-one/laya-serve-win32-x64": "1.2.0",
|
|
87
|
+
"@sys-one/laya-serve-win32-arm64": "1.2.0",
|
|
88
|
+
"@sys-one/laya-serve-linux-x64": "1.2.0",
|
|
89
|
+
"@sys-one/laya-serve-linux-arm64": "1.2.0",
|
|
90
|
+
"@sys-one/laya-serve-universal": "1.2.0",
|
|
89
91
|
"@sys-one/laya-model-chunk-00": "1.1.0",
|
|
90
92
|
"@sys-one/laya-model-chunk-01": "1.1.0",
|
|
91
93
|
"@sys-one/laya-model-chunk-02": "1.1.0",
|
package/src/agent.js
CHANGED
|
@@ -165,6 +165,7 @@ export class Laya {
|
|
|
165
165
|
port: options.port,
|
|
166
166
|
apiKey: options.apiKey,
|
|
167
167
|
threads: options.threads,
|
|
168
|
+
maxLen: options.maxLen,
|
|
168
169
|
});
|
|
169
170
|
await srv.start();
|
|
170
171
|
return new LayaNative(srv, options);
|
|
@@ -307,6 +308,7 @@ export class LayaNative {
|
|
|
307
308
|
port: options.port,
|
|
308
309
|
apiKey: options.apiKey,
|
|
309
310
|
threads: options.threads,
|
|
311
|
+
maxLen: options.maxLen,
|
|
310
312
|
});
|
|
311
313
|
await srv.start();
|
|
312
314
|
return new LayaNative(srv, options);
|
package/src/laya-native.js
CHANGED
|
@@ -136,6 +136,28 @@ function binaryCandidates() {
|
|
|
136
136
|
return { exe, slots };
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
/**
|
|
140
|
+
* The argument list for the native server. Pure, so the precedence between an
|
|
141
|
+
* explicit option, LAYA_MAX_LEN and the config file can be tested without
|
|
142
|
+
* spawning anything - a dropped option here silently leaves long inputs
|
|
143
|
+
* truncated with no error anywhere.
|
|
144
|
+
*/
|
|
145
|
+
export function buildServerArgs({ modelDir, host, port, threads, apiKey, maxLen } = {}) {
|
|
146
|
+
const args = [
|
|
147
|
+
'--model-dir', modelDir,
|
|
148
|
+
'--host', host,
|
|
149
|
+
'--port', String(port),
|
|
150
|
+
'--threads', String(threads),
|
|
151
|
+
];
|
|
152
|
+
// Token budget: an explicit option wins, then LAYA_MAX_LEN, then whatever
|
|
153
|
+
// the config file says (the binary applies that last fallback itself, so we
|
|
154
|
+
// only pass a value when the caller asked for one).
|
|
155
|
+
const budget = maxLen ?? (process.env.LAYA_MAX_LEN ? Number(process.env.LAYA_MAX_LEN) : null);
|
|
156
|
+
if (Number.isFinite(budget) && budget > 0) args.push('--max-len', String(budget));
|
|
157
|
+
if (apiKey) args.push('--api-key', apiKey);
|
|
158
|
+
return args;
|
|
159
|
+
}
|
|
160
|
+
|
|
139
161
|
/**
|
|
140
162
|
* Make sure a resolved binary is executable, and return it.
|
|
141
163
|
*
|
|
@@ -209,6 +231,7 @@ export class NativeServer {
|
|
|
209
231
|
this.port = options.port ?? 0;
|
|
210
232
|
this.apiKey = options.apiKey || null;
|
|
211
233
|
this.threads = options.threads ?? 0;
|
|
234
|
+
this.maxLen = options.maxLen ?? null;
|
|
212
235
|
this.proc = null;
|
|
213
236
|
this.url = null;
|
|
214
237
|
}
|
|
@@ -232,12 +255,14 @@ export class NativeServer {
|
|
|
232
255
|
? bundledLib + ':' + spawnEnv.LD_LIBRARY_PATH
|
|
233
256
|
: bundledLib;
|
|
234
257
|
}
|
|
235
|
-
const args =
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
const args = buildServerArgs({
|
|
259
|
+
modelDir,
|
|
260
|
+
host: this.host,
|
|
261
|
+
port: this.port,
|
|
262
|
+
threads: this.threads,
|
|
263
|
+
apiKey: this.apiKey,
|
|
264
|
+
maxLen: this.maxLen
|
|
265
|
+
});
|
|
241
266
|
if (this.apiKey) args.push('--api-key', this.apiKey);
|
|
242
267
|
return new Promise((resolve, reject) => {
|
|
243
268
|
const proc = spawn(bin, args, {
|