jev-web 0.7.2 → 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/src/questions.mjs CHANGED
@@ -1,39 +1,46 @@
1
- // jev-web — question normalization for typed-decision models.
2
- //
3
- // The model answers three question kinds:
4
- // choice — pick one of 2..255 options
5
- // score — ordered levels (2..10); the answer is the expected level index
6
- // noul — yes/no; the answer is p(yes)
7
-
8
- export const QUESTION_TYPES = new Set(["choice", "score", "noul"]);
9
- export const NOUL_OPTIONS = ["no", "yes"];
10
-
11
- export function normalizeQuestions(questions) {
12
- if (!Array.isArray(questions) || questions.length === 0) {
13
- throw new TypeError("questions must be a non-empty array");
14
- }
15
- return questions.map((q, i) => {
16
- const type = q?.type;
17
- if (!QUESTION_TYPES.has(type)) {
18
- throw new TypeError(`questions[${i}].type must be one of ${[...QUESTION_TYPES].join("|")}`);
19
- }
20
- const instructions = String(q?.instructions ?? "").trim();
21
- if (!instructions) throw new TypeError(`questions[${i}].instructions is required`);
22
-
23
- let options;
24
- if (type === "noul") {
25
- options = Array.isArray(q.options) && q.options.length ? q.options.map(String) : [...NOUL_OPTIONS];
26
- if (options.length !== 2) throw new TypeError(`questions[${i}]: noul takes exactly 2 options`);
27
- } else if (type === "choice") {
28
- options = (q.options ?? []).map(String);
29
- if (options.length < 2) throw new TypeError(`questions[${i}]: choice needs at least 2 options`);
30
- if (options.length > 255) throw new TypeError(`questions[${i}]: choice supports at most 255 options`);
31
- } else {
32
- options = (q.options ?? []).map(String);
33
- if (options.length < 2 || options.length > 10) {
34
- throw new TypeError(`questions[${i}]: score needs 2-10 ordered levels`);
35
- }
36
- }
37
- return { type, instructions, options, ...(q?.criteria !== undefined ? { criteria: q.criteria } : {}) };
38
- });
39
- }
1
+ // jev-web — question normalization for typed-decision models.
2
+ //
3
+ // The model answers three question kinds:
4
+ // choice — pick one of 2..255 options
5
+ // score — ordered levels (2..10); the answer is the expected level index
6
+ // noul — yes/no; the answer is p(yes)
7
+
8
+ export const QUESTION_TYPES = new Set(["choice", "score", "noul"]);
9
+ export const NOUL_OPTIONS = ["no", "yes"];
10
+
11
+ export function normalizeQuestions(questions) {
12
+ if (!Array.isArray(questions) || questions.length === 0) {
13
+ throw new TypeError("questions must be a non-empty array");
14
+ }
15
+ return questions.map((q, i) => {
16
+ const type = q?.type;
17
+ if (!QUESTION_TYPES.has(type)) {
18
+ throw new TypeError(`questions[${i}].type must be one of ${[...QUESTION_TYPES].join("|")}`);
19
+ }
20
+ const instructions = String(q?.instructions ?? "").trim();
21
+ if (!instructions) throw new TypeError(`questions[${i}].instructions is required`);
22
+
23
+ let options;
24
+ if (type === "noul") {
25
+ options = Array.isArray(q.options) && q.options.length ? q.options.map(String) : [...NOUL_OPTIONS];
26
+ if (options.length !== 2) throw new TypeError(`questions[${i}]: noul takes exactly 2 options`);
27
+ } else if (type === "choice") {
28
+ options = (q.options ?? []).map(String);
29
+ if (options.length < 2) throw new TypeError(`questions[${i}]: choice needs at least 2 options`);
30
+ if (options.length > 255) throw new TypeError(`questions[${i}]: choice supports at most 255 options`);
31
+ } else {
32
+ options = (q.options ?? []).map(String);
33
+ if (options.length < 2 || options.length > 10) {
34
+ throw new TypeError(`questions[${i}]: score needs 2-10 ordered levels`);
35
+ }
36
+ }
37
+ return {
38
+ type,
39
+ instructions,
40
+ options,
41
+ ...(q?.criteria !== undefined ? { criteria: q.criteria } : {}),
42
+ // clef renders each field's ID into the record; other families ignore it.
43
+ ...(q?.id !== undefined ? { id: String(q.id) } : {}),
44
+ };
45
+ });
46
+ }
package/src/registry.mjs CHANGED
@@ -1,156 +1,200 @@
1
- /**
2
- * registry.mjs — multi-family runtime for JEV-style typed-decision models.
3
- *
4
- * jev-web ships three families today — open-jev (fused DeBERTa graph), Laya
5
- * (ModernBERT encoder + typed head), Strands Decider 2B (Qwen torso + pointer
6
- * head), Bekko System One v0 (cross-encoder), and Decision 2.0 (Qwen3 +
7
- * candidate head) — and any third party can add its own without touching
8
- * core:
9
- *
10
- * registerDecisionFamily("my-model", {
11
- * defaults: { model: "org/my-typed-decision-ONNX", revision: "main" },
12
- * create: async (opts) => ({ info, decide(state, questions), dispose? }),
13
- * });
14
- *
15
- * const runtime = await createDecisionRuntime({ family: "my-model" });
16
- *
17
- * `createDecisionRuntime` tries the requested family, then any `fallback`
18
- * families in order, reporting each failure through `onFallback`. The returned
19
- * object always exposes the shared contract:
20
- * { info: { family, ... }, decide(state, questions) -> DecideResult, dispose? }
21
- * where DecideResult is `{ answers, truncated, length, prompts?, timings? }`.
22
- */
23
-
24
- import { createDecider } from "./session.mjs";
25
- import { DEFAULT_MODEL, DEFAULT_REVISION } from "./config.mjs";
26
- import {
27
- createLayaDecider,
28
- LAYA_DEFAULT_MODEL,
29
- LAYA_DEFAULT_REVISION,
30
- LAYA_DEFAULT_SUBFOLDER,
31
- } from "./laya.mjs";
32
- import {
33
- createStrandsDecider,
34
- STRANDS_DEFAULT_MODEL,
35
- STRANDS_DEFAULT_REVISION,
36
- } from "./strands.mjs";
37
- import {
38
- createBekkoDecider,
39
- BEKKO_DEFAULT_MODEL,
40
- BEKKO_DEFAULT_REVISION,
41
- } from "./bekko.mjs";
42
- import {
43
- createDecision2Decider,
44
- DECISION2_DEFAULT_MODEL,
45
- DECISION2_DEFAULT_REVISION,
46
- } from "./decision2.mjs";
47
- import {
48
- createKevDecider,
49
- KEV_DEFAULT_MODEL,
50
- KEV_DEFAULT_REVISION,
51
- } from "./kev.mjs";
52
-
53
- const families = new Map();
54
-
55
- /**
56
- * Register (or replace) a decision family.
57
- * @param {string} id short stable identifier, e.g. "open-jev"
58
- * @param {{create:(opts:object)=>Promise<object>, defaults?:object}} descriptor
59
- */
60
- export function registerDecisionFamily(id, descriptor) {
61
- if (!id || typeof id !== "string") throw new TypeError("registerDecisionFamily: id is required");
62
- if (!descriptor || typeof descriptor.create !== "function") {
63
- throw new TypeError(`registerDecisionFamily(${id}): descriptor.create is required`);
64
- }
65
- families.set(id, { defaults: {}, ...descriptor });
66
- return id;
67
- }
68
-
69
- /** @returns {string[]} registered family ids in registration order. */
70
- export function listDecisionFamilies() {
71
- return [...families.keys()];
72
- }
73
-
74
- export function getDecisionFamily(id) {
75
- return families.get(id) ?? null;
76
- }
77
-
78
- registerDecisionFamily("open-jev", {
79
- defaults: { model: DEFAULT_MODEL, revision: DEFAULT_REVISION },
80
- create: createDecider,
81
- });
82
-
83
- registerDecisionFamily("laya", {
84
- defaults: {
85
- model: LAYA_DEFAULT_MODEL,
86
- revision: LAYA_DEFAULT_REVISION,
87
- subfolder: LAYA_DEFAULT_SUBFOLDER,
88
- },
89
- create: createLayaDecider,
90
- });
91
-
92
- registerDecisionFamily("strands-decider", {
93
- defaults: {
94
- model: STRANDS_DEFAULT_MODEL,
95
- revision: STRANDS_DEFAULT_REVISION,
96
- },
97
- create: createStrandsDecider,
98
- });
99
-
100
- registerDecisionFamily("bekko", {
101
- defaults: {
102
- model: BEKKO_DEFAULT_MODEL,
103
- revision: BEKKO_DEFAULT_REVISION,
104
- },
105
- create: createBekkoDecider,
106
- });
107
-
108
- registerDecisionFamily("decision2", {
109
- defaults: {
110
- model: DECISION2_DEFAULT_MODEL,
111
- revision: DECISION2_DEFAULT_REVISION,
112
- },
113
- create: createDecision2Decider,
114
- });
115
-
116
- registerDecisionFamily("kev", {
117
- defaults: {
118
- model: KEV_DEFAULT_MODEL,
119
- revision: KEV_DEFAULT_REVISION,
120
- },
121
- create: createKevDecider,
122
- });
123
-
124
- /**
125
- * Create a decision runtime, preferring `family` and falling back in order.
126
- * @param {{family?:string, fallback?:string[], onFallback?:Function}} [opts]
127
- * @returns {Promise<{info:object, decide:Function, dispose?:Function}>}
128
- */
129
- export async function createDecisionRuntime({
130
- family = "laya",
131
- fallback = [],
132
- onFallback = null,
133
- ...opts
134
- } = {}) {
135
- const order = [family, ...(Array.isArray(fallback) ? fallback : [])].filter(Boolean);
136
- let lastError = null;
137
- for (const id of order) {
138
- const descriptor = families.get(id);
139
- if (!descriptor) {
140
- lastError = new Error(`unknown decision family "${id}"`);
141
- onFallback?.({ family: id, error: lastError });
142
- continue;
143
- }
144
- try {
145
- const runtime = await descriptor.create({ ...descriptor.defaults, ...opts });
146
- return {
147
- ...runtime,
148
- info: { family: id, ...(runtime?.info ?? {}) },
149
- };
150
- } catch (error) {
151
- lastError = error;
152
- onFallback?.({ family: id, error });
153
- }
154
- }
155
- throw lastError ?? new Error("createDecisionRuntime: no family could be created");
1
+ /**
2
+ * registry.mjs — multi-family runtime for JEV-style typed-decision models.
3
+ *
4
+ * jev-web ships nine families today — open-jev (fused DeBERTa graph), Laya
5
+ * (ModernBERT encoder + typed head), Strands Decider 2B (Qwen torso + pointer
6
+ * head), Bekko System One v0 (cross-encoder), Decision 2.0 (Qwen3 + candidate
7
+ * head), Kev (Qwen3 + pointer head), Vega 0.8B (frozen Qwen3.5 torso + latent
8
+ * physics engine), clef (Qwen3 + joint schema head), and Decision 3.0 (d3
9
+ * code readout) — and any third party can add its own without touching
10
+ * core:
11
+ *
12
+ * registerDecisionFamily("my-model", {
13
+ * defaults: { model: "org/my-typed-decision-ONNX", revision: "main" },
14
+ * create: async (opts) => ({ info, decide(state, questions), dispose? }),
15
+ * });
16
+ *
17
+ * const runtime = await createDecisionRuntime({ family: "my-model" });
18
+ *
19
+ * `createDecisionRuntime` tries the requested family, then any `fallback`
20
+ * families in order, reporting each failure through `onFallback`. The returned
21
+ * object always exposes the shared contract:
22
+ * { info: { family, ... }, decide(state, questions) -> DecideResult, dispose? }
23
+ * where DecideResult is `{ answers, truncated, length, prompts?, timings? }`.
24
+ */
25
+
26
+ import { createDecider } from "./session.mjs";
27
+ import { DEFAULT_MODEL, DEFAULT_REVISION } from "./config.mjs";
28
+ import {
29
+ createLayaDecider,
30
+ LAYA_DEFAULT_MODEL,
31
+ LAYA_DEFAULT_REVISION,
32
+ LAYA_DEFAULT_SUBFOLDER,
33
+ } from "./laya.mjs";
34
+ import {
35
+ createStrandsDecider,
36
+ STRANDS_DEFAULT_MODEL,
37
+ STRANDS_DEFAULT_REVISION,
38
+ } from "./strands.mjs";
39
+ import {
40
+ createBekkoDecider,
41
+ BEKKO_DEFAULT_MODEL,
42
+ BEKKO_DEFAULT_REVISION,
43
+ } from "./bekko.mjs";
44
+ import {
45
+ createDecision2Decider,
46
+ DECISION2_DEFAULT_MODEL,
47
+ DECISION2_DEFAULT_REVISION,
48
+ } from "./decision2.mjs";
49
+ import {
50
+ createKevDecider,
51
+ KEV_DEFAULT_MODEL,
52
+ KEV_DEFAULT_REVISION,
53
+ } from "./kev.mjs";
54
+
55
+ import {
56
+ createVegaDecider,
57
+ VEGA_DEFAULT_MODEL,
58
+ VEGA_DEFAULT_REVISION,
59
+ } from "./vega.mjs";
60
+
61
+ import {
62
+ createClefDecider,
63
+ CLEF_DEFAULT_MODEL,
64
+ CLEF_DEFAULT_REVISION,
65
+ } from "./clef.mjs";
66
+
67
+ import {
68
+ createDecision3Decider,
69
+ DECISION3_DEFAULT_MODEL,
70
+ DECISION3_DEFAULT_REVISION,
71
+ } from "./decision3.mjs";
72
+
73
+ const families = new Map();
74
+
75
+ /**
76
+ * Register (or replace) a decision family.
77
+ * @param {string} id short stable identifier, e.g. "open-jev"
78
+ * @param {{create:(opts:object)=>Promise<object>, defaults?:object}} descriptor
79
+ */
80
+ export function registerDecisionFamily(id, descriptor) {
81
+ if (!id || typeof id !== "string") throw new TypeError("registerDecisionFamily: id is required");
82
+ if (!descriptor || typeof descriptor.create !== "function") {
83
+ throw new TypeError(`registerDecisionFamily(${id}): descriptor.create is required`);
84
+ }
85
+ families.set(id, { defaults: {}, ...descriptor });
86
+ return id;
87
+ }
88
+
89
+ /** @returns {string[]} registered family ids in registration order. */
90
+ export function listDecisionFamilies() {
91
+ return [...families.keys()];
92
+ }
93
+
94
+ export function getDecisionFamily(id) {
95
+ return families.get(id) ?? null;
96
+ }
97
+
98
+ registerDecisionFamily("open-jev", {
99
+ defaults: { model: DEFAULT_MODEL, revision: DEFAULT_REVISION },
100
+ create: createDecider,
101
+ });
102
+
103
+ registerDecisionFamily("laya", {
104
+ defaults: {
105
+ model: LAYA_DEFAULT_MODEL,
106
+ revision: LAYA_DEFAULT_REVISION,
107
+ subfolder: LAYA_DEFAULT_SUBFOLDER,
108
+ },
109
+ create: createLayaDecider,
110
+ });
111
+
112
+ registerDecisionFamily("strands-decider", {
113
+ defaults: {
114
+ model: STRANDS_DEFAULT_MODEL,
115
+ revision: STRANDS_DEFAULT_REVISION,
116
+ },
117
+ create: createStrandsDecider,
118
+ });
119
+
120
+ registerDecisionFamily("bekko", {
121
+ defaults: {
122
+ model: BEKKO_DEFAULT_MODEL,
123
+ revision: BEKKO_DEFAULT_REVISION,
124
+ },
125
+ create: createBekkoDecider,
126
+ });
127
+
128
+ registerDecisionFamily("decision2", {
129
+ defaults: {
130
+ model: DECISION2_DEFAULT_MODEL,
131
+ revision: DECISION2_DEFAULT_REVISION,
132
+ },
133
+ create: createDecision2Decider,
134
+ });
135
+
136
+ registerDecisionFamily("kev", {
137
+ defaults: {
138
+ model: KEV_DEFAULT_MODEL,
139
+ revision: KEV_DEFAULT_REVISION,
140
+ },
141
+ create: createKevDecider,
142
+ });
143
+
144
+ registerDecisionFamily("vega", {
145
+ defaults: {
146
+ model: VEGA_DEFAULT_MODEL,
147
+ revision: VEGA_DEFAULT_REVISION,
148
+ },
149
+ create: createVegaDecider,
150
+ });
151
+
152
+ registerDecisionFamily("clef", {
153
+ defaults: {
154
+ model: CLEF_DEFAULT_MODEL,
155
+ revision: CLEF_DEFAULT_REVISION,
156
+ },
157
+ create: createClefDecider,
158
+ });
159
+
160
+ registerDecisionFamily("decision3", {
161
+ defaults: {
162
+ model: DECISION3_DEFAULT_MODEL,
163
+ revision: DECISION3_DEFAULT_REVISION,
164
+ },
165
+ create: createDecision3Decider,
166
+ });
167
+
168
+ /**
169
+ * Create a decision runtime, preferring `family` and falling back in order.
170
+ * @param {{family?:string, fallback?:string[], onFallback?:Function}} [opts]
171
+ * @returns {Promise<{info:object, decide:Function, dispose?:Function}>}
172
+ */
173
+ export async function createDecisionRuntime({
174
+ family = "laya",
175
+ fallback = [],
176
+ onFallback = null,
177
+ ...opts
178
+ } = {}) {
179
+ const order = [family, ...(Array.isArray(fallback) ? fallback : [])].filter(Boolean);
180
+ let lastError = null;
181
+ for (const id of order) {
182
+ const descriptor = families.get(id);
183
+ if (!descriptor) {
184
+ lastError = new Error(`unknown decision family "${id}"`);
185
+ onFallback?.({ family: id, error: lastError });
186
+ continue;
187
+ }
188
+ try {
189
+ const runtime = await descriptor.create({ ...descriptor.defaults, ...opts });
190
+ return {
191
+ ...runtime,
192
+ info: { family: id, ...(runtime?.info ?? {}) },
193
+ };
194
+ } catch (error) {
195
+ lastError = error;
196
+ onFallback?.({ family: id, error });
197
+ }
198
+ }
199
+ throw lastError ?? new Error("createDecisionRuntime: no family could be created");
156
200
  }