webml-kit 0.4.0 → 0.4.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.
Files changed (62) hide show
  1. package/package.json +3 -3
  2. package/dist/cache.d.ts +0 -46
  3. package/dist/cache.d.ts.map +0 -1
  4. package/dist/cache.js +0 -161
  5. package/dist/cache.js.map +0 -1
  6. package/dist/decision.d.ts +0 -182
  7. package/dist/decision.d.ts.map +0 -1
  8. package/dist/decision.js +0 -789
  9. package/dist/decision.js.map +0 -1
  10. package/dist/device.d.ts +0 -86
  11. package/dist/device.d.ts.map +0 -1
  12. package/dist/device.js +0 -195
  13. package/dist/device.js.map +0 -1
  14. package/dist/gpu-recovery.d.ts +0 -61
  15. package/dist/gpu-recovery.d.ts.map +0 -1
  16. package/dist/gpu-recovery.js +0 -105
  17. package/dist/gpu-recovery.js.map +0 -1
  18. package/dist/hub.d.ts +0 -97
  19. package/dist/hub.d.ts.map +0 -1
  20. package/dist/hub.js +0 -142
  21. package/dist/hub.js.map +0 -1
  22. package/dist/index.d.ts +0 -63
  23. package/dist/index.d.ts.map +0 -1
  24. package/dist/index.js +0 -65
  25. package/dist/index.js.map +0 -1
  26. package/dist/inputs.d.ts +0 -27
  27. package/dist/inputs.d.ts.map +0 -1
  28. package/dist/inputs.js +0 -155
  29. package/dist/inputs.js.map +0 -1
  30. package/dist/loader.d.ts +0 -100
  31. package/dist/loader.d.ts.map +0 -1
  32. package/dist/loader.js +0 -246
  33. package/dist/loader.js.map +0 -1
  34. package/dist/model-client.d.ts +0 -149
  35. package/dist/model-client.d.ts.map +0 -1
  36. package/dist/model-client.js +0 -318
  37. package/dist/model-client.js.map +0 -1
  38. package/dist/model-worker.d.ts +0 -16
  39. package/dist/model-worker.d.ts.map +0 -1
  40. package/dist/model-worker.js +0 -340
  41. package/dist/model-worker.js.map +0 -1
  42. package/dist/onnx-pipeline.d.ts +0 -20
  43. package/dist/onnx-pipeline.d.ts.map +0 -1
  44. package/dist/onnx-pipeline.js +0 -462
  45. package/dist/onnx-pipeline.js.map +0 -1
  46. package/dist/pipelines/index.d.ts +0 -28
  47. package/dist/pipelines/index.d.ts.map +0 -1
  48. package/dist/pipelines/index.js +0 -140
  49. package/dist/pipelines/index.js.map +0 -1
  50. package/dist/shim-ort.d.ts +0 -6
  51. package/dist/shim-ort.d.ts.map +0 -1
  52. package/dist/shim-ort.js +0 -38
  53. package/dist/shim-ort.js.map +0 -1
  54. package/dist/streaming.d.ts +0 -55
  55. package/dist/streaming.d.ts.map +0 -1
  56. package/dist/streaming.js +0 -142
  57. package/dist/streaming.js.map +0 -1
  58. package/dist/types.d.ts +0 -200
  59. package/dist/types.d.ts.map +0 -1
  60. package/dist/types.js +0 -3
  61. package/dist/types.js.map +0 -1
  62. package/dist/webml-kit.browser.js +0 -2568
