File size: 14,071 Bytes
529090e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f3b51
529090e
 
 
 
 
 
 
 
66f3b51
 
 
 
 
 
 
 
529090e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f3b51
529090e
 
 
 
 
66f3b51
529090e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f3b51
529090e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66f3b51
 
 
529090e
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/**
 * Decision Engine - AI-Powered Source Selection
 * 
 * Makes intelligent decisions about which data source to use
 * based on learned patterns, current health, context, AND semantic relevance
 */

import { CognitiveMemory } from '../memory/CognitiveMemory.js';
import { getEmbeddingService } from '../../services/embeddings/EmbeddingService.js';
import { logger } from '../../utils/logger.js';

export interface QueryIntent {
    type: string;
    domain: string;
    operation: string;
    params: any;
    priority?: 'low' | 'normal' | 'high';
    freshness?: 'stale' | 'normal' | 'realtime';
}

export interface DataSource {
    name: string;
    type: string;
    capabilities: string[];
    description?: string; // Added description for semantic matching
    isHealthy: () => Promise<boolean>;
    estimatedLatency: number;
    costPerQuery: number;
    query?: (operation: string, params: any) => Promise<any>; // Optional query method
}

export interface SourceScore {
    source: DataSource;
    score: number;
    breakdown: {
        performance: number;
        reliability: number;
        cost: number;
        freshness: number;
        history: number;
        semantic: number;
    };
    reasoning: string;
}

export interface DecisionResult {
    selectedSource: DataSource;
    score: number;
    confidence: number;
    reasoning: string;
    alternatives: SourceScore[];
}

export class DecisionEngine {
    private memory: CognitiveMemory;
    private embeddings = getEmbeddingService();
    private sourceEmbeddings: Map<string, number[]> = new Map();

    // Scoring weights (can be tuned based on priority)
    private weights = {
        performance: 0.20,
        reliability: 0.20,
        cost: 0.15,
        freshness: 0.05,
        history: 0.10,
        semantic: 0.30 // High weight for semantic relevance
    };

    constructor(memory: CognitiveMemory) {
        this.memory = memory;
        this.initializeEmbeddings().catch(err => {
            logger.warn('Failed to initialize source embeddings:', err);
        });
    }

    private async initializeEmbeddings() {
        // Initialize embedding service (and GPU bridge if applicable)
        await this.embeddings.initialize();
    }

    /**
     * Analyze query intent to understand requirements
     */
    async analyzeIntent(query: any): Promise<QueryIntent> {
        // Extract intent from query structure
        const intent: QueryIntent = {
            type: query.type || 'unknown',
            domain: query.domain || this.inferDomain(query),
            operation: query.operation || this.inferOperation(query),
            params: query.params || {},
            priority: query.priority || 'normal',
            freshness: query.freshness || 'normal'
        };

        return intent;
    }

    private inferOperation(query: any): string {
        if (query?.type && typeof query.type === 'string' && query.type.includes('.')) {
            const parts = query.type.split('.');
            if (parts.length > 1) return parts[1];
        }
        return 'read';
    }

    /**
     * Score all available sources for a query
     */
    async scoreAllSources(
        sources: DataSource[],
        intent: QueryIntent
    ): Promise<SourceScore[]> {
        // Ensure embeddings service is ready
        await this.embeddings.initialize();

        const scores = await Promise.all(
            sources.map(source => this.scoreSource(source, intent))
        );

        // Sort by score descending
        return scores.sort((a, b) => b.score - a.score);
    }

    /**
     * Score a single source for this query
     */
    async scoreSource(
        source: DataSource,
        intent: QueryIntent
    ): Promise<SourceScore> {
        // Adjust weights based on priority
        const weights = this.getWeights(intent);

        // Calculate individual scores
        const performance = await this.scorePerformance(source, intent);
        const reliability = await this.scoreReliability(source, intent);
        const cost = this.scoreCost(source, intent);
        const freshness = this.scoreFreshness(source, intent);
        const history = await this.scoreHistory(source, intent);
        const semantic = await this.scoreSemanticRelevance(source, intent);

        // Weighted total
        const totalScore =
            performance * weights.performance +
            reliability * weights.reliability +
            cost * weights.cost +
            freshness * weights.freshness +
            history * weights.history +
            semantic * weights.semantic;

        // Generate reasoning
        const reasoning = this.generateReasoning({
            performance,
            reliability,
            cost,
            freshness,
            history,
            semantic
        }, weights);

        return {
            source,
            score: totalScore,
            breakdown: {
                performance,
                reliability,
                cost,
                freshness,
                history,
                semantic
            },
            reasoning
        };
    }

