jev-web 0.3.2 → 0.4.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/LICENSE +21 -0
- package/README.md +237 -25
- package/docs/demo-negation.png +0 -0
- package/docs/demo-typed.png +0 -0
- package/docs/demo.gif +0 -0
- package/package.json +15 -2
- package/src/bekko.mjs +391 -0
- package/src/index.mjs +41 -0
- package/src/laya.mjs +15 -10
- package/src/questions.mjs +1 -1
- package/src/registry.mjs +30 -3
- package/src/strands.mjs +535 -0
package/src/bekko.mjs
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bekko.mjs — Bekko System One v0 family (Ettin reranker cross-encoder).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors the reference browser runtime (hotchpotch/bekko-system-one, MIT,
|
|
5
|
+
* browser/src/core.js + browser/src/decision.js):
|
|
6
|
+
*
|
|
7
|
+
* query: [cls] Instruction: {instruction}\nState: {state} [sep]
|
|
8
|
+
* (instruction and state split the query budget 50/50, with
|
|
9
|
+
* unused capacity spilling over to the other side)
|
|
10
|
+
* candidates: "Candidate: {id}: {description}" (+ [sep]), one per option
|
|
11
|
+
* graph: prefix_ids, prefix_mask, doc_ids, doc_mask, owners
|
|
12
|
+
* -> logits [N, T] (one column per task: choice, noul, score)
|
|
13
|
+
* decode: softmax over the task column's candidate logits.
|
|
14
|
+
*
|
|
15
|
+
* The browser export used here is hotchpotch/bekko-system-one-v0-17m
|
|
16
|
+
* (onnx_browser/, pinned by revision) — 29 MB, the smallest of the family.
|
|
17
|
+
* Larger siblings: v0-68m and v0-400m.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { normalizeQuestions } from "./questions.mjs";
|
|
21
|
+
import { fetchCachedBytes } from "./laya.mjs";
|
|
22
|
+
|
|
23
|
+
export const BEKKO_DEFAULT_MODEL = "hotchpotch/bekko-system-one-v0-17m";
|
|
24
|
+
export const BEKKO_DEFAULT_REVISION = "b886a1f9b91f4e8368d7830080d52d955e2c8dfa";
|
|
25
|
+
export const BEKKO_DEFAULT_SUBDIR = "onnx_browser";
|
|
26
|
+
export const BEKKO_68M_MODEL = "hotchpotch/bekko-system-one-v0-68m";
|
|
27
|
+
export const BEKKO_68M_REVISION = "ab7685f23e5edbc1acb12ced4f2c4e12591efa69";
|
|
28
|
+
export const BEKKO_400M_MODEL = "hotchpotch/bekko-system-one-v0-400m";
|
|
29
|
+
export const BEKKO_400M_REVISION = "1960df5602bd93cc8d336fdebd9fb68a30926e13";
|
|
30
|
+
|
|
31
|
+
// Candidate batching heuristic from the reference runtime: bound the
|
|
32
|
+
// candidate-attention growth per forward; not a total RAM limit.
|
|
33
|
+
export const BEKKO_ATTENTION_BUDGET = 1_048_576;
|
|
34
|
+
|
|
35
|
+
export const BEKKO_DEFAULT_YES = "Yes, the condition in the question holds.";
|
|
36
|
+
export const BEKKO_DEFAULT_NO = "No, the condition in the question does not hold.";
|
|
37
|
+
// Rendered on both sides of a noul so the two candidates read like the
|
|
38
|
+
// reference runtime's defaults.
|
|
39
|
+
export const BEKKO_NOUL_DESCRIPTIONS = { true: BEKKO_DEFAULT_YES, false: BEKKO_DEFAULT_NO };
|
|
40
|
+
|
|
41
|
+
/** Flatten a state or instruction into text, stably. */
|
|
42
|
+
export function renderBekkoContent(content) {
|
|
43
|
+
if (typeof content === "string") return content.trim();
|
|
44
|
+
return JSON.stringify(content);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render one normalized question to a Bekko request (mirrors decision.js
|
|
49
|
+
* renderDecision): the task, the instruction, the JSON-encoded state and the
|
|
50
|
+
* candidate list. Also returns the metadata jev-web needs to map the answer
|
|
51
|
+
* back: `labels` (answer keys in candidate order) and `kind`.
|
|
52
|
+
*/
|
|
53
|
+
export function renderBekkoRequest(question, stateText) {
|
|
54
|
+
const instruction = renderBekkoContent(question.instructions);
|
|
55
|
+
const kind = question.type;
|
|
56
|
+
if (kind === "choice") {
|
|
57
|
+
const criteria = question.criteria ?? {};
|
|
58
|
+
const candidates = question.options.map((label) => {
|
|
59
|
+
const desc = String(criteria[label] ?? label);
|
|
60
|
+
return { id: label, text: `Candidate: ${label}: ${desc}` };
|
|
61
|
+
});
|
|
62
|
+
return {
|
|
63
|
+
task: "choice",
|
|
64
|
+
instruction,
|
|
65
|
+
state: JSON.stringify(stateText),
|
|
66
|
+
layout: "instruction_state",
|
|
67
|
+
candidates,
|
|
68
|
+
labels: [...question.options],
|
|
69
|
+
kind,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (kind === "noul") {
|
|
73
|
+
const criteria = question.criteria ?? {};
|
|
74
|
+
const yesDesc = String(criteria.true ?? criteria.yes ?? BEKKO_NOUL_DESCRIPTIONS.true);
|
|
75
|
+
const noDesc = String(criteria.false ?? criteria.no ?? BEKKO_NOUL_DESCRIPTIONS.false);
|
|
76
|
+
return {
|
|
77
|
+
task: "noul",
|
|
78
|
+
instruction,
|
|
79
|
+
state: JSON.stringify({ noul: { yes: yesDesc, no: noDesc }, state: stateText }),
|
|
80
|
+
layout: "instruction_state",
|
|
81
|
+
candidates: [
|
|
82
|
+
{ id: "true", text: `Candidate: true: ${yesDesc}` },
|
|
83
|
+
{ id: "false", text: `Candidate: false: ${noDesc}` },
|
|
84
|
+
],
|
|
85
|
+
// answer keys parallel to candidates: candidate 0 ("true") is the
|
|
86
|
+
// question's "yes" option, candidate 1 ("false") is the "no" option.
|
|
87
|
+
labels: [question.options[1], question.options[0]],
|
|
88
|
+
kind,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (kind === "score") {
|
|
92
|
+
const candidates = question.options.map((rubric, i) => ({
|
|
93
|
+
id: String(i),
|
|
94
|
+
text: `Candidate: ${i}: ${rubric}`,
|
|
95
|
+
value: i,
|
|
96
|
+
}));
|
|
97
|
+
return {
|
|
98
|
+
task: "score",
|
|
99
|
+
instruction,
|
|
100
|
+
state: JSON.stringify(stateText),
|
|
101
|
+
layout: "instruction_state",
|
|
102
|
+
candidates,
|
|
103
|
+
labels: question.options.map((_, i) => String(i)),
|
|
104
|
+
kind,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
throw new TypeError(`bekko: unsupported question type ${question.type}`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Tokenise a Bekko request (mirrors core.js tokenize): the shared query
|
|
112
|
+
* prefix plus one tokenised candidate per option. Returns the raw id arrays
|
|
113
|
+
* and whether the instruction/state were truncated to the query budget.
|
|
114
|
+
*/
|
|
115
|
+
export function tokenizeBekkoRequest(request, encode, manifest, system = "") {
|
|
116
|
+
const max = manifest.query_length;
|
|
117
|
+
const systemIds = encode(system.trim() ? `${system}\n\n` : "").slice(0, max);
|
|
118
|
+
const instructionIds = encode(request.instruction).slice(0, max);
|
|
119
|
+
const stateIds = encode(request.state).slice(0, max);
|
|
120
|
+
const im = encode("Instruction: ");
|
|
121
|
+
const sm = encode("State: ");
|
|
122
|
+
const sep = encode("\n");
|
|
123
|
+
const budget = max - 2 - systemIds.length - im.length - sm.length - sep.length;
|
|
124
|
+
if (budget < 2) {
|
|
125
|
+
throw new RangeError("bekko: system prompt leaves fewer than two content tokens");
|
|
126
|
+
}
|
|
127
|
+
let ni = Math.min(instructionIds.length, Math.ceil(budget / 2));
|
|
128
|
+
let ns = Math.min(stateIds.length, Math.floor(budget / 2));
|
|
129
|
+
ni += Math.min(instructionIds.length - ni, budget - ni - ns);
|
|
130
|
+
ns += Math.min(stateIds.length - ns, budget - ni - ns);
|
|
131
|
+
const truncated = ni < instructionIds.length || ns < stateIds.length;
|
|
132
|
+
const ins = [...im, ...instructionIds.slice(0, ni)];
|
|
133
|
+
const ctx = [...sm, ...stateIds.slice(0, ns)];
|
|
134
|
+
const body = request.layout === "state_instruction"
|
|
135
|
+
? [...ctx, ...sep, ...ins]
|
|
136
|
+
: [...ins, ...sep, ...ctx];
|
|
137
|
+
return {
|
|
138
|
+
prefixIds: [manifest.cls_token_id, ...systemIds, ...body, manifest.sep_token_id],
|
|
139
|
+
docIds: request.candidates.map(
|
|
140
|
+
(c) => [...encode(c.text).slice(0, manifest.document_length - 1), manifest.sep_token_id],
|
|
141
|
+
),
|
|
142
|
+
truncated,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Build the ONNX feeds for one question (mirrors core.js feedsFor): the
|
|
148
|
+
* shared prefix plus a batch of candidate documents.
|
|
149
|
+
*/
|
|
150
|
+
export function collateBekkoDocs(prefixIds, docIds, manifest, ort) {
|
|
151
|
+
const pad = BigInt(manifest.pad_token_id);
|
|
152
|
+
const width = Math.max(...docIds.map((row) => row.length));
|
|
153
|
+
const ids = new BigInt64Array(docIds.length * width).fill(pad);
|
|
154
|
+
const mask = new Uint8Array(ids.length);
|
|
155
|
+
docIds.forEach((row, i) => row.forEach((id, j) => {
|
|
156
|
+
ids[i * width + j] = BigInt(id);
|
|
157
|
+
mask[i * width + j] = 1;
|
|
158
|
+
}));
|
|
159
|
+
const pIds = new BigInt64Array(prefixIds.length);
|
|
160
|
+
const pMask = new Uint8Array(prefixIds.length);
|
|
161
|
+
prefixIds.forEach((id, j) => { pIds[j] = BigInt(id); pMask[j] = 1; });
|
|
162
|
+
return {
|
|
163
|
+
prefix_ids: new ort.Tensor("int64", pIds, [1, prefixIds.length]),
|
|
164
|
+
prefix_mask: new ort.Tensor("bool", pMask, [1, prefixIds.length]),
|
|
165
|
+
doc_ids: new ort.Tensor("int64", ids, [docIds.length, width]),
|
|
166
|
+
doc_mask: new ort.Tensor("bool", mask, [docIds.length, width]),
|
|
167
|
+
owners: new ort.Tensor("int64", new BigInt64Array(docIds.length), [docIds.length]),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Softmax over candidate logits (mirrors core.js interpret). */
|
|
172
|
+
export function bekkoSoftmax(logits) {
|
|
173
|
+
if (!Array.isArray(logits) || logits.length === 0) {
|
|
174
|
+
throw new TypeError("bekkoSoftmax: logits must be a non-empty array");
|
|
175
|
+
}
|
|
176
|
+
const max = Math.max(...logits);
|
|
177
|
+
const exps = logits.map((x) => Math.exp(Number(x) - max));
|
|
178
|
+
const sum = exps.reduce((a, b) => a + b, 0);
|
|
179
|
+
return exps.map((e) => e / sum);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Decode one question's task-column logits into a jev-web answer
|
|
184
|
+
* (mirrors core.js interpret, mapped onto the shared answer shapes).
|
|
185
|
+
*
|
|
186
|
+
* @param {{logits:ArrayLike<number>, task:string, request:object}} spec
|
|
187
|
+
*/
|
|
188
|
+
export function bekkoAnswerFromLogits({ logits, task, request }) {
|
|
189
|
+
const probs = bekkoSoftmax(Array.from(logits, Number));
|
|
190
|
+
if (probs.length !== request.candidates.length) {
|
|
191
|
+
throw new TypeError(
|
|
192
|
+
`bekko: got ${probs.length} logits for ${request.candidates.length} candidates`);
|
|
193
|
+
}
|
|
194
|
+
const probabilities = Object.fromEntries(request.labels.map((label, i) => [label, probs[i]]));
|
|
195
|
+
const best = probs.indexOf(Math.max(...probs));
|
|
196
|
+
|
|
197
|
+
if (task === "choice") {
|
|
198
|
+
return {
|
|
199
|
+
type: "choice",
|
|
200
|
+
choice: request.labels[best],
|
|
201
|
+
index: best,
|
|
202
|
+
probabilities,
|
|
203
|
+
confidence: Math.max(...probs),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
if (task === "noul") {
|
|
207
|
+
const yes = probs[request.candidates.findIndex((c) => c.id === "true")];
|
|
208
|
+
// labels are [yesOption, noOption] (candidate order); re-key in the
|
|
209
|
+
// question's option order for the shared answer contract.
|
|
210
|
+
const [yesLabel, noLabel] = request.labels;
|
|
211
|
+
return {
|
|
212
|
+
type: "noul",
|
|
213
|
+
noul: yes,
|
|
214
|
+
probabilities: { [noLabel]: 1 - yes, [yesLabel]: yes },
|
|
215
|
+
confidence: Math.max(yes, 1 - yes),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
// score: expected value over the numeric candidate values.
|
|
219
|
+
const values = request.candidates.map((c) => c.value);
|
|
220
|
+
const score = probs.reduce((acc, p, i) => acc + p * values[i], 0);
|
|
221
|
+
const lo = Math.min(...values);
|
|
222
|
+
const hi = Math.max(...values);
|
|
223
|
+
return {
|
|
224
|
+
type: "score",
|
|
225
|
+
score,
|
|
226
|
+
level: best,
|
|
227
|
+
normalizedScore: hi === lo ? 0 : (score - lo) / (hi - lo),
|
|
228
|
+
probabilities,
|
|
229
|
+
confidence: Math.max(...probs),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
234
|
+
|
|
235
|
+
const hfUrl = (model, revision, ...parts) =>
|
|
236
|
+
`https://huggingface.co/${model}/resolve/${revision ? `${revision}/` : ""}${parts.join("/")}`;
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Create a Bekko System One decider.
|
|
240
|
+
*
|
|
241
|
+
* @param {object} [opts]
|
|
242
|
+
* model/revision/subdir — pinned browser ONNX export (or your own mirror)
|
|
243
|
+
* manifestUrl/modelUrl — override the resolved file URLs
|
|
244
|
+
* ort — injected onnxruntime-web (lazy import otherwise)
|
|
245
|
+
* transformers — injected @huggingface/transformers (tokenizer only)
|
|
246
|
+
* tokenizerModel/tokenizerRevision — override the tokenizer source
|
|
247
|
+
* wasmPaths — ORT wasm/loader directory (self-hosted builds)
|
|
248
|
+
* device — "auto" | "wasm" | "webgpu"
|
|
249
|
+
* sessionFactory — (url, sessionOptions) => Promise<InferenceSession>
|
|
250
|
+
* attentionBudget — candidate batching budget (reference: 1M pairs)
|
|
251
|
+
*/
|
|
252
|
+
export async function createBekkoDecider({
|
|
253
|
+
model = BEKKO_DEFAULT_MODEL,
|
|
254
|
+
revision = BEKKO_DEFAULT_REVISION,
|
|
255
|
+
subdir = BEKKO_DEFAULT_SUBDIR,
|
|
256
|
+
manifestUrl = null,
|
|
257
|
+
modelUrl = null,
|
|
258
|
+
ort = null,
|
|
259
|
+
transformers = null,
|
|
260
|
+
fetchImpl = fetch,
|
|
261
|
+
tokenizerModel = null,
|
|
262
|
+
tokenizerRevision = null,
|
|
263
|
+
wasmPaths = null,
|
|
264
|
+
device = "auto",
|
|
265
|
+
sessionFactory = null,
|
|
266
|
+
onProgress = null,
|
|
267
|
+
system = "",
|
|
268
|
+
attentionBudget = BEKKO_ATTENTION_BUDGET,
|
|
269
|
+
scope = globalThis,
|
|
270
|
+
} = {}) {
|
|
271
|
+
const ortMod = ort ?? (await import("onnxruntime-web"));
|
|
272
|
+
const tf = transformers ?? (await import("@huggingface/transformers"));
|
|
273
|
+
if (wasmPaths) {
|
|
274
|
+
try { ortMod.env.wasm.wasmPaths = wasmPaths; } catch { /* older runtime */ }
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const resolvedDevice = device !== "auto"
|
|
278
|
+
? device
|
|
279
|
+
: (scope?.navigator?.gpu ? "webgpu" : "wasm");
|
|
280
|
+
const executionProviders = resolvedDevice === "webgpu" ? ["webgpu", "wasm"] : ["wasm"];
|
|
281
|
+
|
|
282
|
+
const report = (phase) => (p) =>
|
|
283
|
+
onProgress?.({ phase, file: p?.file ?? null, loaded: p?.loaded ?? null, total: p?.total ?? null });
|
|
284
|
+
|
|
285
|
+
const manifestRes = await fetchImpl(manifestUrl ?? hfUrl(model, revision, subdir, "manifest.json"));
|
|
286
|
+
if (!manifestRes.ok) {
|
|
287
|
+
throw new Error(`bekko: manifest fetch failed (${manifestRes.status})`);
|
|
288
|
+
}
|
|
289
|
+
const manifest = await manifestRes.json();
|
|
290
|
+
const taskColumns = manifest.tasks;
|
|
291
|
+
|
|
292
|
+
const tok = await tf.AutoTokenizer.from_pretrained(tokenizerModel ?? model, {
|
|
293
|
+
revision: tokenizerRevision ?? (tokenizerModel ? undefined : revision),
|
|
294
|
+
progress_callback: report("tokenizer"),
|
|
295
|
+
});
|
|
296
|
+
const encode = (text) =>
|
|
297
|
+
Array.from(tok(text, { add_special_tokens: false }).input_ids.data, Number);
|
|
298
|
+
|
|
299
|
+
const url = modelUrl ?? hfUrl(model, revision, subdir, manifest.model_file ?? "model.onnx");
|
|
300
|
+
const createSession = sessionFactory
|
|
301
|
+
?? (async (u, opts) => {
|
|
302
|
+
const modelBytes = await fetchCachedBytes(u, {
|
|
303
|
+
fetchImpl, onProgress: (p) => onProgress?.(p), persist: true,
|
|
304
|
+
});
|
|
305
|
+
return ortMod.InferenceSession.create(modelBytes, opts);
|
|
306
|
+
});
|
|
307
|
+
let session;
|
|
308
|
+
let activeDevice = resolvedDevice;
|
|
309
|
+
try {
|
|
310
|
+
session = await createSession(url, {
|
|
311
|
+
executionProviders,
|
|
312
|
+
graphOptimizationLevel: "all",
|
|
313
|
+
});
|
|
314
|
+
} catch (e) {
|
|
315
|
+
if (resolvedDevice === "webgpu") {
|
|
316
|
+
onProgress?.({ phase: `webgpu unavailable (${e?.message ?? e}) — retrying on wasm` });
|
|
317
|
+
session = await createSession(url, { executionProviders: ["wasm"], graphOptimizationLevel: "all" });
|
|
318
|
+
activeDevice = "wasm";
|
|
319
|
+
} else {
|
|
320
|
+
throw e;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
info: {
|
|
326
|
+
family: "bekko",
|
|
327
|
+
model,
|
|
328
|
+
revision,
|
|
329
|
+
device: activeDevice,
|
|
330
|
+
tasks: [...taskColumns],
|
|
331
|
+
queryLength: manifest.query_length,
|
|
332
|
+
documentLength: manifest.document_length,
|
|
333
|
+
},
|
|
334
|
+
async decide(state, questions) {
|
|
335
|
+
const t0 = now();
|
|
336
|
+
const normalized = normalizeQuestions(questions);
|
|
337
|
+
const stateText = renderBekkoContent(state);
|
|
338
|
+
const answers = [];
|
|
339
|
+
const prompts = [];
|
|
340
|
+
let truncated = false;
|
|
341
|
+
let length = 0;
|
|
342
|
+
let runMs = 0;
|
|
343
|
+
|
|
344
|
+
for (const q of normalized) {
|
|
345
|
+
const request = renderBekkoRequest(q, stateText);
|
|
346
|
+
const column = taskColumns.indexOf(request.task);
|
|
347
|
+
if (column < 0) throw new Error(`bekko: task ${request.task} absent from this model`);
|
|
348
|
+
const tokens = tokenizeBekkoRequest(request, encode, manifest, system);
|
|
349
|
+
truncated = truncated || tokens.truncated;
|
|
350
|
+
length += tokens.prefixIds.length + tokens.docIds.reduce((a, d) => a + d.length, 0);
|
|
351
|
+
prompts.push({
|
|
352
|
+
query: request.instruction,
|
|
353
|
+
candidates: request.candidates.map((c) => c.text),
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Batch candidates by the reference attention budget.
|
|
357
|
+
const docLen = Math.max(...tokens.docIds.map((d) => d.length));
|
|
358
|
+
const attentionPairs = docLen * (tokens.prefixIds.length + docLen);
|
|
359
|
+
const batchSize = Math.max(1, Math.floor(attentionBudget / attentionPairs));
|
|
360
|
+
const logits = [];
|
|
361
|
+
for (let offset = 0; offset < tokens.docIds.length; offset += batchSize) {
|
|
362
|
+
const docs = tokens.docIds.slice(offset, offset + batchSize);
|
|
363
|
+
const feeds = collateBekkoDocs(tokens.prefixIds, docs, manifest, ortMod);
|
|
364
|
+
const tRun = now();
|
|
365
|
+
const out = await session.run(feeds);
|
|
366
|
+
runMs += now() - tRun;
|
|
367
|
+
const outLogits = out.logits ?? out[Object.keys(out)[0]];
|
|
368
|
+
const T = taskColumns.length;
|
|
369
|
+
if (outLogits.dims.length !== 2 || outLogits.dims[0] !== docs.length || outLogits.dims[1] !== T) {
|
|
370
|
+
throw new Error("bekko: invalid model logits shape");
|
|
371
|
+
}
|
|
372
|
+
for (let r = 0; r < docs.length; r++) {
|
|
373
|
+
logits.push(Number(outLogits.data[r * T + column]));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
answers.push(bekkoAnswerFromLogits({ logits, task: request.task, request }));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return {
|
|
380
|
+
answers,
|
|
381
|
+
truncated,
|
|
382
|
+
length,
|
|
383
|
+
prompts,
|
|
384
|
+
timings: { totalMs: Math.round(now() - t0), runMs: Math.round(runMs) },
|
|
385
|
+
};
|
|
386
|
+
},
|
|
387
|
+
async dispose() {
|
|
388
|
+
try { await session.release?.(); } catch { /* optional */ }
|
|
389
|
+
},
|
|
390
|
+
};
|
|
391
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -28,6 +28,47 @@ export {
|
|
|
28
28
|
LAYA_TEMP_MIN,
|
|
29
29
|
LAYA_TEMP_MAX,
|
|
30
30
|
} from "./laya.mjs";
|
|
31
|
+
export {
|
|
32
|
+
createStrandsDecider,
|
|
33
|
+
renderStrandsQuestion,
|
|
34
|
+
renderStrandsState,
|
|
35
|
+
renderStrandsContent,
|
|
36
|
+
buildStrandsBatch,
|
|
37
|
+
collateStrandsItems,
|
|
38
|
+
strandsOptionTokenIndices,
|
|
39
|
+
strandsAnswersFromLogits,
|
|
40
|
+
strandsChoiceConfidence,
|
|
41
|
+
strandsScoreConfidence,
|
|
42
|
+
STRANDS_DEFAULT_MODEL,
|
|
43
|
+
STRANDS_DEFAULT_REVISION,
|
|
44
|
+
STRANDS_DEFAULT_FILE,
|
|
45
|
+
STRANDS_TEMPERATURE,
|
|
46
|
+
STRANDS_TEMPERATURE_BY_KIND,
|
|
47
|
+
STRANDS_ORDINAL_SMOOTHING,
|
|
48
|
+
STRANDS_DEFAULT_MAX_LEN,
|
|
49
|
+
STRANDS_MAX_QUESTION_FRACTION,
|
|
50
|
+
STRANDS_KIND,
|
|
51
|
+
} from "./strands.mjs";
|
|
52
|
+
export {
|
|
53
|
+
createBekkoDecider,
|
|
54
|
+
renderBekkoContent,
|
|
55
|
+
renderBekkoRequest,
|
|
56
|
+
tokenizeBekkoRequest,
|
|
57
|
+
collateBekkoDocs,
|
|
58
|
+
bekkoSoftmax,
|
|
59
|
+
bekkoAnswerFromLogits,
|
|
60
|
+
BEKKO_DEFAULT_MODEL,
|
|
61
|
+
BEKKO_DEFAULT_REVISION,
|
|
62
|
+
BEKKO_DEFAULT_SUBDIR,
|
|
63
|
+
BEKKO_68M_MODEL,
|
|
64
|
+
BEKKO_68M_REVISION,
|
|
65
|
+
BEKKO_400M_MODEL,
|
|
66
|
+
BEKKO_400M_REVISION,
|
|
67
|
+
BEKKO_ATTENTION_BUDGET,
|
|
68
|
+
BEKKO_DEFAULT_YES,
|
|
69
|
+
BEKKO_DEFAULT_NO,
|
|
70
|
+
BEKKO_NOUL_DESCRIPTIONS,
|
|
71
|
+
} from "./bekko.mjs";
|
|
31
72
|
export {
|
|
32
73
|
DEFAULT_MODEL,
|
|
33
74
|
DEFAULT_REVISION,
|
package/src/laya.mjs
CHANGED
|
@@ -412,17 +412,22 @@ async function readBytesWithProgress(res, url, onProgress) {
|
|
|
412
412
|
* has no external data (404) or the fetch fails.
|
|
413
413
|
*/
|
|
414
414
|
export async function fetchOnnxExternalData(url, { fetchImpl = fetch, onProgress = null, force = false } = {}) {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
const
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
415
|
+
// Probe `<model>.onnx.data` (laya web-q8 layout) then `<model>.onnx_data`
|
|
416
|
+
// (onnx-community layout); a 404 on both means the model has no external data.
|
|
417
|
+
for (const suffix of [".data", "_data"]) {
|
|
418
|
+
const dataUrl = `${url}${suffix}`;
|
|
419
|
+
const path = `${String(url).split("/").pop()}${suffix}`;
|
|
420
|
+
try {
|
|
421
|
+
// Probe cheaply first: a 404 means "try the next suffix".
|
|
422
|
+
const head = await fetchImpl(dataUrl, { method: "HEAD" }).catch(() => null);
|
|
423
|
+
if (head && !head.ok) continue;
|
|
424
|
+
const data = await fetchCachedBytes(dataUrl, { fetchImpl, onProgress, force, persist: true });
|
|
425
|
+
return { path, data };
|
|
426
|
+
} catch {
|
|
427
|
+
// fall through to the next suffix
|
|
428
|
+
}
|
|
425
429
|
}
|
|
430
|
+
return null;
|
|
426
431
|
}
|
|
427
432
|
|
|
428
433
|
/**
|
package/src/questions.mjs
CHANGED
|
@@ -34,6 +34,6 @@ export function normalizeQuestions(questions) {
|
|
|
34
34
|
throw new TypeError(`questions[${i}]: score needs 2-10 ordered levels`);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
-
return { type, instructions, options };
|
|
37
|
+
return { type, instructions, options, ...(q?.criteria !== undefined ? { criteria: q.criteria } : {}) };
|
|
38
38
|
});
|
|
39
39
|
}
|
package/src/registry.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* registry.mjs — multi-family runtime for JEV-style typed-decision models.
|
|
3
3
|
*
|
|
4
|
-
* jev-web ships
|
|
5
|
-
* ModernBERT encoder + typed head
|
|
6
|
-
*
|
|
4
|
+
* jev-web ships three families today (open-jev's fused DeBERTa graph, Laya's
|
|
5
|
+
* ModernBERT encoder + typed head, Strands Decider 2B's Qwen torso + pointer
|
|
6
|
+
* head, and Bekko System One v0's cross-encoder) and any third party can add
|
|
7
|
+
* its own without touching core:
|
|
7
8
|
*
|
|
8
9
|
* registerDecisionFamily("my-model", {
|
|
9
10
|
* defaults: { model: "org/my-typed-decision-ONNX", revision: "main" },
|
|
@@ -27,6 +28,16 @@ import {
|
|
|
27
28
|
LAYA_DEFAULT_REVISION,
|
|
28
29
|
LAYA_DEFAULT_SUBFOLDER,
|
|
29
30
|
} from "./laya.mjs";
|
|
31
|
+
import {
|
|
32
|
+
createStrandsDecider,
|
|
33
|
+
STRANDS_DEFAULT_MODEL,
|
|
34
|
+
STRANDS_DEFAULT_REVISION,
|
|
35
|
+
} from "./strands.mjs";
|
|
36
|
+
import {
|
|
37
|
+
createBekkoDecider,
|
|
38
|
+
BEKKO_DEFAULT_MODEL,
|
|
39
|
+
BEKKO_DEFAULT_REVISION,
|
|
40
|
+
} from "./bekko.mjs";
|
|
30
41
|
|
|
31
42
|
const families = new Map();
|
|
32
43
|
|
|
@@ -67,6 +78,22 @@ registerDecisionFamily("laya", {
|
|
|
67
78
|
create: createLayaDecider,
|
|
68
79
|
});
|
|
69
80
|
|
|
81
|
+
registerDecisionFamily("strands-decider", {
|
|
82
|
+
defaults: {
|
|
83
|
+
model: STRANDS_DEFAULT_MODEL,
|
|
84
|
+
revision: STRANDS_DEFAULT_REVISION,
|
|
85
|
+
},
|
|
86
|
+
create: createStrandsDecider,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
registerDecisionFamily("bekko", {
|
|
90
|
+
defaults: {
|
|
91
|
+
model: BEKKO_DEFAULT_MODEL,
|
|
92
|
+
revision: BEKKO_DEFAULT_REVISION,
|
|
93
|
+
},
|
|
94
|
+
create: createBekkoDecider,
|
|
95
|
+
});
|
|
96
|
+
|
|
70
97
|
/**
|
|
71
98
|
* Create a decision runtime, preferring `family` and falling back in order.
|
|
72
99
|
* @param {{family?:string, fallback?:string[], onFallback?:Function}} [opts]
|