jev-web 0.2.0 → 0.3.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 +30 -0
- package/package.json +1 -1
- package/src/index.mjs +4 -0
- package/src/laya.mjs +118 -10
- package/src/registry.mjs +102 -0
- package/src/session.mjs +20 -1
package/README.md
CHANGED
|
@@ -91,6 +91,36 @@ take `input_ids, attention_mask, seg, pair_q, pair_opt`, and return per-pair
|
|
|
91
91
|
`logits`. `typedMarkerIds()` fails loudly when a repo is missing them.
|
|
92
92
|
|
|
93
93
|
|
|
94
|
+
## Multiple families (registry)
|
|
95
|
+
|
|
96
|
+
`jev-web` is a multi-family runtime: `createDecisionRuntime({ family, fallback })`
|
|
97
|
+
tries the requested family and then any fallbacks, returning the shared contract
|
|
98
|
+
`{ info, decide(state, questions), dispose? }`:
|
|
99
|
+
|
|
100
|
+
```js
|
|
101
|
+
import { createDecisionRuntime, listDecisionFamilies } from "jev-web";
|
|
102
|
+
|
|
103
|
+
listDecisionFamilies(); // ["open-jev", "laya"]
|
|
104
|
+
const runtime = await createDecisionRuntime({
|
|
105
|
+
family: "laya",
|
|
106
|
+
fallback: ["open-jev"],
|
|
107
|
+
onFallback: ({ family, error }) => console.warn(family, error),
|
|
108
|
+
});
|
|
109
|
+
const { answers, truncated, length, prompts, timings } = await runtime.decide(state, questions);
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Third-party families plug in without touching core:
|
|
113
|
+
|
|
114
|
+
```js
|
|
115
|
+
registerDecisionFamily("my-model", {
|
|
116
|
+
defaults: { model: "org/my-typed-decision-ONNX", revision: "main" },
|
|
117
|
+
create: async (opts) => ({ info, decide, dispose }),
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`decide()` returns `prompts` (decoded input sequence(s), for audit/debug) and
|
|
122
|
+
`timings` (per-stage ms) alongside the answers.
|
|
123
|
+
|
|
94
124
|
## Laya family (ModernBERT encoder + typed head)
|
|
95
125
|
|
|
96
126
|
`createLayaDecider(options)` runs the [Laya](https://huggingface.co/convaiinnovations/laya)
|
package/package.json
CHANGED
package/src/index.mjs
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// jev-web — public API (typed decisions in the browser).
|
|
2
2
|
export { createDecider } from "./session.mjs";
|
|
3
|
+
export {
|
|
4
|
+
registerDecisionFamily, createDecisionRuntime,
|
|
5
|
+
listDecisionFamilies, getDecisionFamily,
|
|
6
|
+
} from "./registry.mjs";
|
|
3
7
|
export { normalizeQuestions, QUESTION_TYPES, NOUL_OPTIONS } from "./questions.mjs";
|
|
4
8
|
export { buildDecisionInput, typedMarkerIds, DecisionInputTooLongError } from "./encode.mjs";
|
|
5
9
|
export { answersFromScores, softmaxWithTemperature } from "./answers.mjs";
|
package/src/laya.mjs
CHANGED
|
@@ -206,6 +206,69 @@ export function layaAnswersFromLogits({
|
|
|
206
206
|
return answers;
|
|
207
207
|
}
|
|
208
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Fetch a JSON file with optional byte progress (fetch/Response compatible).
|
|
211
|
+
* Falls back to `res.json()` when the body isn't streamable (Node fakes/tests).
|
|
212
|
+
*/
|
|
213
|
+
async function fetchJson(url, file, { fetchImpl, onProgress }) {
|
|
214
|
+
const res = await fetchImpl(url);
|
|
215
|
+
if (!res.ok) throw new Error(`tokenizer ${file}: HTTP ${res.status}`);
|
|
216
|
+
const total = Number(res.headers?.get?.("content-length")) || 0;
|
|
217
|
+
if (onProgress && res.body?.getReader) {
|
|
218
|
+
const reader = res.body.getReader();
|
|
219
|
+
const chunks = [];
|
|
220
|
+
let loaded = 0;
|
|
221
|
+
for (;;) {
|
|
222
|
+
const { done, value } = await reader.read();
|
|
223
|
+
if (done) break;
|
|
224
|
+
chunks.push(value);
|
|
225
|
+
loaded += value.length;
|
|
226
|
+
onProgress({ phase: "tokenizer", file, loaded, total: total || null });
|
|
227
|
+
}
|
|
228
|
+
const buf = new Uint8Array(loaded);
|
|
229
|
+
let off = 0;
|
|
230
|
+
for (const c of chunks) { buf.set(c, off); off += c.length; }
|
|
231
|
+
return JSON.parse(new TextDecoder().decode(buf));
|
|
232
|
+
}
|
|
233
|
+
return res.json();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Load a Laya tokenizer directly from its files.
|
|
238
|
+
*
|
|
239
|
+
* transformers.js `AutoTokenizer.from_pretrained` ignores `subfolder`, and the
|
|
240
|
+
* Laya web export keeps its tokenizer under `v1/` (and its root has no
|
|
241
|
+
* `tokenizer_config.json`), so the library path throws
|
|
242
|
+
* "Cannot read properties of undefined (reading 'tokenizer_class')".
|
|
243
|
+
* Fetch the two JSON files ourselves and instantiate the class the config
|
|
244
|
+
* names (e.g. `PreTrainedTokenizerFast` -> `PreTrainedTokenizer`).
|
|
245
|
+
*
|
|
246
|
+
* @param {{urlBase:string, fetchImpl?:Function, transformers?:object,
|
|
247
|
+
* onProgress?:Function}} spec `urlBase` must end with "/".
|
|
248
|
+
* @returns {Promise<object>} tokenizer instance
|
|
249
|
+
*/
|
|
250
|
+
export async function loadLayaTokenizer({
|
|
251
|
+
urlBase,
|
|
252
|
+
fetchImpl = fetch,
|
|
253
|
+
transformers = null,
|
|
254
|
+
onProgress = null,
|
|
255
|
+
} = {}) {
|
|
256
|
+
if (!urlBase) throw new TypeError("loadLayaTokenizer: urlBase is required");
|
|
257
|
+
const base = urlBase.endsWith("/") ? urlBase : `${urlBase}/`;
|
|
258
|
+
const [tokenizerJSON, tokenizerConfig] = await Promise.all([
|
|
259
|
+
fetchJson(`${base}tokenizer.json`, "tokenizer.json", { fetchImpl, onProgress }),
|
|
260
|
+
fetchJson(`${base}tokenizer_config.json`, "tokenizer_config.json", { fetchImpl, onProgress }),
|
|
261
|
+
]);
|
|
262
|
+
const tf = transformers ?? (await import("@huggingface/transformers"));
|
|
263
|
+
const className = String(tokenizerConfig?.tokenizer_class ?? "PreTrainedTokenizer")
|
|
264
|
+
.replace(/Fast$/, "");
|
|
265
|
+
const Cls = tf[className] ?? tf.PreTrainedTokenizer;
|
|
266
|
+
if (typeof Cls !== "function") {
|
|
267
|
+
throw new Error(`loadLayaTokenizer: no tokenizer class for "${className}"`);
|
|
268
|
+
}
|
|
269
|
+
return new Cls(tokenizerJSON, tokenizerConfig);
|
|
270
|
+
}
|
|
271
|
+
|
|
209
272
|
/** Wrap a transformers.js tokenizer into the tiny adapter laya needs. */
|
|
210
273
|
export function layaTokenizerAdapter(tok) {
|
|
211
274
|
const idOf = (prop, text) => {
|
|
@@ -216,6 +279,13 @@ export function layaTokenizerAdapter(tok) {
|
|
|
216
279
|
};
|
|
217
280
|
return {
|
|
218
281
|
encode: (text) => Array.from(tok(text, { add_special_tokens: false }).input_ids.data, Number),
|
|
282
|
+
decode: (ids) => {
|
|
283
|
+
try {
|
|
284
|
+
return tok.decode(Array.from(ids), { skip_special_tokens: false });
|
|
285
|
+
} catch {
|
|
286
|
+
return Array.from(ids).join(" ");
|
|
287
|
+
}
|
|
288
|
+
},
|
|
219
289
|
ids: {
|
|
220
290
|
cls: idOf("cls_token_id", "[CLS]"),
|
|
221
291
|
sep: idOf("sep_token_id", "[SEP]"),
|
|
@@ -225,6 +295,8 @@ export function layaTokenizerAdapter(tok) {
|
|
|
225
295
|
};
|
|
226
296
|
}
|
|
227
297
|
|
|
298
|
+
const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
299
|
+
|
|
228
300
|
const hfUrl = (model, revision, file) =>
|
|
229
301
|
`https://huggingface.co/${model}/resolve/${revision ? `${revision}/` : ""}${file}`;
|
|
230
302
|
|
|
@@ -249,6 +321,7 @@ export async function createLayaDecider({
|
|
|
249
321
|
tokenizerSubfolder = null,
|
|
250
322
|
ort = null,
|
|
251
323
|
transformers = null,
|
|
324
|
+
fetchImpl = fetch,
|
|
252
325
|
wasmPaths = null,
|
|
253
326
|
device = "auto",
|
|
254
327
|
// (url, sessionOptions) => Promise<InferenceSession>. Hosts that need custom
|
|
@@ -276,15 +349,28 @@ export async function createLayaDecider({
|
|
|
276
349
|
const report = (phase) => (p) =>
|
|
277
350
|
onProgress?.({ phase, file: p?.file ?? null, loaded: p?.loaded ?? null, total: p?.total ?? null });
|
|
278
351
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
352
|
+
let tok;
|
|
353
|
+
if (tokenizerModel) {
|
|
354
|
+
// Host-provided path/repo (local dir in Node, or a repo whose tokenizer
|
|
355
|
+
// lives at the root) — use the library loader.
|
|
356
|
+
const tokSubfolder = tokenizerSubfolder === null
|
|
357
|
+
? undefined
|
|
358
|
+
: (tokenizerSubfolder ?? subfolder);
|
|
359
|
+
tok = await tf.AutoTokenizer.from_pretrained(tokenizerModel, {
|
|
360
|
+
revision: tokenizerSubfolder ? undefined : revision,
|
|
361
|
+
...(tokSubfolder ? { subfolder: tokSubfolder } : {}),
|
|
362
|
+
progress_callback: report("tokenizer"),
|
|
363
|
+
});
|
|
364
|
+
} else {
|
|
365
|
+
// Default: the pinned web export keeps the tokenizer under `v1/`, which
|
|
366
|
+
// AutoTokenizer cannot address (no `subfolder` support) — load it directly.
|
|
367
|
+
tok = await loadLayaTokenizer({
|
|
368
|
+
urlBase: hfUrl(model, revision, `${subfolder}/`),
|
|
369
|
+
transformers: tf,
|
|
370
|
+
fetchImpl,
|
|
371
|
+
onProgress,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
288
374
|
const adapter = layaTokenizerAdapter(tok);
|
|
289
375
|
|
|
290
376
|
const encUrl = encoderUrl ?? hfUrl(model, revision, `${subfolder}/encoder_q8.onnx`);
|
|
@@ -334,22 +420,27 @@ export async function createLayaDecider({
|
|
|
334
420
|
Object.entries(temperatureByOptions).map(([k, v]) => [k, clampTemperature(v)])),
|
|
335
421
|
},
|
|
336
422
|
async decide(state, questions) {
|
|
423
|
+
const t0 = now();
|
|
337
424
|
const normalized = normalizeQuestions(questions);
|
|
338
425
|
const items = normalized.map((q) =>
|
|
339
426
|
buildLayaSequence({ encode: adapter.encode, ids: adapter.ids, state, question: q, maxLen, headMaxLen, maxPrefixes }));
|
|
340
427
|
const b = collateLayaItems(items, adapter.pad);
|
|
341
428
|
|
|
429
|
+
const tEncode = now();
|
|
342
430
|
const encOut = await encoder.run({
|
|
343
431
|
input_ids: int64(b.inputIds, [b.n, b.L]),
|
|
344
432
|
attention_mask: int64(b.attention, [b.n, b.L]),
|
|
345
433
|
});
|
|
434
|
+
const encoderMs = now() - tEncode;
|
|
346
435
|
const hidden = encOut.hidden ?? encOut.last_hidden_state ?? encOut[Object.keys(encOut)[0]];
|
|
347
436
|
|
|
348
437
|
// The q8 web head is exported batch-1 only (despite dynamic batch
|
|
349
438
|
// axes), so run it once per question over the shared encoder output.
|
|
350
439
|
// The encoder — the expensive half — still runs as one batched pass.
|
|
351
440
|
const headLogits = [];
|
|
441
|
+
const headPerQuestionMs = [];
|
|
352
442
|
const markerCounts = items.map((it) => it.markers.length);
|
|
443
|
+
const tHead = now();
|
|
353
444
|
for (let i = 0; i < b.n; i++) {
|
|
354
445
|
const k = markerCounts[i];
|
|
355
446
|
const row = hidden.data.subarray(i * b.L * hidden.dims[2], (i + 1) * b.L * hidden.dims[2]);
|
|
@@ -357,6 +448,7 @@ export async function createLayaDecider({
|
|
|
357
448
|
const rowMask = b.attention.subarray(i * b.L, (i + 1) * b.L);
|
|
358
449
|
const rowPos = b.markerPos.subarray(i * b.M, i * b.M + k);
|
|
359
450
|
const rowMarkerMask = b.markerMask.subarray(i * b.M, i * b.M + k);
|
|
451
|
+
const tHeadTotal = now();
|
|
360
452
|
const headOut = await head.run({
|
|
361
453
|
hidden: rowHidden,
|
|
362
454
|
attention_mask: int64(rowMask, [1, b.L]),
|
|
@@ -366,6 +458,7 @@ export async function createLayaDecider({
|
|
|
366
458
|
});
|
|
367
459
|
const rowLogits = headOut.logits ?? headOut[Object.keys(headOut)[0]];
|
|
368
460
|
headLogits.push(...Array.from(rowLogits.data, Number));
|
|
461
|
+
headPerQuestionMs.push(Math.round(now() - tHeadTotal));
|
|
369
462
|
}
|
|
370
463
|
|
|
371
464
|
const answers = layaAnswersFromLogits({
|
|
@@ -376,7 +469,22 @@ export async function createLayaDecider({
|
|
|
376
469
|
temperatureByOptions,
|
|
377
470
|
});
|
|
378
471
|
const length = Number(b.attention.reduce((a, v) => a + v, 0n));
|
|
379
|
-
|
|
472
|
+
// Exact, human-readable view of what the encoder consumed: each
|
|
473
|
+
// question's token sequence decoded back to text (special tokens kept).
|
|
474
|
+
const prompts = items.map((it) => adapter.decode(it.ids));
|
|
475
|
+
const headMs = now() - tHead;
|
|
476
|
+
return {
|
|
477
|
+
answers,
|
|
478
|
+
truncated: items.some((it) => it.ids.length >= maxLen),
|
|
479
|
+
length,
|
|
480
|
+
prompts,
|
|
481
|
+
timings: {
|
|
482
|
+
totalMs: Math.round(now() - t0),
|
|
483
|
+
encoderMs: Math.round(encoderMs),
|
|
484
|
+
headMs: Math.round(headMs),
|
|
485
|
+
headPerQuestionMs,
|
|
486
|
+
},
|
|
487
|
+
};
|
|
380
488
|
},
|
|
381
489
|
async dispose() {
|
|
382
490
|
try { await encoder.release?.(); } catch { /* optional */ }
|
package/src/registry.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registry.mjs — multi-family runtime for JEV-style typed-decision models.
|
|
3
|
+
*
|
|
4
|
+
* jev-web ships two families today (open-jev's fused DeBERTa graph and Laya's
|
|
5
|
+
* ModernBERT encoder + typed head) and any third party can add its own without
|
|
6
|
+
* touching core:
|
|
7
|
+
*
|
|
8
|
+
* registerDecisionFamily("my-model", {
|
|
9
|
+
* defaults: { model: "org/my-typed-decision-ONNX", revision: "main" },
|
|
10
|
+
* create: async (opts) => ({ info, decide(state, questions), dispose? }),
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const runtime = await createDecisionRuntime({ family: "my-model" });
|
|
14
|
+
*
|
|
15
|
+
* `createDecisionRuntime` tries the requested family, then any `fallback`
|
|
16
|
+
* families in order, reporting each failure through `onFallback`. The returned
|
|
17
|
+
* object always exposes the shared contract:
|
|
18
|
+
* { info: { family, ... }, decide(state, questions) -> DecideResult, dispose? }
|
|
19
|
+
* where DecideResult is `{ answers, truncated, length, prompts?, timings? }`.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { createDecider } from "./session.mjs";
|
|
23
|
+
import { DEFAULT_MODEL, DEFAULT_REVISION } from "./config.mjs";
|
|
24
|
+
import {
|
|
25
|
+
createLayaDecider,
|
|
26
|
+
LAYA_DEFAULT_MODEL,
|
|
27
|
+
LAYA_DEFAULT_REVISION,
|
|
28
|
+
LAYA_DEFAULT_SUBFOLDER,
|
|
29
|
+
} from "./laya.mjs";
|
|
30
|
+
|
|
31
|
+
const families = new Map();
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Register (or replace) a decision family.
|
|
35
|
+
* @param {string} id short stable identifier, e.g. "open-jev"
|
|
36
|
+
* @param {{create:(opts:object)=>Promise<object>, defaults?:object}} descriptor
|
|
37
|
+
*/
|
|
38
|
+
export function registerDecisionFamily(id, descriptor) {
|
|
39
|
+
if (!id || typeof id !== "string") throw new TypeError("registerDecisionFamily: id is required");
|
|
40
|
+
if (!descriptor || typeof descriptor.create !== "function") {
|
|
41
|
+
throw new TypeError(`registerDecisionFamily(${id}): descriptor.create is required`);
|
|
42
|
+
}
|
|
43
|
+
families.set(id, { defaults: {}, ...descriptor });
|
|
44
|
+
return id;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @returns {string[]} registered family ids in registration order. */
|
|
48
|
+
export function listDecisionFamilies() {
|
|
49
|
+
return [...families.keys()];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getDecisionFamily(id) {
|
|
53
|
+
return families.get(id) ?? null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
registerDecisionFamily("open-jev", {
|
|
57
|
+
defaults: { model: DEFAULT_MODEL, revision: DEFAULT_REVISION },
|
|
58
|
+
create: createDecider,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
registerDecisionFamily("laya", {
|
|
62
|
+
defaults: {
|
|
63
|
+
model: LAYA_DEFAULT_MODEL,
|
|
64
|
+
revision: LAYA_DEFAULT_REVISION,
|
|
65
|
+
subfolder: LAYA_DEFAULT_SUBFOLDER,
|
|
66
|
+
},
|
|
67
|
+
create: createLayaDecider,
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Create a decision runtime, preferring `family` and falling back in order.
|
|
72
|
+
* @param {{family?:string, fallback?:string[], onFallback?:Function}} [opts]
|
|
73
|
+
* @returns {Promise<{info:object, decide:Function, dispose?:Function}>}
|
|
74
|
+
*/
|
|
75
|
+
export async function createDecisionRuntime({
|
|
76
|
+
family = "laya",
|
|
77
|
+
fallback = [],
|
|
78
|
+
onFallback = null,
|
|
79
|
+
...opts
|
|
80
|
+
} = {}) {
|
|
81
|
+
const order = [family, ...(Array.isArray(fallback) ? fallback : [])].filter(Boolean);
|
|
82
|
+
let lastError = null;
|
|
83
|
+
for (const id of order) {
|
|
84
|
+
const descriptor = families.get(id);
|
|
85
|
+
if (!descriptor) {
|
|
86
|
+
lastError = new Error(`unknown decision family "${id}"`);
|
|
87
|
+
onFallback?.({ family: id, error: lastError });
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const runtime = await descriptor.create({ ...descriptor.defaults, ...opts });
|
|
92
|
+
return {
|
|
93
|
+
...runtime,
|
|
94
|
+
info: { family: id, ...(runtime?.info ?? {}) },
|
|
95
|
+
};
|
|
96
|
+
} catch (error) {
|
|
97
|
+
lastError = error;
|
|
98
|
+
onFallback?.({ family: id, error });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw lastError ?? new Error("createDecisionRuntime: no family could be created");
|
|
102
|
+
}
|
package/src/session.mjs
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
// builds). Everything heavy stays out of module scope.
|
|
6
6
|
|
|
7
7
|
import { normalizeQuestions } from "./questions.mjs";
|
|
8
|
+
|
|
9
|
+
const now = () => (typeof performance !== "undefined" ? performance.now() : Date.now());
|
|
8
10
|
import { buildDecisionInput, typedMarkerIds } from "./encode.mjs";
|
|
9
11
|
import { answersFromScores } from "./answers.mjs";
|
|
10
12
|
import {
|
|
@@ -77,6 +79,7 @@ export async function createDecider({
|
|
|
77
79
|
|
|
78
80
|
return {
|
|
79
81
|
info: {
|
|
82
|
+
family: "open-jev",
|
|
80
83
|
model,
|
|
81
84
|
revision,
|
|
82
85
|
device: resolvedDevice,
|
|
@@ -88,6 +91,7 @@ export async function createDecider({
|
|
|
88
91
|
configSource: remote.source,
|
|
89
92
|
},
|
|
90
93
|
async decide(state, questions) {
|
|
94
|
+
const t0 = now();
|
|
91
95
|
const normalized = normalizeQuestions(questions);
|
|
92
96
|
const tokenized = normalized.map((q) => ({
|
|
93
97
|
type: q.type,
|
|
@@ -109,7 +113,9 @@ export async function createDecider({
|
|
|
109
113
|
pair_q: i64(input.pairQ, [1, input.pairQ.length]),
|
|
110
114
|
pair_opt: i64(input.pairOpt, [1, input.pairOpt.length]),
|
|
111
115
|
};
|
|
116
|
+
const tInfer = now();
|
|
112
117
|
const out = await net(feeds);
|
|
118
|
+
const inferMs = now() - tInfer;
|
|
113
119
|
const logits = out?.logits;
|
|
114
120
|
if (!logits) throw new Error("model output has no `logits` — not a typed-decision export?");
|
|
115
121
|
const scores = Array.from(logits.to("float32").data, Number);
|
|
@@ -118,7 +124,20 @@ export async function createDecider({
|
|
|
118
124
|
groups: input.groups,
|
|
119
125
|
temperature: effective.temperature,
|
|
120
126
|
});
|
|
121
|
-
|
|
127
|
+
// Exact view of the single fused sequence the graph consumed.
|
|
128
|
+
let prompt;
|
|
129
|
+
try {
|
|
130
|
+
prompt = tokenizer.decode(input.inputIds, { skip_special_tokens: false });
|
|
131
|
+
} catch {
|
|
132
|
+
prompt = input.inputIds.join(" ");
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
answers,
|
|
136
|
+
truncated: input.truncated,
|
|
137
|
+
length: input.length,
|
|
138
|
+
prompts: [prompt],
|
|
139
|
+
timings: { totalMs: Math.round(now() - t0), inferMs: Math.round(inferMs) },
|
|
140
|
+
};
|
|
122
141
|
},
|
|
123
142
|
async decideMany(cases) {
|
|
124
143
|
const out = [];
|