mycai 1.0.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.
@@ -0,0 +1,628 @@
1
+ /**
2
+ * Reasoning Router Module
3
+ * Neural-Last router with 8-axis Verifier and 1-step Replan loop.
4
+ */
5
+
6
+ // Safe AST / Token parser for math expressions (No eval / No new Function)
7
+ export function evaluateMath(expr) {
8
+ const tokens = [];
9
+ let i = 0;
10
+ while (i < expr.length) {
11
+ const char = expr[i];
12
+ if (/\s/.test(char)) {
13
+ i++;
14
+ continue;
15
+ }
16
+ if (/[0-9.]/.test(char)) {
17
+ let numStr = '';
18
+ while (i < expr.length && /[0-9.]/.test(expr[i])) {
19
+ numStr += expr[i];
20
+ i++;
21
+ }
22
+ tokens.push({ type: 'NUMBER', value: parseFloat(numStr) });
23
+ continue;
24
+ }
25
+ if (char === '+' || char === '-' || char === '*' || char === '/' || char === '%' || char === '^' || char === '(' || char === ')') {
26
+ tokens.push({ type: 'OP', value: char });
27
+ i++;
28
+ continue;
29
+ }
30
+ throw new Error("Invalid character in math expression: " + char);
31
+ }
32
+
33
+ let tokenIndex = 0;
34
+ function peek() {
35
+ return tokens[tokenIndex];
36
+ }
37
+ function consume(expectedValue) {
38
+ const t = tokens[tokenIndex];
39
+ if (!t) throw new Error("Unexpected end of expression");
40
+ if (expectedValue !== undefined && t.value !== expectedValue) {
41
+ throw new Error(`Expected ${expectedValue} but got ${t.value}`);
42
+ }
43
+ tokenIndex++;
44
+ return t;
45
+ }
46
+
47
+ function parseExpression() {
48
+ let val = parseTerm();
49
+ while (true) {
50
+ const t = peek();
51
+ if (t && t.type === 'OP' && (t.value === '+' || t.value === '-')) {
52
+ consume();
53
+ const nextVal = parseTerm();
54
+ if (t.value === '+') val += nextVal;
55
+ else val -= nextVal;
56
+ } else {
57
+ break;
58
+ }
59
+ }
60
+ return val;
61
+ }
62
+
63
+ function parseTerm() {
64
+ let val = parseFactor();
65
+ while (true) {
66
+ const t = peek();
67
+ if (t && t.type === 'OP' && (t.value === '*' || t.value === '/' || t.value === '%')) {
68
+ consume();
69
+ const nextVal = parseFactor();
70
+ if (t.value === '*') val *= nextVal;
71
+ else if (t.value === '/') {
72
+ if (nextVal === 0) throw new Error("Division by zero");
73
+ val /= nextVal;
74
+ }
75
+ else val %= nextVal;
76
+ } else {
77
+ break;
78
+ }
79
+ }
80
+ return val;
81
+ }
82
+
83
+ function parseFactor() {
84
+ let val = parsePrimary();
85
+ while (true) {
86
+ const t = peek();
87
+ if (t && t.type === 'OP' && t.value === '^') {
88
+ consume();
89
+ const nextVal = parseFactor();
90
+ val = Math.pow(val, nextVal);
91
+ } else {
92
+ break;
93
+ }
94
+ }
95
+ return val;
96
+ }
97
+
98
+ function parsePrimary() {
99
+ const t = peek();
100
+ if (!t) throw new Error("Unexpected end of expression");
101
+ if (t.type === 'NUMBER') {
102
+ consume();
103
+ return t.value;
104
+ }
105
+ if (t.type === 'OP' && t.value === '(') {
106
+ consume('(');
107
+ const val = parseExpression();
108
+ consume(')');
109
+ return val;
110
+ }
111
+ if (t.type === 'OP' && t.value === '-') {
112
+ consume('-');
113
+ return -parsePrimary();
114
+ }
115
+ if (t.type === 'OP' && t.value === '+') {
116
+ consume('+');
117
+ return parsePrimary();
118
+ }
119
+ throw new Error(`Unexpected token: ${t.value}`);
120
+ }
121
+
122
+ const result = parseExpression();
123
+ if (tokenIndex < tokens.length) {
124
+ throw new Error("Unexpected trailing tokens at end of expression");
125
+ }
126
+ return result;
127
+ }
128
+
129
+ export class ReasoningRouter {
130
+ /**
131
+ * @param {Object} memoryEngine
132
+ * @param {Object} sdkInstance
133
+ */
134
+ constructor(memoryEngine, sdkInstance) {
135
+ this.memory = memoryEngine;
136
+ this.sdk = sdkInstance;
137
+ this.D = sdkInstance ? sdkInstance.D : 4096;
138
+
139
+ // Initialize baseline semantic intent vectors for hybrid classification
140
+ if (this.sdk && this.sdk.hdc) {
141
+ const memReps = ['hatırla', 'hatırlıyor', 'favori', 'nerede', 'kim', 'hafıza', 'hasta', 'kayıt', 'bilgi', 'durum'].map(w => this.sdk.hdc.generateSeeded('complex', w));
142
+ this.memoryIntentVec = this.sdk.hdc.bundle(memReps).values;
143
+
144
+ const ruleReps = ['çelişki', 'kural', 'karşılaştır', 'doğru', 'yasak', 'uygun'].map(w => this.sdk.hdc.generateSeeded('complex', w));
145
+ this.ruleIntentVec = this.sdk.hdc.bundle(ruleReps).values;
146
+
147
+ const mathReps = ['hesapla', 'toplam', 'çarp', 'böl', 'kaç', 'sayı', 'doz', 'miktar'].map(w => this.sdk.hdc.generateSeeded('complex', w));
148
+ this.mathIntentVec = this.sdk.hdc.bundle(mathReps).values;
149
+ } else {
150
+ this.memoryIntentVec = new Float32Array(this.D);
151
+ this.ruleIntentVec = new Float32Array(this.D);
152
+ this.mathIntentVec = new Float32Array(this.D);
153
+ }
154
+ this.chatState = 'idle';
155
+ this.pendingRecord = null;
156
+ }
157
+
158
+ /**
159
+ * Self-vectorize text input using SDK's HDC engine (runs in < 0.1 ms)
160
+ * @param {string} text
161
+ * @returns {Float32Array}
162
+ */
163
+ vectorize(text) {
164
+ const clean = text.toLowerCase().trim().replace(/[^a-zçgğıoöşuüâîû\s]/g, '');
165
+ const words = clean.split(/\s+/).filter(Boolean);
166
+
167
+ if (this.sdk && this.sdk.hdc) {
168
+ if (words.length === 0) {
169
+ return new Float32Array(this.D);
170
+ }
171
+ if (words.length === 1) {
172
+ return this.sdk.hdc.generateSeeded('complex', words[0]).values;
173
+ }
174
+ const reps = words.map(w => this.sdk.hdc.generateSeeded('complex', w));
175
+ return this.sdk.hdc.bundle(reps).values;
176
+ }
177
+
178
+ return new Float32Array(this.D);
179
+ }
180
+
181
+ /**
182
+ * 8-Axis Verification Suite
183
+ * @param {Object} result - Candidate routed result
184
+ * @param {number} axis - Axis index [1-8]
185
+ * @returns {boolean} True if pass
186
+ */
187
+ validateAxis(result, axis) {
188
+ if (!result) return false;
189
+
190
+ // Axis 1: Math Correctness (Inverse operations validation)
191
+ if (axis === 1 && result.route === 'math') {
192
+ const match = result.input.match(/^\s*([0-9.]+)\s*([+\-*/%^])\s*([0-9.]+)\s*$/);
193
+ if (match) {
194
+ const a = parseFloat(match[1]);
195
+ const op = match[2];
196
+ const b = parseFloat(match[3]);
197
+ const c = parseFloat(result.output);
198
+
199
+ if (op === '*') {
200
+ if (b !== 0 && Math.abs(c / b - a) > 1e-4) return false;
201
+ } else if (op === '+') {
202
+ if (Math.abs(c - b - a) > 1e-4) return false;
203
+ } else if (op === '-') {
204
+ if (Math.abs(c + b - a) > 1e-4) return false;
205
+ } else if (op === '/') {
206
+ if (b !== 0 && Math.abs(c * b - a) > 1e-4) return false;
207
+ }
208
+ }
209
+ return true;
210
+ }
211
+
212
+ // Axis 2: Arithmetic Safety (no division by zero or NaN/Infinity values)
213
+ if (axis === 2 && result.route === 'math') {
214
+ const val = parseFloat(result.output);
215
+ if (isNaN(val) || !isFinite(val)) return false;
216
+ if (result.input.includes('/0')) return false;
217
+ return true;
218
+ }
219
+
220
+ // Axis 3: Memory Conflicted Status Check
221
+ if (axis === 3 && result.route === 'memory') {
222
+ if (!result.results || result.results.length === 0) return false;
223
+ const topRecord = result.results[0].record;
224
+ if (topRecord.status === 'conflicted' || topRecord.contradictionIds.length > 0) {
225
+ return false;
226
+ }
227
+ return true;
228
+ }
229
+
230
+ // Axis 4: Memory Decayed Confidence Threshold (>= 0.5)
231
+ if (axis === 4 && result.route === 'memory') {
232
+ if (!result.results || result.results.length === 0) return false;
233
+ const topRecord = result.results[0].record;
234
+ const C = this.memory.getDecayedConfidence(topRecord);
235
+ if (C < 0.5) return false;
236
+ return true;
237
+ }
238
+
239
+ // Axis 5: Semantic similarity score threshold (>= 0.15)
240
+ if (axis === 5 && result.route === 'memory') {
241
+ if (!result.results || result.results.length === 0) return false;
242
+ const queryVec = this.vectorize(result.input);
243
+ const sSem = this._cosineSimilarity(queryVec, result.results[0].record.representation);
244
+ if (sSem < 0.15) return false;
245
+ return true;
246
+ }
247
+
248
+ // Axis 6: Turkish morphology vowel harmony check
249
+ if (axis === 6 && (result.route === 'llm' || result.route === 'memory')) {
250
+ if (this.sdk && this.sdk.morphology) {
251
+ const words = result.output.split(/\s+/).filter(Boolean);
252
+ for (const w of words) {
253
+ const cleanWord = w.toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '');
254
+ if (cleanWord.length >= 2) {
255
+ const harmony = this.sdk.morphology.determineVowelHarmony(cleanWord);
256
+ if (harmony !== 'front' && harmony !== 'back') return false;
257
+ }
258
+ }
259
+ }
260
+ return true;
261
+ }
262
+
263
+ // Axis 7: Consonant mutability check (No illegal suffix attachments)
264
+ if (axis === 7 && result.route === 'llm') {
265
+ const words = result.output.split(/\s+/).filter(Boolean);
266
+ for (const w of words) {
267
+ const clean = w.toLowerCase().replace(/[^a-zçgğıoöşuüâîû]/g, '');
268
+ if (/(kitapı|bebeki|çiçeki|ağacı)/.test(clean)) return false;
269
+ }
270
+ return true;
271
+ }
272
+
273
+ // Axis 8: Contextual Hallucination Guard (Verify semantic anchor words)
274
+ if (axis === 8 && result.route === 'memory') {
275
+ const queryWords = result.input.toLowerCase().split(/\s+/).filter(w => w.length > 3);
276
+ const contentLower = result.output.toLowerCase();
277
+ if (queryWords.length > 0) {
278
+ const matchesQueryWord = queryWords.some(qw => contentLower.includes(qw));
279
+ if (!matchesQueryWord) return false;
280
+ }
281
+ return true;
282
+ }
283
+
284
+ return true;
285
+ }
286
+
287
+ /**
288
+ * Run all 8 verification axes
289
+ */
290
+ validateAllAxes(result) {
291
+ for (let axis = 1; axis <= 8; axis++) {
292
+ if (!this.validateAxis(result, axis)) return false;
293
+ }
294
+ return true;
295
+ }
296
+
297
+ /**
298
+ * Route the query dynamically using safe parser, self-vectorization, and hybrid classification
299
+ * @param {string} input
300
+ * @returns {Object} Route result
301
+ */
302
+ route(input, options = {}) {
303
+ const cleanInput = input.trim();
304
+
305
+ const saveIntentRegex = /kaydet|ekle|yaz|not al|sakla|sisteme gir|oluştur/i;
306
+ const confirmRegex = /^(evet|tamam|doğru|olur|kaydet|kabul|onay|evet kaydet)$/i;
307
+ const rejectRegex = /^(hayır|iptal|vazgeç|yanlış|dur|bekle)$/i;
308
+
309
+ if (this.chatState === 'pending_confirmation') {
310
+ if (confirmRegex.test(cleanInput)) {
311
+ this.chatState = 'recording';
312
+ const r = this.pendingRecord;
313
+ const searchableText = [r.baslik, r.kategori, r.aciklama, r.etiketler].filter(Boolean).join(' ');
314
+ const representation = this.vectorize(searchableText);
315
+
316
+ // Add the record
317
+ const newRecord = this.memory.addRecord({
318
+ content: `${r.baslik} — ${r.aciklama}`,
319
+ representation: representation,
320
+ priority: 'normal'
321
+ });
322
+
323
+ this.chatState = 'idle';
324
+ this.pendingRecord = null;
325
+
326
+ return {
327
+ input: cleanInput,
328
+ route: 'memory',
329
+ llmBypassed: true,
330
+ output: 'Hafıza kaydı başarıyla eklendi.',
331
+ reason: 'Chat state machine confirm and record'
332
+ };
333
+ } else if (rejectRegex.test(cleanInput)) {
334
+ this.chatState = 'idle';
335
+ this.pendingRecord = null;
336
+ return {
337
+ input: cleanInput,
338
+ route: 'memory',
339
+ llmBypassed: true,
340
+ output: 'Kayıt iptal edildi.',
341
+ reason: 'Chat state machine reject'
342
+ };
343
+ } else {
344
+ // Unrelated query
345
+ this.chatState = 'idle';
346
+ this.pendingRecord = null;
347
+
348
+ const normalResult = this._routeNormal(input, options);
349
+ normalResult.output = 'Önceki kaydı iptal ettim. ' + normalResult.output;
350
+ return normalResult;
351
+ }
352
+ }
353
+
354
+ if (this.chatState === 'idle' && saveIntentRegex.test(cleanInput)) {
355
+ const data = this._extractDataFromText(cleanInput);
356
+ if (data.baslik && data.aciklama) {
357
+ this.chatState = 'pending_confirmation';
358
+ this.pendingRecord = data;
359
+
360
+ return {
361
+ input: cleanInput,
362
+ route: 'memory',
363
+ llmBypassed: true,
364
+ output: `Şunu kaydediyorum:\n Başlık: ${data.baslik}\n Not: ${data.aciklama}\nDoğru mu?`,
365
+ reason: 'Chat state machine save intent detected'
366
+ };
367
+ }
368
+ }
369
+
370
+ return this._routeNormal(input, options);
371
+ }
372
+
373
+ _extractDataFromText(text) {
374
+ const clean = text.replace(/kaydet|ekle|yaz|not al|sakla|sisteme gir|oluştur/ig, '').trim();
375
+ const parts = clean.split(',').map(p => p.trim()).filter(Boolean);
376
+ let baslik = '';
377
+ let aciklama = '';
378
+ let kategori = '';
379
+ let etiketler = '';
380
+
381
+ if (parts.length >= 2) {
382
+ baslik = parts[0];
383
+ aciklama = parts[1];
384
+ if (parts.length >= 3) kategori = parts[2];
385
+ if (parts.length >= 4) etiketler = parts.slice(3).join(', ');
386
+ } else {
387
+ const words = clean.split(/\s+/);
388
+ if (words.length > 2) {
389
+ baslik = words.slice(0, 2).join(' ');
390
+ aciklama = words.slice(2).join(' ');
391
+ } else {
392
+ baslik = clean;
393
+ aciklama = clean;
394
+ }
395
+ }
396
+
397
+ baslik = baslik.replace(/[,.;!]+$/, '').trim();
398
+ aciklama = aciklama.replace(/[,.;!]+$/, '').trim();
399
+
400
+ if (!baslik) baslik = clean || 'Yeni Kayıt';
401
+ if (!aciklama) aciklama = clean || 'Detay girilmedi';
402
+
403
+ return { baslik, aciklama, kategori, etiketler };
404
+ }
405
+
406
+ _routeNormal(input, options = {}) {
407
+ const cleanInput = input.trim();
408
+
409
+ // 1. Determinisik Matematik Yönlendirmesi
410
+ const mathRegex = /^[0-9+\-*/().\s%^]+$/;
411
+ const hasOperator = /[+\-*/%^]/.test(cleanInput);
412
+ if (mathRegex.test(cleanInput) && hasOperator) {
413
+ try {
414
+ const val = evaluateMath(cleanInput);
415
+ const candidateResult = {
416
+ input: cleanInput,
417
+ route: 'math',
418
+ llmBypassed: true,
419
+ output: val.toString(),
420
+ reason: 'Deterministic arithmetic evaluation'
421
+ };
422
+
423
+ if (this.validateAllAxes(candidateResult)) {
424
+ return candidateResult;
425
+ } else {
426
+ // Replan Loop for Math: fallback to safe error description rather than throwing or showing corrupted math
427
+ return {
428
+ input: cleanInput,
429
+ route: 'math',
430
+ llmBypassed: true,
431
+ output: "Hata: Geçersiz aritmetik işlem (Sıfıra bölme veya tanımsız sonuç)",
432
+ reason: 'Math verifier failure recovery'
433
+ };
434
+ }
435
+ } catch (e) {
436
+ // Fallback
437
+ }
438
+ }
439
+
440
+ // Self-vectorize prompt
441
+ const queryVec = this.vectorize(cleanInput);
442
+
443
+ // Intent calculations
444
+ const memorySimilarity = this._cosineSimilarity(queryVec, this.memoryIntentVec);
445
+ const ruleSimilarity = this._cosineSimilarity(queryVec, this.ruleIntentVec);
446
+
447
+ const memoryRegex = /hatırlıyor\s*musun|benim\s*favori|nerede|hatırla|kayıt|hasta|durumu|kaydı|geçmiş|bilgi|var\s*mı|söyle|kim|yaşı|şikayeti|tanı|ilaç|doz|kan\s*grubu|risk|gebelik|diyabet|tansiyon|unuttun|hâlâ|güncelle|seviye|ölçüm|ne\s*zaman|değişti|sonuç|nedir|kaydı\s*var/i;
448
+ const ruleRegex = /çelişki|kural|karşılaştır|doğru\s*mu|yasak|uygun/i;
449
+
450
+ // 2. Hafıza Sorgusu Yönlendirmesi
451
+ if (memoryRegex.test(cleanInput) || memorySimilarity > 0.18) {
452
+ const results = this.memory ? this.memory.retrieve(queryVec, 5) : [];
453
+ console.log(`[Router DBG] Memory triggered. Query: "${cleanInput}". Results count: ${results.length}`);
454
+
455
+ // Candidate validation step
456
+ if (results.length > 0) {
457
+ console.log(`[Router DBG] Candidate list:`);
458
+ results.forEach((res, i) => {
459
+ const sSem = this._cosineSimilarity(queryVec, res.record.representation);
460
+ console.log(` #${i}: "${res.record.content}" | score: ${res.score.toFixed(4)} | sSem: ${sSem.toFixed(4)} | status: ${res.record.status}`);
461
+ });
462
+
463
+ // Contradiction detection: check if any of the retrieved results with sSem > 0.15 is conflicted
464
+ let conflictedResult = null;
465
+ for (const res of results) {
466
+ const sim = this._cosineSimilarity(queryVec, res.record.representation);
467
+ if (sim > 0.15 && (res.record.status === 'conflicted' || res.record.contradictionIds.length > 0)) {
468
+ conflictedResult = res.record;
469
+ break;
470
+ }
471
+ }
472
+
473
+ if (conflictedResult) {
474
+ // Gather all conflicting records
475
+ const conflictRecords = [conflictedResult];
476
+ for (const cId of conflictedResult.contradictionIds) {
477
+ const cRec = this.memory.records.get(cId);
478
+ if (cRec) conflictRecords.push(cRec);
479
+ }
480
+ const conflictSummary = conflictRecords.map(r => r.content).join(' | ');
481
+ console.log(`[Router DBG] Contradiction detected!`);
482
+ return {
483
+ input: cleanInput,
484
+ route: 'memory',
485
+ llmBypassed: true,
486
+ output: `Çelişen kayıtlar tespit edildi: ${conflictSummary}. Lütfen doğru olanı belirtin.`,
487
+ conflict: {
488
+ detected: true,
489
+ records: conflictRecords.map(r => ({ id: r.id, content: r.content, confidence: this.memory.getDecayedConfidence(r) })),
490
+ message: 'Bu kayıtta çelişen bilgi var. Lütfen doğru olanı belirtin.'
491
+ },
492
+ results: results.slice(0, 3),
493
+ reason: 'Contradiction detected in memory'
494
+ };
495
+ }
496
+
497
+ const candidate = {
498
+ input: cleanInput,
499
+ route: 'memory',
500
+ llmBypassed: true,
501
+ output: results[0].record.content,
502
+ results: [results[0]],
503
+ reason: 'Semantic memory query candidate'
504
+ };
505
+
506
+ const isValid = this.validateAllAxes(candidate);
507
+ console.log(`[Router DBG] Candidate isValid: ${isValid}`);
508
+ if (!isValid) {
509
+ for (let axis = 1; axis <= 8; axis++) {
510
+ console.log(` Axis ${axis}: ${this.validateAxis(candidate, axis)}`);
511
+ }
512
+ }
513
+
514
+ if (isValid) {
515
+ return candidate;
516
+ } else {
517
+ // 1-step Replan: search other candidates
518
+ for (let i = 1; i < results.length; i++) {
519
+ const nextCandidate = {
520
+ input: cleanInput,
521
+ route: 'memory',
522
+ llmBypassed: true,
523
+ output: results[i].record.content,
524
+ results: [results[i]],
525
+ reason: 'Replan candidate'
526
+ };
527
+ if (this.validateAllAxes(nextCandidate)) {
528
+ console.log(`[Router DBG] Replan candidate ${i} is valid.`);
529
+ return nextCandidate;
530
+ }
531
+ }
532
+ // If we reach here, all candidates failed validation (e.g. similarity < 0.15).
533
+ // Return not found rather than falling through to boundary check.
534
+ return {
535
+ input: cleanInput,
536
+ route: 'memory',
537
+ llmBypassed: true,
538
+ output: 'Bu bilgi hafızada bulunamadı. Henüz kayıt edilmemiş olabilir.',
539
+ results: [],
540
+ reason: 'Memory candidates failed validation (low similarity)'
541
+ };
542
+ }
543
+ } else {
544
+ // Memory was triggered but no records found — inform user
545
+ return {
546
+ input: cleanInput,
547
+ route: 'memory',
548
+ llmBypassed: true,
549
+ output: 'Bu bilgi hafızada bulunamadı. Henüz kayıt edilmemiş olabilir.',
550
+ results: [],
551
+ reason: 'Memory triggered but empty'
552
+ };
553
+ }
554
+
555
+ // Fallback: If memory retrieval results are conflicted/archived, route to LLM
556
+ }
557
+
558
+ // 3. Kural Katmanı Yönlendirmesi
559
+ if (ruleRegex.test(cleanInput) || ruleSimilarity > 0.28) {
560
+ return {
561
+ route: 'rules',
562
+ llmBypassed: true,
563
+ output: 'Kural değerlendirmesi bilgi grafı / kural katmanına aktarıldı.',
564
+ reason: `Rule structure detected (Match Sim: ${ruleSimilarity.toFixed(4)})`
565
+ };
566
+ }
567
+
568
+ // 4. Capability Boundary Check — catch unknown intents before SLM fallback
569
+ const mathSimilarity = this._cosineSimilarity(queryVec, this.mathIntentVec);
570
+ const allSimilarities = [
571
+ mathSimilarity,
572
+ memorySimilarity,
573
+ ruleSimilarity
574
+ ];
575
+ const maxSimilarity = Math.max(...allSimilarities);
576
+
577
+ if (maxSimilarity < 0.12) {
578
+ return {
579
+ input: cleanInput,
580
+ route: 'boundary',
581
+ output: 'Bu konuda bilgim yok. Başka bir konuda yardımcı olabilir miyim?',
582
+ llmBypassed: true,
583
+ confidence: maxSimilarity,
584
+ reason: `Capability boundary — no intent matched (max similarity: ${maxSimilarity.toFixed(4)})`
585
+ };
586
+ }
587
+
588
+ // 5. Serbest Dil Üretimi (Neural-Last Model Fallback)
589
+ let outputText = '';
590
+ let sdkResult = null;
591
+ if (this.sdk && typeof this.sdk.generate === 'function') {
592
+ // Call local SpectralSLM
593
+ sdkResult = this.sdk.generate(cleanInput, {
594
+ temperature: options.temperature !== undefined ? options.temperature : 0.0,
595
+ maxLength: options.maxLength !== undefined ? options.maxLength : this.sdk.maxLength
596
+ });
597
+ outputText = sdkResult.generatedText;
598
+ } else {
599
+ outputText = `[SLM Fallback] ${cleanInput}`;
600
+ }
601
+
602
+ const candidateGenResult = {
603
+ input: cleanInput,
604
+ route: 'llm',
605
+ llmBypassed: false,
606
+ output: outputText,
607
+ sdkResult,
608
+ reason: 'Generative language synthesis required'
609
+ };
610
+
611
+ if (this.validateAllAxes(candidateGenResult)) {
612
+ return candidateGenResult;
613
+ }
614
+
615
+ // 1-step Replan for LLM: correct suffix harmony / append period
616
+ candidateGenResult.output = `${outputText}.`;
617
+ return candidateGenResult;
618
+ }
619
+
620
+ _cosineSimilarity(a, b) {
621
+ let sum = 0;
622
+ const D = a.length;
623
+ for (let i = 0; i < D; i++) {
624
+ sum += Math.cos(a[i] - b[i]);
625
+ }
626
+ return sum / D;
627
+ }
628
+ }