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.
- package/package.json +3 -3
- package/dist/cache.d.ts +0 -46
- package/dist/cache.d.ts.map +0 -1
- package/dist/cache.js +0 -161
- package/dist/cache.js.map +0 -1
- package/dist/decision.d.ts +0 -182
- package/dist/decision.d.ts.map +0 -1
- package/dist/decision.js +0 -789
- package/dist/decision.js.map +0 -1
- package/dist/device.d.ts +0 -86
- package/dist/device.d.ts.map +0 -1
- package/dist/device.js +0 -195
- package/dist/device.js.map +0 -1
- package/dist/gpu-recovery.d.ts +0 -61
- package/dist/gpu-recovery.d.ts.map +0 -1
- package/dist/gpu-recovery.js +0 -105
- package/dist/gpu-recovery.js.map +0 -1
- package/dist/hub.d.ts +0 -97
- package/dist/hub.d.ts.map +0 -1
- package/dist/hub.js +0 -142
- package/dist/hub.js.map +0 -1
- package/dist/index.d.ts +0 -63
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -65
- package/dist/index.js.map +0 -1
- package/dist/inputs.d.ts +0 -27
- package/dist/inputs.d.ts.map +0 -1
- package/dist/inputs.js +0 -155
- package/dist/inputs.js.map +0 -1
- package/dist/loader.d.ts +0 -100
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -246
- package/dist/loader.js.map +0 -1
- package/dist/model-client.d.ts +0 -149
- package/dist/model-client.d.ts.map +0 -1
- package/dist/model-client.js +0 -318
- package/dist/model-client.js.map +0 -1
- package/dist/model-worker.d.ts +0 -16
- package/dist/model-worker.d.ts.map +0 -1
- package/dist/model-worker.js +0 -340
- package/dist/model-worker.js.map +0 -1
- package/dist/onnx-pipeline.d.ts +0 -20
- package/dist/onnx-pipeline.d.ts.map +0 -1
- package/dist/onnx-pipeline.js +0 -462
- package/dist/onnx-pipeline.js.map +0 -1
- package/dist/pipelines/index.d.ts +0 -28
- package/dist/pipelines/index.d.ts.map +0 -1
- package/dist/pipelines/index.js +0 -140
- package/dist/pipelines/index.js.map +0 -1
- package/dist/shim-ort.d.ts +0 -6
- package/dist/shim-ort.d.ts.map +0 -1
- package/dist/shim-ort.js +0 -38
- package/dist/shim-ort.js.map +0 -1
- package/dist/streaming.d.ts +0 -55
- package/dist/streaming.d.ts.map +0 -1
- package/dist/streaming.js +0 -142
- package/dist/streaming.js.map +0 -1
- package/dist/types.d.ts +0 -200
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- 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
|