decidekit 0.1.0 → 0.1.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/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 (2026-11-02)
4
+
5
+ ### Models
6
+
7
+ - The default model id is `debuffjp/decide-small` (Decide Small). Before 0.1.1 it was `Xenova/bge-small-en-v1.5`. The
8
+ weights are identical, so decisions don't change. For other languages the id is `debuffjp/decide-multilingual`
9
+ (Decide Multilingual), identical to `Xenova/multilingual-e5-small`.
10
+ - The old ids and heads recording them keep working. They resolve to the new ids, so `d.model` returns the new id. A
11
+ head trained on 0.1.1 records the new id, so a 0.1.0 decider needs that id passed as `model` to use it.
12
+ - Downloads of both models are pinned to a revision.
13
+ - New option `modelHost` serves model files from `<modelHost>/<model>/` instead of Hugging Face. It applies to the
14
+ whole page or process.
15
+
16
+ ### Docs
17
+
18
+ - Label names count as 1 example in every doc, and the advice to turn them off with bge-small is gone: it applied to the
19
+ old weight of 3.
20
+ - Calibration, out-of-scope and zero-shot numbers are now bge-small's or labelled with the model they were measured on.
21
+ - `decide()` documents that a described label scores by the better of its name and description.
22
+ - The API reference covers `decidekit/typesafe`.
23
+
3
24
  ## 0.1.0 (2026-11-02)
4
25
 
