首次 Laya.load() 会从 Hugging Face 下载约 1.7 GB 权重并缓存到 ~/.cache/receptron-laya,耗时要把下载算进去;每个问题的选项须在 head_max_len(192)token 内,超出会抛错;choice 以列表给出的标签必须互不相同,重复会抛错。
laya
receptronMIT活跃维护在 Node.js/TypeScript 里通过 ONNX Runtime 本地跑 Laya 这个兼容 Jev 的开源 System-1 决策模型,做分类和推理,不用依赖云端。
Run Laya, the open-source Jev-compatible System-1 decision model, from Node.js / TypeScript via ONNX Runtime
npm install @receptron/laya
node -e 'import("@receptron/laya").then((m) => m.ensureBundle())'README 未说明 GPU 要求,默认使用 cpu 执行提供器;ONNX 权重约 1.7 GB(fp32),加载后约需 2 GB 内存,每批问题另需几百 MB。
在 Node.js/TypeScript 里用 ONNX Runtime 跑 Laya 决策模型,一次前向就给出带概率的结构化答案
在 Node.js/TypeScript 里通过 ONNX Runtime 本地跑 Laya 这个兼容 Jev 的开源 System-1 决策模型,做分类和推理,不用依赖云端。
核心能力
- 支持 choice、score、noul 三类带校准概率的问题
- 单次 systemOne 调用把所有问题合并成一次批量推理
- 用 ONNX Runtime 执行,运行时不需要 Python 和 PyTorch
- 输出与 Python 参考实现对齐到小数点后四位
- 提供 ensureBundle 预下载权重,便于构建或镜像预热
容易踩的坑
- v0.2.0最新正式版2026-10-17 · 2026-10-17 00:18 UTC
Calibration fix for large
choicequestions, closer parity with upstream laya, and a newanswer_confidencefield.Calibration
choicequestions with 11+ options are no longer ~99% confident. The upstream checkpoint shipschoice:11+ = 0.1006, which multiplied those logits by ~10 (#10). The published bundle (receptron/laya-onnx) now uses 0.945, the held-out fit for the English checkpoint (NandhaKishorM/laya#1245). The cached config refreshes on the nextLaya.load()with network access.- Temperatures are clamped to [0.5, 5] at load and again at the point of use, so a malformed bundle or a later edit to
laya.configcannot bypass it. Out-of-range or invalid values print a warning; an invalid per-option entry falls back to the question type's temperature. Bundles withouttemperature_by_options(fine-tuned exports) now load (#11, thanks @kuishou68). export/export_onnx.pyreplaces the known-badchoice:11+value, so a re-export reproduces the published config.
Confidences for 11+ option
choicequestions intentionally differ from the Pythonlayapackage, which clamps the same entry to 0.5.New
answer_confidenceon every answer (choice,score,noul):max(p), the probability of the reported answer. It is the quantity the temperatures are fitted for and means the same thing across question types, so use it for confidence gating.confidenceonchoice/scoreis still 1 − normalized entropy.
Fixes (parity with upstream laya)
- A single-option
choiceno longer crashes in the ONNX graph's TopK node. - Object
instructionsare serialized with Pythonjson.dumpsseparators, matching the reference tokenization. - A list (conversation) state that does not fit keeps its newest turns, as in training.
Behavior changes
- Duplicate or null labels in a list-form
choice(["a", "a"],["1", 1]) now throw instead of being silently merged into fewer options. - Long array states are truncated from the start instead of the end.
- Answers for object-valued
instructionsshift slightly (they now match Python).
Full changelog: https://github.com/receptron/laya/compare/v0.1.2...v0.2.0
- v0.1.12026-09-19 · 2026-09-19 15:37 UTC
查看更新说明
First public release of @receptron/laya on npm.
Laya.load()/systemOne()— Jev-compatiblechoice/score/noulanswers from Node.js via ONNX Runtime, matching the Python reference to four decimal places- ONNX bundle download from Hugging Face with local caching, or
modelDirfor a local export export/export_onnx.pyto build the bundle from the Hugging Face checkpoint
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
@receptron/laya
Run Laya — the open-source, Jev-compatible System 1 decision model by Convai Innovations — from Node.js / TypeScript.
Laya does not generate text. You hand it a state (a ticket, an email, a JSON object) and typed questions, and it returns every answer with calibrated probabilities in one forward pass:
choice— pick one option, with a probability per optionscore— an expected level on an ordered rubric, with the distributionnoul— a calibrated P(true) for a yes/no statement
This package runs the model with ONNX Runtime; PyTorch and Python are
not needed at runtime. The request/response shape is the same as the Python reference
implementation (RLAgent.system_one) and as TypeSafe Jev's system_one API, and the output
matches the Python implementation to four decimal places.
Install
npm install @receptron/laya
Node.js 20 or newer; CI runs on Node 20, 22 and 24 (Linux, macOS, Windows). The @types/node version
in devDependencies is only for type-checking this package and does not require Node 24.
The ONNX weights (about 1.7 GB, fp32) are downloaded from Hugging Face on first use and cached under
~/.cache/receptron-laya (override with LAYA_CACHE). Budget roughly 2 GB of RAM for the loaded model
plus a few hundred MB per batch of questions.
The first Laya.load() therefore includes that download, so time it after the cache is warm. To
fetch the weights ahead of time (in a build step or container image), call ensureBundle():
node -e 'import("@receptron/laya").then((m) => m.ensureBundle())'
Later loads only send a cheap freshness check and read from the cache (about 4 s on an Apple M4, measured in #13).
Usage
import { Laya } from "@receptron/laya";
const laya = await Laya.load();
const result = await laya.systemOne(
{ subject: "Refund not received", body: "I cancelled two weeks ago and still have no refund..." },
{
department: {
type: "choice",
instructions: "Which team should handle this ticket?",
criteria: { billing: "payments, refunds, invoices", support: "product help and bugs", sales: "new purchases" },
},
urgency: {
type: "score",
instructions: "How urgent is this ticket?",
criteria: ["not urgent", "somewhat urgent", "urgent", "critical"],
},
churn_risk: { type: "noul", instructions: "Is the customer likely to cancel or dispute?" },
},
);
result.answers.department.choice; // "billing"
result.answers.department.probabilities; // { billing: 0.9415, support: 0.031, sales: 0.0275 }
result.answers.urgency.score; // 1.3886 (expected level, 0..3)
result.answers.churn_risk.noul; // 0.0988 (P(true))
result.answers.department.answer_confidence; // 0.9415 (max probability, on every answer type)
result.usage.input_tokens; // 267
await laya.close();
The answer types follow the question types, so result.answers.department is a ChoiceAnswer
and result.answers.churn_risk a NoulAnswer without any casting.
To gate on confidence, use answer_confidence: the probability of the reported answer (max(p)),
which is the quantity the checkpoint's temperatures are fitted for and means the same thing for every
question type. confidence on choice / score answers is a different quantity (1 − normalized entropy).
Options
await Laya.load({
modelDir: "./onnx", // use a local export instead of downloading (see below)
repo: "receptron/laya-onnx", // Hugging Face repo that holds the ONNX bundle
subfolder: "multilingual", // a checkpoint variant inside that repo
revision: "main", // pin a commit hash for reproducible results; "main" follows the repo
cacheDir: "/var/cache/laya",
token: process.env.HF_TOKEN, // for private repos
onProgress: ({ file, received, total }) => {}, // download progress
executionProviders: ["cpu"], // onnxruntime-node execution providers
sessionOptions: { intraOpNumThreads: 4 },
});
Every question of one systemOne call is batched into a single run; a call with three questions
takes about 140 ms on an Apple-silicon CPU once the model is warm.
Exporting the ONNX bundle yourself
export/export_onnx.py turns the Hugging Face checkpoint (ModernBERT encoder + Laya's decision head)
into one ONNX graph and copies the tokenizer and calibration values next to it. You only need this
to build a bundle from a newer checkpoint or from a variant that is not published:
cd export
uv venv -p 3.12 .venv
uv pip install -p .venv/bin/python torch transformers safetensors onnx onnxscript onnxruntime huggingface_hub
.venv/bin/python -c "from huggingface_hub import snapshot_download; snapshot_download('convaiinnovations/laya', local_dir='model', allow_patterns=['model.safetensors','encoder/*','tokenizer/*','rl_agent_config.json','rl_common.py','rl_agent_api.py'])"
.venv/bin/python export_onnx.py model ../onnx # prints the max logit difference vs. PyTorch (≈1e-5)
Then Laya.load({ modelDir: "./onnx" }). The bundle is the five files listed in BUNDLE_FILES:
laya.onnx, laya.onnx.data, laya_config.json, tokenizer/tokenizer.json, tokenizer/tokenizer_config.json.
Limits
- Each question's options must fit in
head_max_len(192) tokens;systemOnethrows otherwise. Fewer than about 20 options perchoicequestion is the model's own recommendation. - The state is truncated to
max_len(512 tokens for the English checkpoint) after the question header. A string or object state keeps its start; a list state (a conversation) keeps its newest end, as in training. choicelabels given as a list must be distinct; a repeated label throws instead of being merged.- Temperatures are clamped to [0.5, 5]. The published bundle uses 0.945 for
choicequestions with 11 or more options, where the upstream checkpoint ships 0.1006 (clamped to 0.5 by the Pythonlayapackage), so confidences for those questions differ from upstream by design. - A JSON state is serialized like Python's
json.dumps(ensure_ascii=False)so that tokens match the reference implementation; non-integer numbers may format differently between JS and Python.
Development
yarn install
yarn test # unit tests; the model test runs when ./onnx holds a bundle (or LAYA_MODEL_DIR)
yarn typecheck
yarn build
LAYA_MODEL_DIR=./onnx yarn example
License
MIT. The Laya model weights are published by Convai Innovations under Apache 2.0.