    /**
     * Make final decision from scored sources
     */
    async decide(
        sources: DataSource[],
        intent: QueryIntent
    ): Promise<DecisionResult> {
        const scored = await this.scoreAllSources(sources, intent);

        if (scored.length === 0) {
            throw new Error('No available sources for this query');
        }

        const best = scored[0];

        // Confidence is based on score gap between #1 and #2
        const confidence = scored.length > 1
            ? Math.min(1.0, (best.score - scored[1].score) / 0.3 + 0.5)
            : 1.0;

        return {
            selectedSource: best.source,
            score: best.score,
            confidence,
            reasoning: best.reasoning,
            alternatives: scored.slice(1, 4) // Top 3 alternatives
        };
    }

    /**
     * Score semantic relevance using embeddings
     */
    private async scoreSemanticRelevance(
        source: DataSource,
        intent: QueryIntent
    ): Promise<number> {
        try {
            // 1. Get or generate embedding for source description/capabilities
            let sourceVector = this.sourceEmbeddings.get(source.name);
            if (!sourceVector) {
                const description = `${source.name} ${source.type} ${source.capabilities.join(' ')} ${source.description || ''}`;
                sourceVector = await this.embeddings.generateEmbedding(description);
                this.sourceEmbeddings.set(source.name, sourceVector);
            }

            // 2. Generate embedding for query intent
            const queryText = `${intent.type} ${intent.domain} ${intent.operation} ${JSON.stringify(intent.params)}`;
            const queryVector = await this.embeddings.generateEmbedding(queryText);

            // 3. Calculate cosine similarity
            return this.cosineSimilarity(sourceVector, queryVector);

        } catch (error) {
            // Fallback to keyword matching if embedding fails
            logger.warn(`Semantic scoring failed for ${source.name}:`, error);

            // Simple keyword overlap fallback
            const queryStr = JSON.stringify(intent).toLowerCase();
            const capsStr = source.capabilities.join(' ').toLowerCase();
            if (capsStr.includes(intent.type.toLowerCase())) return 0.8;
            if (queryStr.includes(source.name.toLowerCase())) return 0.6;

            return 0.3; // Default low relevance
        }
    }

    private cosineSimilarity(vecA: number[], vecB: number[]): number {
        if (vecA.length !== vecB.length) return 0;
        let dot = 0;
        let magA = 0;
        let magB = 0;
        for (let i = 0; i < vecA.length; i++) {
            dot += vecA[i] * vecB[i];
            magA += vecA[i] * vecA[i];
            magB += vecB[i] * vecB[i];
        }
        return magA === 0 || magB === 0 ? 0 : dot / (Math.sqrt(magA) * Math.sqrt(magB));
    }

    /**
     * Score performance (latency, throughput)
     */
    private async scorePerformance(
        source: DataSource,
        intent: QueryIntent
    ): Promise<number> {
        // Get average latency from memory
        const avgLatency = await this.memory.getAverageLatency(source.name);

        // Normalize: 0-50ms = 1.0, 500ms+ = 0.0
        const latencyScore = Math.max(0, Math.min(1, 1 - (avgLatency / 500)));

        // For high priority queries, penalize slow sources more
        if (intent.priority === 'high' && avgLatency > 200) {
            return latencyScore * 0.5;
        }

        return latencyScore;
    }

    /**
     * Score reliability (uptime, success rate)
     */
    private async scoreReliability(
        source: DataSource,
        intent: QueryIntent
    ): Promise<number> {
        // Current health check
        const isHealthy = await source.isHealthy();
        if (!isHealthy) {
            return 0.0; // Unhealthy source gets zero score
        }

        // Historical success rate
        const successRate = await this.memory.getSuccessRate(
            source.name,
            intent.type
        );

        // Get failure intelligence
        const intelligence = await this.memory.getSourceIntelligence(source.name);

        // Penalize if there were recent failures
        const recentFailurePenalty = Math.min(0.3, intelligence.recentFailures * 0.05);

        return Math.max(0, successRate - recentFailurePenalty);
    }