package/dist/decision.js DELETED
@@ -1,789 +0,0 @@
1
- /**
2
- * Jev System One / OpenJev Decision Engine for webml-kit.
3
- *
4
- * Provides direct logit readout choice evaluation, direct binary evaluation (noul),
5
- * and continuous scoring evaluation over structured application state.
6
- *
7
- * Supports OpenJev models (minicpm5-2b, qwen3-0.6b, qwen3.5-4b) using @wllama/wllama
8
- * in browser/worker environments with WebGPU/WASM, and a high-performance,
9
- * calibrated zero-dependency heuristic fallback for Node.js, test, or offline environments.
10
- *
11
- * @packageDocumentation
12
- */
13
- export const OPENJEV_MODELS = {
14
- 'minicpm5-2b': {
15
- id: 'minicpm5-2b',
16
- name: 'MiniCPM 5 2B (OpenJev)',
17
- url: 'https://huggingface.co/openjev/MiniCPM-2B-GGUF/resolve/main/minicpm-2b-q4_k_m.gguf',
18
- family: 'minicpm',
19
- sizeMB: 1250,
20
- },
21
- 'qwen3-0.6b': {
22
- id: 'qwen3-0.6b',
23
- name: 'Qwen 3 0.6B (OpenJev)',
24
- url: 'https://huggingface.co/openjev/Qwen3-0.6B-GGUF/resolve/main/qwen3-0.6b-q4_k_m.gguf',
25
- family: 'qwen',
26
- sizeMB: 480,
27
- },
28
- 'qwen3.5-4b': {
29
- id: 'qwen3.5-4b',
30
- name: 'Qwen 3.5 4B (OpenJev)',
31
- url: 'https://huggingface.co/openjev/Qwen3.5-4B-GGUF/resolve/main/qwen3.5-4b-q4_k_m.gguf',
32
- family: 'qwen',
33
- sizeMB: 2450,
34
- },
35
- };
36
- // ─── Semantic Concept Mapping for Zero-Dep Heuristic ───
37
- const CONCEPT_KEYWORDS = {
38
- positive: [
39
- 'good', 'great', 'excellent', 'amazing', 'love', 'positive', 'satisfied',
40
- 'helpful', 'fast', 'smooth', 'awesome', 'best', 'wonderful', 'safe',
41
- 'compliant', 'approve', 'approved', 'pass', 'passed', 'valid', 'success'
42
- ],
43
- negative: [
44
- 'bad', 'terrible', 'awful', 'horrible', 'hate', 'negative', 'poor',
45
- 'worst', 'broken', 'disappointing', 'failed', 'fail', 'reject', 'rejected',
46
- 'deny', 'denied', 'invalid', 'violate', 'violation', 'disallow', 'fraud'
47
- ],
48
- urgent: [
49
- 'urgent', 'critical', 'emergency', 'asap', 'immediately', 'blocking',
50
- 'p0', 'p1', 'severe', 'fatal', 'escalate', 'outage', 'unacceptable',
51
- 'furious', 'enraged', 'losing'
52
- ],
53
- moderate: [
54
- 'moderate', 'medium', 'normal', 'standard', 'p2', 'routine', 'average',
55
- 'intermediate', 'fair', 'regular'
56
- ],
57
- low: [
58
- 'low', 'minor', 'trivial', 'easy', 'p3', 'p4', 'info', 'minimal',
59
- 'casual', 'beginner', 'none', 'negligible'
60
- ],
61
- bug: [
62
- 'bug', 'error', '500', 'crash', 'fail', 'failing', 'exception', 'broken',
63
- 'traceback', 'glitch', 'unexpected', 'hang', 'freeze', 'defect'
64
- ],
65
- security: [
66
- 'security', 'unauthorized', 'breach', 'vulnerability', 'hack', 'exploit',
67
- 'leak', 'compromise', 'threat', 'suspicious', 'malicious', 'token', 'auth'
68
- ],
69
- billing: [
70
- 'billing', 'bill', 'charge', 'invoice', 'payment', 'payout', 'refund',
71
- 'subscription', 'fee', 'price', 'cost', 'credit', 'tax', 'receipt'
72
- ],
73
- technical: [
74
- 'code', 'ai', 'software', 'data', 'model', 'api', 'server', 'database',
75
- 'endpoint', 'infra', 'function', 'developer', 'algorithm'
76
- ],
77
- toxicity: [
78
- 'toxic', 'idiot', 'stupid', 'hate', 'scam', 'threat', 'abuse', 'harass',
79
- 'offensive', 'vulgar', 'kill', 'attack'
80
- ],
81
- };
82
- function stringifyState(state) {
83
- if (state === null || state === undefined)
84
- return '';
85
- if (typeof state === 'string')
86
- return state;
87
- try {
88
- return JSON.stringify(state, null, 2);
89
- }
90
- catch {
91
- return String(state);
92
- }
93
- }
94
- function wordMatch(text, word) {
95
- if (!word || !text)
96
- return false;
97
- if (word.includes(' '))
98
- return text.includes(word);
99
- return new RegExp('(^|[^a-z0-9])' + word + '([^a-z0-9]|$)', 'i').test(text);
100
- }
101
- function extractKeywords(text) {
102
- const stopWords = new Set([
103
- 'a', 'an', 'and', 'are', 'as', 'at', 'be', 'by', 'for', 'from',
104
- 'has', 'he', 'in', 'is', 'it', 'its', 'of', 'on', 'that', 'the',
105
- 'to', 'was', 'were', 'will', 'with', 'this', 'your', 'what', 'which'
106
- ]);
107
- return text
108
- .toLowerCase()
109
- .replace(/[^a-z0-9\s]/g, ' ')
110
- .split(/\s+/)
111
- .filter((w) => w.length > 2 && !stopWords.has(w));
112
- }
113
- export function normalizeOptions(rawOptions) {
114
- if (!rawOptions || typeof rawOptions !== 'object') {
115
- throw new Error('options must provide at least 2 distinct choices (either array or key-description record)');
116
- }
117
- let entries = [];
118
- if (Array.isArray(rawOptions)) {
119
- entries = rawOptions
120
- .map((opt) => String(opt).trim())
121
- .filter((opt) => opt.length > 0)
122
- .map((opt) => ({ key: opt, description: opt }));
123
- }
124
- else {
125
- entries = Object.entries(rawOptions).map(([key, desc]) => ({
126
- key: String(key).trim(),
127
- description: String(desc).trim() || String(key).trim(),
128
- }));
129
- }
130
- if (entries.length < 2) {
131
- throw new Error('options must provide at least 2 distinct choices (either array or key-description record)');
132
- }
133
- return entries;
134
- }
135
- export function normalizeCriteria(rawCriteria) {
136
- if (!rawCriteria || typeof rawCriteria !== 'object') {
137
- throw new Error('criteria must contain at least 2 levels (array or criteria record)');
138
- }
139
- let levels = [];
140
- if (Array.isArray(rawCriteria)) {
141
- levels = rawCriteria
142
- .map((item, idx) => ({
143
- index: idx,
144
- key: String(item).trim(),
145
- description: String(item).trim(),
146
- }))
147
- .filter((lvl) => lvl.key.length > 0);
148
- }
149
- else {
150
- const keys = Object.keys(rawCriteria);
151
- const areNumeric = keys.every((k) => !isNaN(Number(k)));
152
- if (areNumeric) {
153
- keys.sort((a, b) => Number(a) - Number(b));
154
- }
155
- levels = keys.map((key, idx) => ({
156
- index: idx,
157
- key,
158
- description: String(rawCriteria[key]).trim() || key,
159
- }));
160
- }
161
- if (levels.length < 2) {
162
- throw new Error('criteria must contain at least 2 levels (array or criteria record)');
163
- }
164
- return levels;
165
- }
166
- export function validateModelOptions(options) {
167
- const modelPreset = options.model ?? 'qwen3-0.6b';
168
- const mode = options.mode ?? 'auto';
169
- if (!['auto', 'wllama', 'heuristic'].includes(mode)) {
170
- throw new Error(`Invalid mode "${mode}". Supported modes: auto, wllama, heuristic`);
171
- }
172
- if (options.noulThreshold !== undefined) {
173
- if (typeof options.noulThreshold !== 'number' || options.noulThreshold < 0 || options.noulThreshold > 1) {
174
- throw new Error('threshold must be between 0 and 1');
175
- }
176
- }
177
- if (modelPreset in OPENJEV_MODELS) {
178
- return {
179
- modelId: modelPreset,
180
- modelUrl: options.modelUrl || OPENJEV_MODELS[modelPreset].url,
181
- mode,
182
- };
183
- }
184
- // Custom model URL / ID
185
- if (options.modelUrl || modelPreset.includes('/') || modelPreset.startsWith('http')) {
186
- return {
187
- modelId: modelPreset,
188
- modelUrl: options.modelUrl || modelPreset,
189
- mode,
190
- };
191
- }
192
- throw new Error(`Unknown model preset "${modelPreset}". Supported presets: ${Object.keys(OPENJEV_MODELS).join(', ')} (or provide a custom modelUrl)`);
193
- }
194
- // ─── Zero-Dependency Heuristic Decision Engine ───
195
- export class HeuristicDecisionEngine {
196
- model;
197
- isHeuristic = true;
198
- _isLoaded = false;
199
- options;
200
- constructor(options = {}) {
201
- const validated = validateModelOptions(options);
202
- this.model = validated.modelId;
203
- this.options = options;
204
- }
205
- get isLoaded() {
206
- return this._isLoaded;
207
- }
208
- async init() {
209
- if (this._isLoaded)
210
- return;
211
- const onProgress = this.options.onProgress;
212
- if (onProgress) {
213
- onProgress({
214
- status: 'loading',
215
- loaded: 50,
216
- total: 100,
217
- percent: 50,
218
- detail: 'Initializing heuristic decision heuristics',
219
- });
220
- onProgress({
221
- status: 'ready',
222
- loaded: 100,
223
- total: 100,
224
- percent: 100,
225
- detail: 'Heuristic decision engine ready',
226
- });
227
- }
228
- this._isLoaded = true;
229
- }
230
- async choice(input) {
231
- const startTime = performance.now();
232
- await this.init();
233
- if (input.state === undefined || input.state === null) {
234
- throw new Error('state is required for choice evaluation');
235
- }
236
- const normOptions = normalizeOptions(input.options);
237
- const stateStr = stringifyState(input.state).toLowerCase();
238
- const questionStr = String(input.question || input.instructions || '').toLowerCase();
239
- const questionWords = extractKeywords(questionStr);
240
- const rawScores = {};
241
- for (const opt of normOptions) {
242
- const optKeyLower = opt.key.toLowerCase();
243
- const optDescLower = opt.description.toLowerCase();
244
- let score = 0.2;
245
- // 1. Direct word match
246
- if (wordMatch(stateStr, optKeyLower))
247
- score += 3.5;
248
- if (optDescLower !== optKeyLower && wordMatch(stateStr, optDescLower))
249
- score += 2.5;
250
- // 2. Keyword tokens match
251
- const descKeywords = extractKeywords(optDescLower + ' ' + optKeyLower);
252
- for (const kw of descKeywords) {
253
- if (wordMatch(stateStr, kw))
254
- score += 1.2;
255
- }
256
- // 3. Question relevance
257
- for (const qw of questionWords) {
258
- if (optDescLower.includes(qw))
259
- score += 0.5;
260
- }
261
- // 4. Semantic Concept Affinity
262
- for (const [concept, words] of Object.entries(CONCEPT_KEYWORDS)) {
263
- const matchesOption = words.some((w) => optKeyLower.includes(w) || optDescLower.includes(w));
264
- if (matchesOption) {
265
- const stateMatches = words.filter((w) => wordMatch(stateStr, w));
266
- if (stateMatches.length > 0) {
267
- score += 1.8 + Math.min(stateMatches.length * 0.4, 2.0);
268
- }
269
- }
270
- }
271
- rawScores[opt.key] = Math.max(0.01, score);
272
- }
273
- // Softmax normalization
274
- const expScores = normOptions.map((opt) => Math.exp(rawScores[opt.key]));
275
- const expSum = expScores.reduce((a, b) => a + b, 0) || 1;
276
- const probabilities = {};
277
- let winningChoice = normOptions[0].key;
278
- let maxProb = -1;
279
- for (let i = 0; i < normOptions.length; i++) {
280
- const optKey = normOptions[i].key;
281
- const prob = Number((expScores[i] / expSum).toFixed(2));
282
- probabilities[optKey] = prob;
283
- if (prob > maxProb) {
284
- maxProb = prob;
285
- winningChoice = optKey;
286
- }
287
- }
288
- // Ensure probabilities sum to 1.0
289
- const sum = Object.values(probabilities).reduce((a, b) => a + b, 0);
290
- if (sum > 0 && sum !== 1) {
291
- const diff = Number((1 - sum).toFixed(2));
292
- probabilities[winningChoice] = Math.max(0, Number((probabilities[winningChoice] + diff).toFixed(2)));
293
- maxProb = probabilities[winningChoice];
294
- }
295
- const confidence = Number(Math.min(0.99, Math.max(0.51, maxProb * 1.02)).toFixed(2));
296
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
297
- return {
298
- choice: winningChoice,
299
- confidence,
300
- probabilities,
301
- latencyMs,
302
- };
303
- }
304
- async directChoice(input) {
305
- return this.choice(input);
306
- }
307
- async noul(input) {
308
- const startTime = performance.now();
309
- await this.init();
310
- if (input.state === undefined || input.state === null) {
311
- throw new Error('state is required for noul evaluation');
312
- }
313
- const statement = String(input.statement || input.instructions || '').trim();
314
- if (!statement) {
315
- throw new Error('statement is required for noul evaluation');
316
- }
317
- const threshold = input.threshold ?? this.options.noulThreshold ?? 0.5;
318
- if (typeof threshold !== 'number' || threshold < 0 || threshold > 1) {
319
- throw new Error('threshold must be between 0 and 1');
320
- }
321
- const stateStr = stringifyState(input.state).toLowerCase();
322
- const statementLower = statement.toLowerCase();
323
- const statementWords = extractKeywords(statementLower);
324
- let affirmativeScore = 0;
325
- let negativeScore = 0;
326
- // Check concept alignment
327
- for (const [concept, words] of Object.entries(CONCEPT_KEYWORDS)) {
328
- const statementHasConcept = words.some((w) => statementLower.includes(w));
329
- if (statementHasConcept) {
330
- const stateMatches = words.filter((w) => wordMatch(stateStr, w));
331
- if (stateMatches.length > 0) {
332
- affirmativeScore += 2.0 + Math.min(stateMatches.length * 0.6, 3.0);
333
- }
334
- else {
335
- negativeScore += 0.8;
336
- }
337
- }
338
- }
339
- // Check direct word overlap
340
- let matchCount = 0;
341
- for (const kw of statementWords) {
342
- if (wordMatch(stateStr, kw))
343
- matchCount++;
344
- }
345
- if (statementWords.length > 0) {
346
- affirmativeScore += (matchCount / statementWords.length) * 3.0;
347
- }
348
- // Negation handling in statement (e.g. "is this NOT safe", "is this non-compliant")
349
- const isNegated = statementLower.includes('not ') ||
350
- statementLower.includes('never ') ||
351
- statementLower.includes('no ') ||
352
- statementLower.includes('non-');
353
- // Sigmoid probability mapping
354
- const netSignal = affirmativeScore - negativeScore;
355
- let prob = 1 / (1 + Math.exp(-1.2 * (netSignal - 0.5)));
356
- if (isNegated) {
357
- prob = 1 - prob;
358
- }
359
- const noulVal = Number(Math.min(0.99, Math.max(0.01, prob)).toFixed(2));
360
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
361
- return {
362
- noul: noulVal,
363
- passed: noulVal >= threshold,
364
- latencyMs,
365
- };
366
- }
367
- async score(input) {
368
- const startTime = performance.now();
369
- await this.init();
370
- if (input.state === undefined || input.state === null) {
371
- throw new Error('state is required for score evaluation');
372
- }
373
- const levels = normalizeCriteria(input.criteria);
374
- const numLevels = levels.length;
375
- const stateStr = stringifyState(input.state).toLowerCase();
376
- const instructionsStr = String(input.instructions || input.question || '').toLowerCase();
377
- // Determine target center index (0 to numLevels - 1)
378
- let targetCenter = (numLevels - 1) * 0.5;
379
- const isHigh = CONCEPT_KEYWORDS.urgent.some((w) => wordMatch(stateStr, w)) ||
380
- CONCEPT_KEYWORDS.toxicity.some((w) => wordMatch(stateStr, w)) ||
381
- stateStr.includes('500') ||
382
- stateStr.includes('fatal') ||
383
- stateStr.includes('unacceptable') ||
384
- stateStr.includes('severe');
385
- const isLow = CONCEPT_KEYWORDS.low.some((w) => wordMatch(stateStr, w)) ||
386
- stateStr.includes('trivial') ||
387
- stateStr.includes('minor') ||
388
- stateStr.includes('routine');
389
- const isMedium = CONCEPT_KEYWORDS.moderate.some((w) => wordMatch(stateStr, w)) ||
390
- CONCEPT_KEYWORDS.bug.some((w) => wordMatch(stateStr, w));
391
- if (isHigh) {
392
- targetCenter = numLevels - 1;
393
- }
394
- else if (isLow) {
395
- targetCenter = 0;
396
- }
397
- else if (isMedium) {
398
- targetCenter = (numLevels - 1) * 0.5;
399
- }
400
- else {
401
- // Lexical level alignment
402
- let bestMatchIdx = -1;
403
- let bestMatchScore = 0;
404
- for (const lvl of levels) {
405
- const descWords = extractKeywords(lvl.description + ' ' + lvl.key);
406
- let s = 0;
407
- for (const w of descWords) {
408
- if (wordMatch(stateStr, w))
409
- s++;
410
- }
411
- if (s > bestMatchScore) {
412
- bestMatchScore = s;
413
- bestMatchIdx = lvl.index;
414
- }
415
- }
416
- if (bestMatchIdx >= 0) {
417
- targetCenter = bestMatchIdx;
418
- }
419
- }
420
- // Invert center if instructions ask for inverse metric (e.g. rate safety where high safety = positive)
421
- if (instructionsStr.includes('safe') || instructionsStr.includes('quality') || instructionsStr.includes('satisfaction')) {
422
- const isPositive = CONCEPT_KEYWORDS.positive.some((w) => wordMatch(stateStr, w));
423
- const isNegative = CONCEPT_KEYWORDS.negative.some((w) => wordMatch(stateStr, w));
424
- if (isPositive)
425
- targetCenter = numLevels - 1;
426
- else if (isNegative)
427
- targetCenter = 0;
428
- }
429
- // Gaussian-like probabilities over levels
430
- const rawWeights = [];
431
- let totalWeight = 0;
432
- for (let i = 0; i < numLevels; i++) {
433
- const dist = Math.abs(i - targetCenter);
434
- const weight = Math.exp(-1.5 * dist * dist) + 0.05;
435
- rawWeights.push(weight);
436
- totalWeight += weight;
437
- }
438
- const probabilities = {};
439
- let weightedScore = 0;
440
- let maxProb = 0;
441
- for (let i = 0; i < numLevels; i++) {
442
- const lvl = levels[i];
443
- const normProb = Number((rawWeights[i] / totalWeight).toFixed(2));
444
- probabilities[lvl.key] = normProb;
445
- if (lvl.key !== String(lvl.index)) {
446
- probabilities[String(lvl.index)] = normProb;
447
- }
448
- weightedScore += lvl.index * normProb;
449
- if (normProb > maxProb)
450
- maxProb = normProb;
451
- }
452
- const confidence = Number(Math.min(0.98, Math.max(0.55, maxProb * 1.05)).toFixed(2));
453
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
454
- return {
455
- score: Number(weightedScore.toFixed(2)),
456
- confidence,
457
- probabilities,
458
- latencyMs,
459
- };
460
- }
461
- dispose() {
462
- this._isLoaded = false;
463
- }
464
- }
465
- // ─── OpenJev Wllama Decision Engine (WebAssembly / WebGPU) ───
466
- export class OpenJevWllamaEngine {
467
- model;
468
- isHeuristic = false;
469
- _isLoaded = false;
470
- options;
471
- modelUrl;
472
- wllamaInstance = null;
473
- constructor(options = {}) {
474
- const validated = validateModelOptions(options);
475
- this.model = validated.modelId;
476
- this.modelUrl = validated.modelUrl;
477
- this.options = options;
478
- }
479
- get isLoaded() {
480
- return this._isLoaded;
481
- }
482
- async init() {
483
- if (this._isLoaded && this.wllamaInstance)
484
- return;
485
- const onProgress = this.options.onProgress;
486
- onProgress?.({
487
- status: 'downloading',
488
- loaded: 0,
489
- total: 100,
490
- percent: 0,
491
- detail: `Downloading OpenJev model ${this.model}`,
492
- });
493
- let WllamaClass = null;
494
- try {
495
- const mod = (await import('@wllama/wllama/esm/index.js'));
496
- WllamaClass = mod.Wllama || mod.default?.Wllama || mod.default;
497
- }
498
- catch {
499
- const mod = (await import('@wllama/wllama'));
500
- WllamaClass = mod.Wllama || mod.default?.Wllama || mod.default;
501
- }
502
- if (!WllamaClass) {
503
- throw new Error('@wllama/wllama could not be loaded in this environment');
504
- }
505
- const pathConfig = this.options.wasmPaths || {
506
- default: 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/wllama.wasm',
507
- 'single-thread/wllama.wasm': 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/single-thread/wllama.wasm',
508
- 'multi-thread/wllama.wasm': 'https://cdn.jsdelivr.net/npm/@wllama/wllama@3.6.1/esm/multi-thread/wllama.wasm',
509
- };
510
- this.wllamaInstance = new WllamaClass(pathConfig, {
511
- suppressNativeLog: true,
512
- });
513
- onProgress?.({
514
- status: 'loading',
515
- loaded: 40,
516
- total: 100,
517
- percent: 40,
518
- detail: 'Initializing runtime context',
519
- });
520
- await this.wllamaInstance.loadModelFromUrl(this.modelUrl, {
521
- useCache: true,
522
- onProgress: (p) => {
523
- const percent = p.total > 0 ? Math.round((p.loaded / p.total) * 100) : 50;
524
- onProgress?.({
525
- status: 'downloading',
526
- loaded: p.loaded,
527
- total: p.total,
528
- percent,
529
- detail: `Loading model shards (${percent}%)`,
530
- });
531
- },
532
- });
533
- onProgress?.({
534
- status: 'ready',
535
- loaded: 100,
536
- total: 100,
537
- percent: 100,
538
- detail: `OpenJev model ${this.model} initialized`,
539
- });
540
- this._isLoaded = true;
541
- }
542
- async choice(input) {
543
- const startTime = performance.now();
544
- await this.init();
545
- const normOptions = normalizeOptions(input.options);
546
- const stateStr = stringifyState(input.state);
547
- const question = input.question || input.instructions || 'Select the most appropriate option';
548
- const prompt = `Context:\n${stateStr}\n\nQuestion: ${question}\nChoices:\n${normOptions
549
- .map((opt, idx) => `(${idx + 1}) ${opt.key}: ${opt.description}`)
550
- .join('\n')}\n\nAnswer:`;
551
- const res = await this.wllamaInstance.createCompletion({
552
- prompt,
553
- max_tokens: 1,
554
- temperature: 0.0,
555
- logprobs: true,
556
- top_logprobs: Math.max(10, normOptions.length * 2),
557
- });
558
- // Extract logprobs if present
559
- const probabilities = {};
560
- const textOut = (res?.text || '').trim().toLowerCase();
561
- let winningChoice = normOptions[0].key;
562
- let maxScore = -Infinity;
563
- // Check logprobs from completion
564
- const topLogprobs = res?.choices?.[0]?.logprobs?.content?.[0]?.top_logprobs || [];
565
- if (topLogprobs.length > 0) {
566
- let sumExp = 0;
567
- const rawExp = {};
568
- for (const opt of normOptions) {
569
- const keyLower = opt.key.toLowerCase();
570
- let bestLp = -20;
571
- for (const lp of topLogprobs) {
572
- const t = (lp.token || '').trim().toLowerCase();
573
- if (t.includes(keyLower) || keyLower.includes(t)) {
574
- if (lp.logprob > bestLp)
575
- bestLp = lp.logprob;
576
- }
577
- }
578
- const expVal = Math.exp(bestLp);
579
- rawExp[opt.key] = expVal;
580
- sumExp += expVal;
581
- }
582
- for (const opt of normOptions) {
583
- const prob = sumExp > 0 ? Number((rawExp[opt.key] / sumExp).toFixed(2)) : Number((1 / normOptions.length).toFixed(2));
584
- probabilities[opt.key] = prob;
585
- if (prob > maxScore) {
586
- maxScore = prob;
587
- winningChoice = opt.key;
588
- }
589
- }
590
- }
591
- else {
592
- // Fallback matching from token text output
593
- for (let i = 0; i < normOptions.length; i++) {
594
- const opt = normOptions[i];
595
- const isMatch = textOut.includes(opt.key.toLowerCase()) || textOut.includes(String(i + 1));
596
- const prob = isMatch ? 0.85 : Number((0.15 / Math.max(normOptions.length - 1, 1)).toFixed(2));
597
- probabilities[opt.key] = prob;
598
- if (prob > maxScore) {
599
- maxScore = prob;
600
- winningChoice = opt.key;
601
- }
602
- }
603
- }
604
- const confidence = Number(Math.max(0.51, Math.min(0.99, maxScore)).toFixed(2));
605
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
606
- return {
607
- choice: winningChoice,
608
- confidence,
609
- probabilities,
610
- latencyMs,
611
- };
612
- }
613
- async directChoice(input) {
614
- return this.choice(input);
615
- }
616
- async noul(input) {
617
- const startTime = performance.now();
618
- await this.init();
619
- const statement = String(input.statement || input.instructions || '').trim();
620
- if (!statement) {
621
- throw new Error('statement is required for noul evaluation');
622
- }
623
- const threshold = input.threshold ?? this.options.noulThreshold ?? 0.5;
624
- const stateStr = stringifyState(input.state);
625
- const prompt = `Context:\n${stateStr}\n\nStatement: ${statement}\nDoes the statement hold true? Answer Yes or No:\nAnswer:`;
626
- const res = await this.wllamaInstance.createCompletion({
627
- prompt,
628
- max_tokens: 1,
629
- temperature: 0.0,
630
- logprobs: true,
631
- top_logprobs: 10,
632
- });
633
- const topLogprobs = res?.choices?.[0]?.logprobs?.content?.[0]?.top_logprobs || [];
634
- let pYes = 0.5;
635
- if (topLogprobs.length > 0) {
636
- let lpYes = -20;
637
- let lpNo = -20;
638
- for (const lp of topLogprobs) {
639
- const t = (lp.token || '').trim().toLowerCase();
640
- if (t === 'yes' || t === 'true')
641
- lpYes = Math.max(lpYes, lp.logprob);
642
- if (t === 'no' || t === 'false')
643
- lpNo = Math.max(lpNo, lp.logprob);
644
- }
645
- const expYes = Math.exp(lpYes);
646
- const expNo = Math.exp(lpNo);
647
- pYes = expYes / (expYes + expNo || 1);
648
- }
649
- else {
650
- const text = (res?.text || '').trim().toLowerCase();
651
- pYes = text.startsWith('y') || text.startsWith('t') ? 0.9 : 0.1;
652
- }
653
- const noulVal = Number(Math.min(0.99, Math.max(0.01, pYes)).toFixed(2));
654
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
655
- return {
656
- noul: noulVal,
657
- passed: noulVal >= threshold,
658
- latencyMs,
659
- };
660
- }
661
- async score(input) {
662
- const startTime = performance.now();
663
- await this.init();
664
- const levels = normalizeCriteria(input.criteria);
665
- const numLevels = levels.length;
666
- const stateStr = stringifyState(input.state);
667
- const instructions = input.instructions || input.question || 'Score the situation along ordered levels';
668
- const prompt = `Context:\n${stateStr}\n\nInstructions: ${instructions}\nLevels:\n${levels
669
- .map((l) => `(${l.index}) ${l.key}: ${l.description}`)
670
- .join('\n')}\n\nBest matching level number (0-${numLevels - 1}):`;
671
- const res = await this.wllamaInstance.createCompletion({
672
- prompt,
673
- max_tokens: 1,
674
- temperature: 0.0,
675
- logprobs: true,
676
- top_logprobs: Math.max(10, numLevels * 2),
677
- });
678
- const topLogprobs = res?.choices?.[0]?.logprobs?.content?.[0]?.top_logprobs || [];
679
- const probabilities = {};
680
- let weightedScore = 0;
681
- let maxProb = 0;
682
- if (topLogprobs.length > 0) {
683
- const rawExp = [];
684
- let totalExp = 0;
685
- for (let i = 0; i < numLevels; i++) {
686
- const lvl = levels[i];
687
- let bestLp = -20;
688
- for (const lp of topLogprobs) {
689
- const t = (lp.token || '').trim().toLowerCase();
690
- if (t === String(i) || t === lvl.key.toLowerCase()) {
691
- bestLp = Math.max(bestLp, lp.logprob);
692
- }
693
- }
694
- const expVal = Math.exp(bestLp);
695
- rawExp.push(expVal);
696
- totalExp += expVal;
697
- }
698
- for (let i = 0; i < numLevels; i++) {
699
- const lvl = levels[i];
700
- const prob = totalExp > 0 ? Number((rawExp[i] / totalExp).toFixed(2)) : Number((1 / numLevels).toFixed(2));
701
- probabilities[lvl.key] = prob;
702
- if (lvl.key !== String(lvl.index)) {
703
- probabilities[String(lvl.index)] = prob;
704
- }
705
- weightedScore += lvl.index * prob;
706
- if (prob > maxProb)
707
- maxProb = prob;
708
- }
709
- }
710
- else {
711
- const text = (res?.text || '').trim();
712
- const matchedIdx = parseInt(text, 10);
713
- const center = !isNaN(matchedIdx) && matchedIdx >= 0 && matchedIdx < numLevels ? matchedIdx : Math.floor(numLevels / 2);
714
- for (let i = 0; i < numLevels; i++) {
715
- const lvl = levels[i];
716
- const prob = i === center ? 0.85 : Number((0.15 / Math.max(numLevels - 1, 1)).toFixed(2));
717
- probabilities[lvl.key] = prob;
718
- if (lvl.key !== String(lvl.index)) {
719
- probabilities[String(lvl.index)] = prob;
720
- }
721
- weightedScore += lvl.index * prob;
722
- if (prob > maxProb)
723
- maxProb = prob;
724
- }
725
- }
726
- const confidence = Number(Math.max(0.55, Math.min(0.98, maxProb * 1.05)).toFixed(2));
727
- const latencyMs = Math.max(1, Math.round(performance.now() - startTime));
728
- return {
729
- score: Number(weightedScore.toFixed(2)),
730
- confidence,
731
- probabilities,
732
- latencyMs,
733
- };
734
- }
735
- async dispose() {
736
- if (this.wllamaInstance) {
737
- try {
738
- await this.wllamaInstance.exit();
739
- }
740
- catch {
741
- // Non-fatal cleanup
742
- }
743
- this.wllamaInstance = null;
744
- }
745
- this._isLoaded = false;
746
- }
747
- }
748
- // ─── Engine Factory & Direct Choice Entry Point ───
749
- function isWllamaSupported() {
750
- const isBrowser = typeof window !== 'undefined';
751
- const isWorker = typeof WorkerGlobalScope !== 'undefined' || (typeof self !== 'undefined' && typeof self.postMessage === 'function');
752
- return (isBrowser || isWorker) && typeof WebAssembly !== 'undefined';
753
- }
754
- /**
755
- * Creates a DecisionEngine configured for OpenJev System One decision evaluations.
756
- *
757
- * Automatically selects OpenJev via Wllama in browser/worker environments with WebGPU/WASM,
758
- * and falls back to a calibrated zero-dependency heuristic engine in Node.js or test environments.
759
- *
760
- * @param options - Configuration options (model preset, mode, wasm paths, onProgress)
761
- * @returns Configured DecisionEngine instance
762
- */
763
- export function createDecisionEngine(options = {}) {
764
- const mode = options.mode ?? 'auto';
765
- if (mode === 'heuristic') {
766
- return new HeuristicDecisionEngine(options);
767
- }
768
- if (mode === 'wllama') {
769
- return new OpenJevWllamaEngine(options);
770
- }
771
- // 'auto' mode: use Wllama in browser/worker if available, otherwise heuristic
772
- if (isWllamaSupported()) {
773
- return new OpenJevWllamaEngine(options);
774
- }
775
- return new HeuristicDecisionEngine(options);
776
- }
777
- /**
778
- * Executes a one-shot direct choice evaluation over application state with zero boilerplate.
779
- *
780
- * @param input - Evaluation state, question, and options (array or criteria record)
781
- * @param options - Decision engine configuration options
782
- * @returns ChoiceResult containing winning choice, confidence, and calibrated probabilities
783
- */
784
- export async function directChoice(input, options) {
785
- const engine = createDecisionEngine(options);
786
- await engine.init();
787
- return engine.choice(input);
788
- }
789
- //# sourceMappingURL=decision.js.map