5
26
  The first release. Numbers are on public test splits unless noted; the scripts that produced them are in `eval/`.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/decidekit?color=cffb2d&labelColor=11140d)](https://www.npmjs.com/package/decidekit) [![ci](https://github.com/debuffjp/decidekit/actions/workflows/ci.yml/badge.svg)](https://github.com/debuffjp/decidekit/actions/workflows/ci.yml) [![MIT](https://img.shields.io/badge/licence-MIT-cffb2d?labelColor=11140d)](LICENSE) [![Discord](https://img.shields.io/badge/discord-debuff-a78bfa?labelColor=11140d)](https://discord.gg/jshbDHSpCY)
4
4
 
5
- **[Try it in your browser](https://debuff.jp/decidekit/playground/)** · [Watch it learn from an LLM](https://debuff.jp/decidekit/lanes/) · [Launch post](https://debuff.jp/blog/introducing-decidekit) · [Research: when the LLM is wrong](https://debuff.jp/research/learning-from-a-noisy-llm)
5
+ **[Try it in your browser](https://debuff.jp/decide/playground/)** · [Watch it learn from an LLM](https://debuff.jp/decide/lanes/) · [Launch post](https://debuff.jp/blog/introducing-decide) · [Research: when the LLM is wrong](https://debuff.jp/research/learning-from-a-noisy-llm)
6
6
 
7
7
  A decision model for the browser and Node: it picks one of your labels for a piece of text, with confidence you can trust, and learns from an LLM when it isn't sure. Your labels are the type. No server, no API key.
8
8
 
@@ -29,8 +29,8 @@ await d.decide(text, ["question", "bug_report", "praise"]);
29
29
  await d.decide(text, { refund: "wants their money back", bug: "something is broken" });
30
30
  ```
31
31
 
32
- Zero-shot is for trying things out: 61% on BANKING77 and 70% on CLINC150 (dev splits), against 90-94% trained.
33
- Its confidence came out close to calibrated on those datasets (error 0.005-0.045 with 2 to 150 options), but check
32
+ Zero-shot is for trying things out: 66% on BANKING77 and 73% on CLINC150 (dev splits), against 90-94% trained.
33
+ Its confidence came out close to calibrated on those datasets (error 0.03-0.04), but check
34
34
  it on your own labels before relying on a threshold.
35
35
 
36
36
  ## Train a head
@@ -146,20 +146,24 @@ That lets wasm use several threads: in our runs with all-MiniLM-L6-v2 it cut p95
146
146
 
147
147
  Download on first visit: the model (34 MB) and onnxruntime's wasm (23.6 MB, 5.8 MB gzipped), both cached after.
148
148
 
149
- ## Models
150
-
151
- decidekit runs the Decide models, each a sentence encoder plus a calibrated head:
152
-
153
- | Model | Encoder | Download | Reads |
154
- |---|---|---|---|
155
- | Decide Small (default) | `Xenova/bge-small-en-v1.5` | 34 MB | English |
156
- | Decide Multilingual | `Xenova/multilingual-e5-small` | 118 MB | 76% on Japanese and Chinese intents |
157
-
158
- Pass the encoder id as `model` to `createDecider()`.
159
-
149
+ ## Models
150
+
151
+ decidekit runs the Decide models, each a sentence encoder plus a calibrated head:
152
+
153
+ | Model | Id | Download | Reads | Upstream |
154
+ |---|---|---|---|---|
155
+ | Decide Small (default) | [`debuffjp/decide-small`](https://huggingface.co/debuffjp/decide-small) | 34 MB | English | BAAI's [bge-small-en-v1.5](https://huggingface.co/BAAI/bge-small-en-v1.5), identical to `Xenova/bge-small-en-v1.5` |
156
+ | Decide Multilingual | [`debuffjp/decide-multilingual`](https://huggingface.co/debuffjp/decide-multilingual) | about 118 MB | 76% on Japanese and Chinese intents | intfloat's [multilingual-e5-small](https://huggingface.co/intfloat/multilingual-e5-small), identical to `Xenova/multilingual-e5-small` |
157
+
158
+ Pass the id as `model` to `createDecider()`. The files download from Hugging Face on first use, pinned to a revision, and
159
+ are then cached. To serve them yourself, pass `modelHost`: see [self-hosting](docs/guide-browser.md#host-the-model-files-yourself).
160
+
161
+ Before 0.1.1 the default id was `Xenova/bge-small-en-v1.5`. The old ids and heads recording them keep working: they
162
+ resolve to the new ids, so `d.model` returns the new id.
163
+
160
164
  ## Numbers
161
165
 
162
- bge-small-en-v1.5 (the default) at q8, Ryzen 7 260 laptop, Node 24 and headless Chrome 156, 2026-11-01. Accuracy
166
+ Decide Small (bge-small-en-v1.5, the default) at q8, without label names, Ryzen 7 260 laptop, Node 24 and headless Chrome 156, 2026-11-01. Accuracy
163
167
  and calibration error (ECE, lower is better) on each dataset's test split, mean of 3 seeds (one run for "all"):
164
168
  [eval/results-bge-small.json](eval/results-bge-small.json).
165
169
 
@@ -196,16 +200,16 @@ in-scope examples only, with `minConfidence` 0.93 picked on the validation split
196
200
 
197
201
  **The smaller model.** `createDecider({ model: "Xenova/all-MiniLM-L6-v2" })` (23.7 MB, the pre-release default) is
198
202
  about 1.8x faster per decision in Chrome and 3 to 5 points less accurate with few examples, about the same with all
199
- the data. Pass it to keep using heads trained on it. Before this default changed, the README suggested
200
- `{ labelNames: false }` with bge-small: label names slightly hurt it on the dev splits.
203
+ the data. Pass it to keep using heads trained on it. Before 0.1.0 a label name counted as 3 examples and the README
204
+ suggested `{ labelNames: false }` with bge-small. It now counts as 1, which helps.
201
205
 
202
206
  Reproduce: `npm run eval:suite` (Node), `npm run bench` then `npm run bench:run` (Chrome).
203
207
 
204
208
  ## Playground
205
209
 
206
- Try it at [debuff.jp/decidekit](https://debuff.jp/decidekit/). To run it locally: `npm run playground`, then serve `playground/dist` cross-origin isolated (`node bench/serve.mjs` style headers).
210
+ Try it at [debuff.jp/decide](https://debuff.jp/decide/). To run it locally: `npm run playground`, then serve `playground/dist` cross-origin isolated (`node bench/serve.mjs` style headers).
207
211
  Try zero-shot labels, the ready-made heads, a head trained in the page and "none of these".
208
- [Two lanes](https://debuff.jp/decidekit/lanes/) shows `withFallback` live: decidekit starts from twelve label names,
212
+ [Two lanes](https://debuff.jp/decide/lanes/) shows `withFallback` live: decidekit starts from twelve label names,
209
213
  asks a small LLM when it's unsure, learns from each answer, and replays the first 20 messages at the end.
210
214
 
211
215
  ## Ready-made heads
@@ -231,14 +235,9 @@ and offline apps, and text that can't leave the device. Details, code and poor f
231
235
  | MediaPipe text classifier | yes | no, offline | no | no |
232
236
  | LLM structured output (AI SDK `Output.choice`) | no, API | n/a | yes | no |
233
237
 
234
- jimothy reports 92.4% on BANKING77, above our 90.7%. Pick it if you train in Node and want the higher number. Pick
235
- decidekit if training has to happen in the page, from the user's own data.
236
-
237
- [system-one](https://github.com/lukeramsden/system-one) is a different kind of tool: a typed client for hosted System One models (Jev and Laya). It runs no model itself.
238
-
239
238
  ## Docs
240
239
 
241
- [docs/README.md](docs/README.md) indexes the guides (training, confidence and thresholds, the browser), the
240
+ [debuff.jp/decide/docs](https://debuff.jp/decide/docs/) (also in [docs/README.md](docs/README.md)) indexes the guides (training, confidence and thresholds, the browser), the
242
241
  API reference, how it works and the FAQ.
243
242
 
244
243
  ## Community
@@ -249,6 +248,6 @@ issue template for them. See [CONTRIBUTING.md](CONTRIBUTING.md).
249
248
 
250
249
  ## Status
251
250
 
252
- 0.1. English with the default model, other languages with `multilingual-e5-small` ([FAQ](docs/faq.md#which-languages-does-it-support)); short texts (about a paragraph). Not done yet: WebGPU in the worker and fine-tuning the encoder (the likely route past about 91%).
251
+ 0.1. English with the default model, other languages with Decide Multilingual ([FAQ](docs/faq.md#which-languages-does-it-support)); short texts (about a paragraph). Not done yet: WebGPU in the worker and fine-tuning the encoder (the likely route past about 91%).
253
252
 
254
253
  Made by the [debuff](https://debuff.jp) team. MIT.
package/dist/index.d.ts CHANGED
@@ -72,13 +72,19 @@ export interface TrainOptions<L extends string = string> {
72
72
  }
73
73
  export interface DeciderOptions {
74
74
  /**
75
- * A Hugging Face sentence-embedding model with ONNX weights. Default `Xenova/bge-small-en-v1.5` (34 MB at q8).
75
+ * A Hugging Face sentence-embedding model with ONNX weights. Default `debuffjp/decide-small` (Decide Small, 34 MB at q8).
76
+ * Before 0.1.1 the default id was `Xenova/bge-small-en-v1.5`, the same weights; that id and heads recording it still work.
76
77
  * Pre-release builds defaulted to `Xenova/all-MiniLM-L6-v2` (23 MB): smaller and 1.6x faster, 3 to 5 points less
77
78
  * accurate. Pass it to keep using heads trained on it.
78
79
  */
79
80
  model?: string;
80
81
  /** Defaults to transformers.js' choice: wasm in browsers, cpu in Node. */
81
82
  device?: "wasm" | "webgpu" | "cpu";
83
+ /**
84
+ * Serve model files from your own host instead of Hugging Face, as `<modelHost>/<model>/`, for example
85
+ * `https://cdn.example.com/models/debuffjp/decide-small/onnx/model_quantized.onnx`. Applies to the whole page or process.
86
+ */
87
+ modelHost?: string;
82
88
  /** Weight precision. Default `q8`. */
83
89
  dtype?: "q8" | "fp16" | "fp32";
84
90
  /** Called as model files download (first load only; later loads come from cache). */
@@ -98,7 +104,7 @@ export interface DeciderOptions {
98
104
  * `train()`. In a browser, prefer `createWorkerDecider()` from `decidekit/browser`: the encoder blocks the thread
99
105
  * it runs on.
100
106
  */
101
- export declare function createDecider({ model, device, dtype, temperature, onProgress }?: DeciderOptions): Promise<{
107
+ export declare function createDecider({ model: requested, device, dtype, temperature, onProgress, modelHost }?: DeciderOptions): Promise<{
102
108
  model: string;
103
109
  decide: {
104
110
  <const L extends string>(text: string, using: Choices<L>, options?: {
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // decide(text, options): typed, calibrated decisions on device. Zero-shot compares the text with each option's
2
2
  // embedding; a trained head (a softmax layer over the same embeddings, 156 KB for 77 labels) is far more
3
3
  // accurate. Both return the option literals as the choice type.
4
- import { pipeline } from "@huggingface/transformers";
4
+ import { env, pipeline } from "@huggingface/transformers";
5
5
  import { fitHead, logits, softmax } from "./head.js";
6
6
  function isHead(c) {
7
7
  if (Array.isArray(c) || !("weights" in c))
@@ -23,9 +23,15 @@ const LABEL_NAME_WEIGHT = 1;
23
23
  // multilingual-e5-small's similarities sit close together, so it needs far lower temperatures: on MASSIVE in English
24
24
  // and Japanese, 0.0105 for label names (calibration error 0.03 to 0.04; descriptions not yet measured) and a floor of
25
25
  // 0.4 (eval/e5-settings.ts). It also expects "query: " before every text.
26
- const SMALL_HEAD_FLOOR = { "Xenova/bge-small-en-v1.5": 0.5, "Xenova/multilingual-e5-small": 0.4 };
27
- const TEMPERATURES = { "Xenova/bge-small-en-v1.5": [0.03, 0.025], "Xenova/all-MiniLM-L6-v2": [0.05, 0.05], "Xenova/multilingual-e5-small": [0.0105, 0.0105] };
28
- const PREFIX = { "Xenova/multilingual-e5-small": "query: " };
26
+ // The Decide models are unmodified copies of these upstream ids, so the old ids and heads trained on them still work.
27
+ const SMALL = "debuffjp/decide-small", MULTILINGUAL = "debuffjp/decide-multilingual";
28
+ const SAME_MODEL = { "Xenova/bge-small-en-v1.5": SMALL, "Xenova/multilingual-e5-small": MULTILINGUAL };
29
+ const canonical = (model) => SAME_MODEL[model] ?? model;
30
+ // Pinned so a later change on Hugging Face can't change what an installed version decides.
31
+ const REVISIONS = { [SMALL]: "188d690f86190863e3b4e44e29c88348b10230c4", [MULTILINGUAL]: "a3e353d26d846b2163db8acad65ef3d43893592e" };
32
+ const SMALL_HEAD_FLOOR = { [SMALL]: 0.5, [MULTILINGUAL]: 0.4 };
33
+ const TEMPERATURES = { [SMALL]: [0.03, 0.025], "Xenova/all-MiniLM-L6-v2": [0.05, 0.05], [MULTILINGUAL]: [0.0105, 0.0105] };
34
+ const PREFIX = { [MULTILINGUAL]: "query: " };
29
35
  function toDecision(labels, p) {
30
36
  let best = 0;
31
37
  for (let i = 1; i < p.length; i++)
@@ -39,9 +45,14 @@ function toDecision(labels, p) {
39
45
  * `train()`. In a browser, prefer `createWorkerDecider()` from `decidekit/browser`: the encoder blocks the thread
40
46
  * it runs on.
41
47
  */
42
- export async function createDecider({ model = "Xenova/bge-small-en-v1.5", device, dtype = "q8", temperature, onProgress } = {}) {
48
+ export async function createDecider({ model: requested = SMALL, device, dtype = "q8", temperature, onProgress, modelHost } = {}) {
49
+ const model = canonical(requested);
50
+ if (modelHost) {
51
+ env.remoteHost = modelHost.endsWith("/") ? modelHost : modelHost + "/";
52
+ env.remotePathTemplate = "{model}/";
53
+ }
43
54
  const embed = await pipeline("feature-extraction", model, {
44
- device, dtype,
55
+ device, dtype, revision: REVISIONS[model],
45
56
  progress_callback: onProgress && ((p) => p.status === "progress" && onProgress({ file: p.file, loaded: p.loaded, total: p.total })),
46
57
  });
47
58
  const optionCache = new Map(), prefix = PREFIX[model] ?? "";
@@ -58,7 +69,7 @@ export async function createDecider({ model = "Xenova/bge-small-en-v1.5", device
58
69
  async function score(q, using, minConfidence) {
59
70
  let d;
60
71
  if (isHead(using)) {
61
- if (using.model !== model)
72
+ if (canonical(using.model) !== model)
62
73
  throw new Error(`decidekit: this head was trained on ${using.model}, but the decider uses ${model}. Pass { model: "${using.model}" } to createDecider.`);
63
74
  d = toDecision(using.labels, softmax(logits(using, q), using.temperature));
64
75
  }
package/package.json CHANGED
@@ -1,93 +1,93 @@
1
- {
2
- "name": "decidekit",
3
- "version": "0.1.0",
4
- "description": "A System One model in the browser: typed, calibrated text decisions on the user's device. Learns from an LLM when it isn't sure.",
5
- "license": "MIT",
6
- "repository": "github:debuffjp/decidekit",
7
- "keywords": [
8
- "decision model",
9
- "system one",
10
- "classification",
11
- "intent",
12
- "zero-shot",
13
- "calibration",
14
- "llm",
15
- "embeddings",
16
- "browser",
17
- "typescript",
18
- "text classification",
19
- "transformers.js",
20
- "on-device",
21
- "llm fallback"
22
- ],
23
- "type": "module",
24
- "exports": {
25
- ".": {
26
- "types": "./dist/index.d.ts",
27
- "default": "./dist/index.js"
28
- },
29
- "./browser": {
30
- "types": "./dist/browser.d.ts",
31
- "default": "./dist/browser.js"
32
- },
33
- "./react": {
34
- "types": "./dist/react.d.ts",
35
- "default": "./dist/react.js"
36
- },
37
- "./switch": {
38
- "types": "./dist/switch.d.ts",
39
- "default": "./dist/switch.js"
40
- },
41
- "./typesafe": {
42
- "types": "./dist/typesafe.d.ts",
43
- "default": "./dist/typesafe.js"
44
- }
45
- },
46
- "files": [
47
- "dist",
48
- "CHANGELOG.md"
49
- ],
50
- "scripts": {
51
- "build": "tsc -p tsconfig.build.json",
52
- "prepack": "npm run build",
53
- "typecheck": "tsc --noEmit",
54
- "eval": "node --experimental-strip-types eval/banking77.ts",
55
- "bench": "esbuild src/worker.ts src/browser.ts --bundle --format=esm --platform=browser --outdir=bench --alias:onnxruntime-web/webgpu=onnxruntime-web/wasm && node bench/serve.mjs",
56
- "playground": "node playground/build.mjs",
57
- "heads": "node --experimental-strip-types heads/build.ts",
58
- "bench:run": "node bench/run.mjs",
59
- "eval:suite": "node --experimental-strip-types eval/suite.ts",
60
- "eval:hill": "node --experimental-strip-types eval/hill.ts",
61
- "test": "node --test --experimental-strip-types \"test/*.test.ts\"",
62
- "lint:prose": "node scripts/lint-prose.mjs",
63
- "smoke": "node scripts/smoke.mjs"
64
- },
65
- "dependencies": {
66
- "@huggingface/transformers": "4.2.0"
67
- },
68
- "devDependencies": {
69
- "@types/node": "22.15.3",
70
- "@types/react": "19.3.0",
71
- "esbuild": "0.25.10",
72
- "playwright-core": "1.64.1",
73
- "react": "19.3.1",
74
- "spryte": "0.11.0",
75
- "typescript": "5.9.3"
76
- },
77
- "engines": {
78
- "node": ">=22.18"
79
- },
80
- "homepage": "https://debuff.jp/decidekit/",
81
- "bugs": "https://github.com/debuffjp/decidekit/issues",
82
- "bin": {
83
- "decidekit": "./dist/cli.js"
84
- },
85
- "peerDependencies": {
86
- "react": ">=18"
87
- },
88
- "peerDependenciesMeta": {
89
- "react": {
90
- "optional": true
91
- }
92
- }
93
- }
1
+ {
2
+ "name": "decidekit",
3
+ "version": "0.1.1",
4
+ "description": "A System One model in the browser: typed, calibrated text decisions on the user's device. Learns from an LLM when it isn't sure.",
5
+ "license": "MIT",
6
+ "repository": "github:debuffjp/decidekit",
7
+ "keywords": [
8
+ "decision model",
9
+ "system one",
10
+ "classification",
11
+ "intent",
12
+ "zero-shot",
13
+ "calibration",
14
+ "llm",
15
+ "embeddings",
16
+ "browser",
17
+ "typescript",
18
+ "text classification",
19
+ "transformers.js",
20
+ "on-device",
21
+ "llm fallback"
22
+ ],
23
+ "type": "module",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./browser": {
30
+ "types": "./dist/browser.d.ts",
31
+ "default": "./dist/browser.js"
32
+ },
33
+ "./react": {
34
+ "types": "./dist/react.d.ts",
35
+ "default": "./dist/react.js"
36
+ },
37
+ "./switch": {
38
+ "types": "./dist/switch.d.ts",
39
+ "default": "./dist/switch.js"
40
+ },
41
+ "./typesafe": {
42
+ "types": "./dist/typesafe.d.ts",
43
+ "default": "./dist/typesafe.js"
44
+ }
45
+ },
46
+ "files": [
47
+ "dist",
48
+ "CHANGELOG.md"
49
+ ],
50
+ "scripts": {
51
+ "build": "tsc -p tsconfig.build.json",
52
+ "prepack": "npm run build",
53
+ "typecheck": "tsc --noEmit",
54
+ "eval": "node --experimental-strip-types eval/banking77.ts",
55
+ "bench": "esbuild src/worker.ts src/browser.ts --bundle --format=esm --platform=browser --outdir=bench --alias:onnxruntime-web/webgpu=onnxruntime-web/wasm && node bench/serve.mjs",
56
+ "playground": "node playground/build.mjs",
57
+ "heads": "node --experimental-strip-types heads/build.ts",
58
+ "bench:run": "node bench/run.mjs",
59
+ "eval:suite": "node --experimental-strip-types eval/suite.ts",
60
+ "eval:hill": "node --experimental-strip-types eval/hill.ts",
61
+ "test": "node --test --experimental-strip-types \"test/*.test.ts\"",
62
+ "lint:prose": "node scripts/lint-prose.mjs",
63
+ "smoke": "node scripts/smoke.mjs"
64
+ },
65
+ "dependencies": {
66
+ "@huggingface/transformers": "4.2.0"
67
+ },
68
+ "devDependencies": {
69
+ "@types/node": "22.15.3",
70
+ "@types/react": "19.3.0",
71
+ "esbuild": "0.25.10",
72
+ "playwright-core": "1.64.1",
73
+ "react": "19.3.1",
74
+ "spryte": "0.11.0",
75
+ "typescript": "5.9.3"
76
+ },
77
+ "engines": {
78
+ "node": ">=22.18"
79
+ },
80
+ "homepage": "https://debuff.jp/decide/",
81
+ "bugs": "https://github.com/debuffjp/decidekit/issues",
82
+ "bin": {
83
+ "decidekit": "./dist/cli.js"
84
+ },
85
+ "peerDependencies": {
86
+ "react": ">=18"
87
+ },
88
+ "peerDependenciesMeta": {
89
+ "react": {
90
+ "optional": true
91
+ }
92
+ }
93
+ }