jev-web 0.8.0 → 0.9.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 CHANGED
@@ -10,11 +10,12 @@ calibrated probability distribution per question comes back from a **single
10
10
  forward pass**. No server, no API keys, no network calls after the weights are
11
11
  cached.
12
12
 
13
- Seven model families ship today — open-jev (fused DeBERTa graph), Laya
13
+ Nine model families ship today — open-jev (fused DeBERTa graph), Laya
14
14
  (ModernBERT encoder + typed head), Strands Decider 2B (Qwen torso + pointer
15
15
  head), Bekko System One v0 (Ettin cross-encoder), Decision 2.0 (Qwen3 +
16
- candidate head), Kev (Qwen3 + pointer head), and Vega 0.8B (frozen Qwen3.5
17
- torso + latent decision-physics engine) — behind one registry:
16
+ candidate head), Kev (Qwen3 + pointer head), Vega 0.8B (frozen Qwen3.5
17
+ torso + latent decision-physics engine), clef (Qwen3 torso + joint schema
18
+ head), and Decision 3.0 (d3 code readout) — behind one registry:
18
19
  `createDecisionRuntime({ family, fallback })`.
19
20
 
20
21
  This is a generic runtime, not a classifier for any particular domain. You bring
@@ -28,10 +29,20 @@ you give it.
28
29
  An interactive playground that runs this exact package: type a state, add typed
29
30
  questions, and watch the model and a hand-written keyword baseline answer
30
31
  side by side. It also counts your actual outbound requests, so the
31
- "offline / private / no server" claim is checkable rather than asserted.
32
+ "offline / private / no server" claim is checkable rather than asserted. The
33
+ model selector loads **any of the nine registered families** — open-jev by
34
+ default, plus Bekko, Kev, Laya, Decision 2.0/3.0, Vega 0.8B (fused or the
35
+ 8-bit split export), clef and Strands Decider — and `?model=<family>`
36
+ deep-links one.
32
37
 
33
38
  ![jev-web demo: typed decisions vs raw text parsing](docs/demo.gif)
34
39
 
