Kraft102's picture
Deploy from GitHub Actions 2025-12-15_12-42-58
66f3b51 verified
/**
* 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';
}
}