    /**
     * Score cost (API costs, compute)
     */
    private scoreCost(source: DataSource, intent: QueryIntent): number {
        const cost = source.costPerQuery || 0;

        // Normalize: $0 = 1.0, $0.10+ = 0.0
        const costScore = Math.max(0, Math.min(1, 1 - (cost / 0.1)));

        // For low priority queries, strongly prefer free sources
        if (intent.priority === 'low' && cost > 0) {
            return costScore * 0.5;
        }

        return costScore;
    }

    /**
     * Score data freshness
     */
    private scoreFreshness(source: DataSource, intent: QueryIntent): number {
        // Database sources are typically fresher than cached/file sources
        const freshnessMap: Record<string, number> = {
            'database': 1.0,
            'api': 0.9,
            'cache': 0.5,
            'file': 0.3
        };

        const baseScore = freshnessMap[source.type] || 0.5;

        // Adjust based on required freshness
        if (intent.freshness === 'realtime') {
            return source.type === 'database' || source.type === 'api' ? 1.0 : 0.2;
        } else if (intent.freshness === 'stale') {
            return 1.0; // Don't care about freshness
        }

        return baseScore;
    }

    /**
     * Score based on historical patterns
     */
    private async scoreHistory(
        source: DataSource,
        intent: QueryIntent
    ): Promise<number> {
        // Check if this source has been successful for similar queries
        const historyScore = await this.memory.getSimilarQuerySuccess(
            intent.type,
            intent.params
        );

        return historyScore;
    }

    /**
     * Adjust weights based on query intent
     */
    private getWeights(intent: QueryIntent) {
        const weights = { ...this.weights };

        // High priority: favor performance and reliability
        if (intent.priority === 'high') {
            weights.performance = 0.30;
            weights.reliability = 0.30;
            weights.semantic = 0.20; // Reduce semantic weight slightly
            weights.cost = 0.10;
            weights.freshness = 0.05;
            weights.history = 0.05;
        }

        // Low priority: favor cost
        else if (intent.priority === 'low') {
            weights.performance = 0.10;
            weights.reliability = 0.20;
            weights.semantic = 0.20;
            weights.cost = 0.40;
            weights.freshness = 0.05;
            weights.history = 0.05;
        }

        // Realtime freshness: favor databases/APIs
        if (intent.freshness === 'realtime') {
            weights.freshness = 0.30;
            weights.performance = 0.20;
            weights.semantic = 0.20;
            weights.reliability = 0.20;
            weights.cost = 0.10;
        }

        return weights;
    }

    /**
     * Generate human-readable reasoning
     */
    private generateReasoning(
        breakdown: SourceScore['breakdown'],
        weights: typeof this.weights
    ): string {
        const reasons: string[] = [];

        // Find strongest factor
        const factors = Object.entries(breakdown).sort((a, b) => b[1] - a[1]);
        const [topFactor, topScore] = factors[0];

        if (topScore > 0.8) {
            reasons.push(`Excellent ${topFactor} (${(topScore * 100).toFixed(0)}%)`);
        } else if (topScore > 0.6) {
            reasons.push(`Good ${topFactor} (${(topScore * 100).toFixed(0)}%)`);
        }

        // Explicitly mention semantic match if high
        if (breakdown.semantic > 0.8) {
            reasons.push(`Strong conceptual match`);
        }

        // Note any weak factors
        for (const [factor, score] of factors) {
            if (score < 0.3 && weights[factor as keyof typeof weights] > 0.15) {
                reasons.push(`Low ${factor} (${(score * 100).toFixed(0)}%)`);
            }
        }

        return reasons.join(', ') || 'Balanced scores across all factors';
    }

    /**
     * Infer domain from query structure
     */
    private inferDomain(query: any): string {
        // Simple heuristics
        if (query?.type && typeof query.type === 'string' && query.type.includes('.')) {
            return query.type.split('.')[0];
        }
        if (query.uri?.startsWith('agents://')) return 'agents';
        if (query.uri?.startsWith('security://')) return 'security';
        if (query.tool?.includes('search')) return 'search';
        if (query.tool?.includes('agent')) return 'agents';

        return 'general';
    }
}