40
+ The picker is part of the demo: every family answers through the same
41
+ `decide()` contract, and the pass counter reports each family's real cost
42
+ (split Vega reads the state once per question, the others batch).
43
+
44
+ ![the demo's model picker answering with Bekko](docs/demo-model-picker.png)
45
+
35
46
  <sub>The frames are captured from the live demo at
36
47
  [warsang.github.io/jev-web](https://warsang.github.io/jev-web/) — site → typed
37
48
  decisions with latency → the negation case where the rules and the model
@@ -60,8 +71,9 @@ answers[0].confidence; // 0.92
60
71
 
61
72
  That is the whole integration. The first call downloads the weights and the
62
73
  browser caches them; every later call — and every later visit, including
63
- offline — is local. **Zero dependencies**: `@huggingface/transformers` is an
64
- optional peer, imported lazily.
74
+ offline — is local. **Zero bundled dependencies**: `onnxruntime-web` is a peer
75
+ the package manager installs, `@huggingface/transformers` is an optional peer
76
+ imported lazily, and hosts can inject either one.
65
77
 
66
78
  ## What comes back
67
79
 
@@ -149,9 +161,11 @@ The demo also tracks outbound requests through the Performance API: you will see
149
161
  npm i jev-web @huggingface/transformers
150
162
  ```
151
163
 
152
- `@huggingface/transformers` is an optional peer dependency (it is imported
153
- lazily). Hosts that already ship their own copy can inject it via
154
- `createDecider({ transformers })`.
164
+ `@huggingface/transformers` is an optional peer (imported lazily); hosts that
165
+ already ship their own copy can inject it via `createDecider({ transformers })`.
166
+ `onnxruntime-web` is a regular peer, so `npm i jev-web` installs it; hosts with
167
+ a custom runtime (local files, an older ORT, a worker) inject it via
168
+ `createDecider({ ort })`.
155
169
 
156
170
  ## Use
157
171
 
@@ -227,7 +241,7 @@ tries the requested family and then any fallbacks, returning the shared contract
227
241
  ```js
228
242
  import { createDecisionRuntime, listDecisionFamilies } from "jev-web";
229
243
 
230
- listDecisionFamilies(); // ["open-jev", "laya", "strands-decider", "bekko", "decision2", "kev", "vega"]
244
+ listDecisionFamilies(); // ["open-jev", "laya", "strands-decider", "bekko", "decision2", "kev", "vega", "clef", "decision3"]
231
245
  const runtime = await createDecisionRuntime({
232
246
  family: "strands-decider",
233
247
  fallback: ["laya", "open-jev"],
@@ -502,21 +516,23 @@ and download size. Lower-level helpers are exported for custom runtimes:
502
516
 
503
517
  `createVegaDecider(options)` runs [Vega 0.8B](https://huggingface.co/nandakishorm/vega-08b-public-intents)
504
518
  (by the Laya author): the language model is frozen and never trained — a 0.8B
505
- Qwen3.5 torso is read once (layers 13 and 19, span means pooled in-graph) and
506
- a 14M-parameter physics engine on top does the deciding. Each question drops a
507
- particle into a latent space whose landscape holds one Gaussian well per
508
- option; a damped symplectic integrator with a selective-SSM thermostat runs
509
- up to 12 steps (settled particles freeze), and the Boltzmann occupancy of the
510
- wells is the answer distribution:
519
+ Qwen3.5 torso is read once (layers 13 and 19) and a 14M-parameter physics
520
+ engine on top does the deciding. Each question drops a particle into a latent
521
+ space whose landscape holds one Gaussian well per option; a damped symplectic
522
+ integrator with a selective-SSM thermostat runs up to 12 steps (settled
523
+ particles freeze), and the Boltzmann occupancy of the wells is the answer
524
+ distribution:
511
525
 
512
526
  ```js
513
527
  import { createVegaDecider } from "jev-web";
514
528
 
515
529
  const decider = await createVegaDecider({
516
- // defaults to warsang/vega-08b-web (revision pinned once the export repo
517
- // lands — export/vega/05_upload.py prints the sha for VEGA_DEFAULT_REVISION)
530
+ // variant "fp16" (default): warsang/vega-08b-web, fused torso + engine, 1.7 GB
518
531
  onProgress: (p) => console.log(p.phase, p.loaded, p.total),
519
532
  });
533
+ const q8 = await createVegaDecider({ variant: "q8" });
534
+ // nvkudva/vega-onnx-q8: 8-bit embed + cut decoder + fp32 engine, 840 MB,
535
+ // WebAssembly only (its MatMulNBits graphs have no WebGPU kernel)
520
536
  const { answers } = await decider.decide(stateText, questions);
521
537
  ```
522
538
 
@@ -525,16 +541,22 @@ Protocol implemented here (mirrors the reference `vega_common.py` / `vega_api.py
525
541
  - prompt `[BOS] Observation:\n<state>\n\nMeasurement (choice|score|boolean): <instructions>\nPossible outcomes:\n* <option> …\nOutcome:` — the state comes first so its span never sees the question
526
542
  (causal attention), and short states get a query-first prefix so the frozen
527
543
  model reads knowing what is asked;
528
- - two ONNX graphs: the frozen torso emits per-segment span means for the
544
+ - perception: the torso emits per-segment span means for the
529
545
  state/question/final-token slots, every option, and 16 equal state chunks
530
- (the engine's memory reader) from the one causal read; the engine graph
531
- (unrolled integrator; the settle-freeze math is identical to the
532
- reference's early exit) emits raw logits plus the particle physics;
546
+ (the engine's memory reader) from the one causal read — pooled in-graph in
547
+ the fused variant, in JS over the decoder's layer-13/19 hidden states in
548
+ the split one; the engine graph (unrolled integrator; the settle-freeze
549
+ math is identical to the reference's early exit) emits raw logits plus the
550
+ particle physics;
533
551
  - calibration is **thermal and checkpoint-driven**: `vega_config.json`'s
534
552
  `calibration.theta` maps the particle's physical state (residual kinetic
535
553
  energy, distance to the nearest well, steps used, option count) to a
536
554
  temperature per question type; `decider.info.calibrationSource` names the
537
- file used;
555
+ file used. The split export's `vega_web.json` also carries the two gated
556
+ task adapters (`public_intents`, `typed_decisions`): the engine's per-
557
+ question gates pick the adapter whose `calibration` block (theta, conformal,
558
+ abstain, unbound) calibrates that answer, and the answers gain `adapter`
559
+ and `gate`;
538
560
  - answers carry the full distribution plus Vega's reliability machinery:
539
561
  a split-conformal `predictionSet`, an `abstain` flag at the chosen risk
540
562
  (`alpha` conformal level, `risk` abstention level, both per question type),
@@ -544,19 +566,143 @@ Protocol implemented here (mirrors the reference `vega_common.py` / `vega_api.py
544
566
  `choice` accepts `criteria` as `{label: description}`, and `score` uses the
545
567
  option texts as levels.
546
568
 
547
- Caveats: the checkpoint's gated task adapters are not shipped in v1 — the
548
- runtime uses the base engine, which is the source repo's default; the torso
549
- ships as the exact fp16 graph (~1.5 GB, the weight class of the strands 2B q8
550
- default) because q8 on this graph drifts on the GatedDeltaNet delta rule,
551
- with the measured q8 experiment available as an opt-in file; and the prompt
552
- window is the trained 73,728 tokens, so pass `maxLen` to cap a browser's
553
- read. The browser export repo (`warsang/vega-08b-web`, ~1.7 GB) is uploaded
554
- by `export/vega/05_upload.py`; the revision lands in `VEGA_DEFAULT_REVISION`.
569
+ Variants (`variant`, or explicit `model`/`revision`/`format`):
570
+
571
+ | variant | export | graphs | size |
572
+ |---|---|---|---|
573
+ | `fp16` (default) | `warsang/vega-08b-web` | fused fp16 torso + fp32 engine, span means pooled in-graph | ~1.7 GB |
574
+ | `q8` | `nvkudva/vega-onnx-q8` (`v2/`) | 8-bit `embed_q8` + cut `decoder_q8` (layers 0–19, text M-RoPE positions, zeroed `past_*` state) + fp32 `engine` with both adapters; JS pools | ~840 MB |
575
+
576
+ Caveats: the fused export's gated task adapters are not shipped in its v1 — it
577
+ uses the base engine, the source repo's default; and that torso ships as the
578
+ exact fp16 graph because q8 on the fused graph drifts on the GatedDeltaNet
579
+ delta rule. The split export is text-only here (its vision encoder is not
580
+ wired in), runs one decoder read per question, is refused — `RangeError` —
581
+ rather than truncated when a state does not fit `maxLen`, and its checkpoints
582
+ were verified end-to-end against the reference (`export/vega/06_verify_q8_export.py`:
583
+ JS-vs-reference pooling exact, engine graph vs PyTorch 1.4e-6, argmax 3/3
584
+ against the fp16 backbone). Both keep the trained 73,728-token window, so pass
585
+ `maxLen` to cap a browser's read. The fused export repo (`warsang/vega-08b-web`,
586
+ ~1.7 GB) is uploaded by `export/vega/05_upload.py`; the revision lands in
587
+ `VEGA_DEFAULT_REVISION`.
555
588
 
556
589
  Lower-level helpers are exported for custom runtimes:
557
590
  `buildVegaPrompt`, `packVegaBatch`, `packVegaChunks`, `vegaQuestionDef`,
558
591
  `vegaOptionTexts`, `vegaThermalFeatures`, `vegaThermalProbabilities`,
559
- `vegaAnswers`, `vegaOptionLabels`.
592
+ `vegaAnswers`, `vegaOptionLabels`, `vegaJsonDumps`, `vegaRoute`,
593
+ `vegaTextPositions`, `emptyVegaPast`, `poolVegaHidden`.
594
+
595
+ ## clef family (Qwen3 torso + joint schema head)
596
+
597
+ `createClefDecider(options)` runs [clef-text-0.6b](https://huggingface.co/FluidInference/clef-text-0.6b)
598
+ (Apache-2.0): a Qwen3-0.6B torso (LoRA merged) with Cloudflare's **joint schema
599
+ head** — the whole record (state plus every question's schema) becomes one
600
+ prompt, and the head decides every field in one joint pass. It is distilled
601
+ from Cloudflare's clef-flash 9B (79.1 vs 88.2 mean across 12 held-out
602
+ benchmarks) and ships Cloudflare's unchanged reference module
603
+ (`joint_schema_model.py`).
604
+
605
+ ```js
606
+ import { createClefDecider } from "jev-web";
607
+
608
+ const decider = await createClefDecider({
609
+ // defaults to warsang/clef-text-0.6b-web @ pinned revision
610
+ onProgress: (p) => console.log(p.phase, p.loaded, p.total),
611
+ });
612
+ const { answers } = await decider.decide(stateText, questions);
613
+ ```
614
+
615
+ Protocol implemented here (mirrors `encode_record` / `ClefModel`):
616
+
617
+ - record `<|im_start|>system\n…<|im_end|>\n<|im_start|>user\nSTATE:\n<state>`
618
+ + `\n\nSCHEMA FIELDS:\n` + one block per question (`\nFIELD n\nID: …\nTYPE:
619
+ …\nINSTRUCTION: …\nALLOWED OPTIONS:\nOPTION n:
620
+ {"description":…,"option_id":…}\n…END FIELD\n`) + `<|im_end|>\n<|im_start|>
621
+ assistant\n<think>\n\n</think>\n\nJOINT SCHEMA DECISIONS:` — every piece
622
+ tokenized separately, the state truncated head-first to the window;
623
+ - `choice` option keys are **sorted** like the reference's
624
+ `sorted(criteria.items())` (probabilities still come back keyed by your
625
+ labels); `noul` maps to the fixed `true`/`false` slots (default descriptions,
626
+ overridable under either `true/false` or `yes/no`); `score` levels map to
627
+ numeric keys `0..n-1` with a `legend`;
628
+ - a question may carry an `id` — it is rendered as the field ID (default
629
+ `"0"`, `"1"`, …);
630
+ - the head's evidence and field passes attend to the full hidden sequence, so
631
+ torso and head ship as **one fused ONNX graph**; span means (question spans,
632
+ option spans, the final token, and the tied-embedding lexical mean per
633
+ option) travel as pooling matrices — the Vega trick;
634
+ - choice/score confidence is the top probability (the reference SystemOne
635
+ measure), not an entropy score.
636
+
637
+ Lower-level helpers are exported for custom runtimes:
638
+ `encodeClefRecord`, `packClefBatch`, `clefQuestionOptions`, `clefAnswers`,
639
+ `renderClefContent`.
640
+
641
+ Caveat: this family ships the **text-only** clef — images and videos (the
642
+ multimodal clef checkpoints' processor path) are not part of the browser
643
+ export. The fused fp16 graph needs an onnxruntime in the 1.31 generation
644
+ (transformers.js 4.3 ships ORT-web 1.31 in the browser); hosts pinned to an
645
+ older runtime can re-export the exact fp32 graph (2.7 GB) with
646
+ `export/clef/01_export.py` and pass it as `file`.
647
+
648
+ ## Decision 3.0 family (d3 code readout)
649
+
650
+ `createDecision3Decider(options)` runs the
651
+ [vLLM Semantic Router Decision 3.0](https://huggingface.co/vllm-sr/d3-edge)
652
+ collection (Apache-2.0): Decision 2.0's candidate head is replaced by a **code
653
+ readout** — every option is printed under a single-token answer code (A…Z,
654
+ AA…AZ, up to 255), one question is decided per forward pass, and a 255-way
655
+ readout scores those codes at the last prompt position. d3-edge (0.59B,
656
+ multimodal) scores **28.82** on the Jev Decision Index 0.3 public suite (+12
657
+ over Decision 2.0's 0.6B).
658
+
659
+ ```js
660
+ import { createDecision3Decider } from "jev-web";
661
+
662
+ const decider = await createDecision3Decider({
663
+ // defaults to warsang/d3-edge-web @ pinned revision
664
+ onProgress: (p) => console.log(p.phase, p.loaded, p.total),
665
+ });
666
+ // or pick a larger sibling:
667
+ // await createDecision3Decider({ variant: "lite" }); // 0.85B
668
+ // await createDecision3Decider({ variant: "nano" }); // 2.21B
669
+ const { answers } = await decider.decide(stateText, questions);
670
+ ```
671
+
672
+ Protocol implemented here (mirrors `d3_format.py` / `d3_runtime.py`):
673
+
674
+ - user prompt `State:\n<state>\n\nQuestion:\n<instructions>\n\nOptions:\nA:
675
+ <option>\nB: …\n\nReply with only the code of the best option.` wrapped in
676
+ the checkpoint's Qwen3.5 chat template with `add_generation_prompt` and
677
+ `enable_thinking=false` (the built-in rendering is byte-identical to the
678
+ template and kicks in when the runtime has no `apply_chat_template`);
679
+ - the answer-code table comes from the checkpoint's `decision_config.json`
680
+ (falling back to the shipped A…Z / AA…ZZ list); questions run `batchSize`
681
+ (8) per forward pass, left-padded like the reference;
682
+ - `choice` keys are the option labels with optional descriptions; `noul` maps
683
+ to the fixed `false`/`true` slots with overridable descriptions; `score`
684
+ takes 2–10 distinct levels, renders as bare option texts, and answers with
685
+ the expected level index, the reference entropy `confidence` and a `legend`;
686
+ - two ONNX graphs ship: the torso (Qwen3.5 backbone with the reference
687
+ **non-causal full attention** — softmax layers see the whole prompt while the
688
+ GatedDeltaNet layers stay causal and padding-safe) emits the last prompt
689
+ token's hidden state, and a 255×d readout graph emits masked code logits
690
+ (fp32); the JS softmaxes them with the checkpoint temperature;
691
+ - a prompt over `maxLen` raises a `RangeError` — like the reference, nothing
692
+ is truncated (`truncated` is always `false`).
693
+
694
+ Caveats: the browser export is **text-only in v1** — the checkpoints'
695
+ image/video inputs run through a separate vision tower that the two shipped
696
+ graphs do not include; the exported window is capped by the browser budget
697
+ (`maxLen`, 8192 default) rather than the checkpoint's uncapped `max_length`;
698
+ and the fp16 torso needs an onnxruntime in the 1.31 generation (the same
699
+ requirement as clef — transformers.js 4.3 ships ORT-web 1.31 in the browser).
700
+
701
+ Lower-level helpers are exported for custom runtimes:
702
+ `buildDecision3Prompt`, `decision3Messages`, `renderDecision3Chat`,
703
+ `normalizeDecision3Question`, `decision3OptionTexts`,
704
+ `decision3ProbsFromLogits`, `decision3Confidence`, `decision3Answers`,
705
+ `packDecision3Batch`.
560
706
 
561
707
  ## Worker / thread
562
708
 
@@ -575,6 +721,8 @@ import { createDecider } from "jev-web";
575
721
  - `createDecider(options)` → open-jev family: `{ info, decide, decideMany, dispose }`
576
722
  - `createLayaDecider(options)` → Laya family: `{ info, decide, dispose }` (same `decide` contract)
577
723
  - `createStrandsDecider(options)` → Strands Decider family: `{ info, decide, dispose }` (same `decide` contract)
724
+ - `createBekkoDecider(options)`, `createDecision2Decider(options)`, `createKevDecider(options)`,
725
+ `createVegaDecider(options)`, `createClefDecider(options)`, `createDecision3Decider(options)` → the same `decide` contract
578
726
  - `normalizeQuestions`, `buildDecisionInput`, `typedMarkerIds`, `answersFromScores`,
579
727
  `softmaxWithTemperature`, `resolveDevice`, `resolveDtype`, `detectWebGPU`,
580
728
  `loadModelConfig`, defaults (model/revision/temperature/caps).
Binary file
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "jev-web",
3
- "version": "0.8.0",
4
- "description": "Run open-jev-, Laya- and Strands-Decider-shaped typed-decision models in the browser: one state plus typed questions in, calibrated probabilities out.",
3
+ "version": "0.9.0",
4
+ "description": "Run open-jev-, Laya-, Strands-Decider-, Bekko-, Decision-, Kev-, Vega-, clef-shaped typed-decision models in the browser: one state plus typed questions in, calibrated probabilities out.",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -24,6 +24,12 @@
24
24
  "open-jev",
25
25
  "laya",
26
26
  "strands-decider",
27
+ "bekko",
28
+ "decision2",
29
+ "decision3",
30
+ "kev",
31
+ "vega",
32
+ "clef",
27
33
  "typed-decisions",
28
34
  "onnx",
29
35
  "transformers.js",
@@ -45,13 +51,11 @@
45
51
  "peerDependenciesMeta": {
46
52
  "@huggingface/transformers": {
47
53
  "optional": true
48
- },
49
- "onnxruntime-web": {
50
- "optional": true
51
54
  }
52
55
  },
53
56
  "devDependencies": {
54
57
  "@huggingface/transformers": "^4.3.0",
58
+ "onnxruntime-web": "1.31.0-dev.20260914-8d85527a0",
55
59
  "vite": "^6.3.5"
56
60
  },
57
61
  "engines": {