UNPKG

@codai/memorai-mcp

Version:

MemorAI CBD-based MCP Server - High-Performance Vector Memory System

801 lines (796 loc) 32.8 kB
/** * Phase 3: AI-Powered Memory Intelligence - Predictive Memory Engine * * Revolutionary predictive capabilities that set new industry standards: * - Predicts what memories users will need before they search * - Optimizes memory creation timing * - Forecasts relationship formation and importance evolution * - Provides proactive memory management suggestions */ export class PredictiveMemoryEngine { openaiClient; memories; predictionHistory; userPatterns; predictionCache; cacheTimeout = 5 * 60 * 1000; // 5 minutes constructor(openaiClient, memories) { this.openaiClient = openaiClient; this.memories = memories || new Map(); this.predictionHistory = new Map(); this.userPatterns = new Map(); this.predictionCache = new Map(); } /** * Predict what memories user will need before they search * Revolutionary proactive memory suggestions */ async predictNeededMemories(agentId, context) { const cacheKey = `needed_memories_${agentId}_${JSON.stringify(context)}`; const cached = this.getCachedPrediction(cacheKey); if (cached) return cached; try { // Get agent's memories and activity patterns const agentMemories = Array.from(this.memories.values()) .filter(m => m.metadata.agentId === agentId); if (agentMemories.length === 0) { return []; } // Analyze user patterns const userPattern = await this.analyzeUserPattern(agentId, context); // Score memories based on predicted need const scoredMemories = await this.scoreMemoriesByPredictedNeed(agentMemories, context, userPattern); // Generate predictions using AI if available const aiPredictions = this.openaiClient ? await this.generateAIPredictions(agentMemories, context, userPattern) : []; // Combine and rank predictions const predictions = await this.combineAndRankPredictions(scoredMemories, aiPredictions, context); this.cachePrediction(cacheKey, predictions); return predictions; } catch (error) { console.error('Error predicting needed memories:', error); return []; } } /** * Predict optimal memory creation timing * Smart timing recommendations based on context and patterns */ async predictMemoryCreationTiming(agentId, contentType, context) { try { const userPattern = await this.analyzeUserPattern(agentId, context); // Analyze timing factors const factors = []; // Current workload analysis const workloadFactor = this.analyzeWorkloadTiming(context); factors.push(workloadFactor); // Time of day optimization const timeFactor = this.analyzeTimeOfDayOptimal(userPattern, new Date()); factors.push(timeFactor); // Memory type considerations const typeFactor = this.analyzeMemoryTypeTiming(contentType, userPattern); factors.push(typeFactor); // Calculate optimal timing const optimalTiming = this.calculateOptimalTiming(factors, context); return optimalTiming; } catch (error) { console.error('Error predicting optimal timing:', error); return { recommendedTime: new Date().toISOString(), reasoning: 'Using current time due to prediction error', confidence: 0.3, factors: [] }; } } /** * Predict memory relationship formation * Forecast future connections between memories */ async predictFutureRelationships(memoryId, timeHorizon = 30 // days ) { try { const sourceMemory = this.memories.get(memoryId); if (!sourceMemory) { return []; } const agentMemories = Array.from(this.memories.values()) .filter(m => m.metadata.agentId === sourceMemory.metadata.agentId); const predictions = []; for (const targetMemory of agentMemories) { if (targetMemory.id === memoryId) continue; // Skip if relationship already exists if (this.hasExistingRelationship(sourceMemory, targetMemory)) { continue; } const prediction = await this.predictRelationshipFormation(sourceMemory, targetMemory, timeHorizon); if (prediction && prediction.formationProbability > 0.3) { predictions.push(prediction); } } // Sort by formation probability return predictions.sort((a, b) => b.formationProbability - a.formationProbability); } catch (error) { console.error('Error predicting relationships:', error); return []; } } /** * Predict memory importance evolution * Forecast how memory importance will change over time */ async predictImportanceChanges(memoryId, timeHorizon = 90 // days ) { try { const memory = this.memories.get(memoryId); if (!memory) { throw new Error(`Memory ${memoryId} not found`); } const currentImportance = memory.metadata.importance || 0.5; // Generate time points for prediction const timePoints = this.generateTimePoints(timeHorizon); // Analyze importance drivers const drivers = await this.analyzeImportanceDrivers(memory); // Predict importance at each time point const predictedImportance = await this.forecastImportanceTrajectory(memory, drivers, timePoints); // Determine trend direction const trendDirection = this.determineTrendDirection(predictedImportance); // Calculate confidence intervals const confidenceInterval = this.calculateConfidenceIntervals(predictedImportance, drivers); return { memoryId, currentImportance, predictedImportance, timePoints: timePoints.map(t => t.toISOString()), trendDirection, confidenceInterval, factors: drivers }; } catch (error) { console.error('Error predicting importance changes:', error); throw error; } } // Private helper methods getCachedPrediction(key) { const cached = this.predictionCache.get(key); if (cached && Date.now() - cached.timestamp < this.cacheTimeout) { return cached.data; } return null; } cachePrediction(key, data) { this.predictionCache.set(key, { data, timestamp: Date.now() }); } async analyzeUserPattern(agentId, context) { // Check if we have cached pattern let pattern = this.userPatterns.get(agentId); if (!pattern) { // Analyze from memory access patterns pattern = await this.extractUserPatternFromMemories(agentId); this.userPatterns.set(agentId, pattern); } // Update with current context if (context) { pattern = this.updatePatternWithContext(pattern, context); } return pattern; } async extractUserPatternFromMemories(agentId) { const memories = Array.from(this.memories.values()) .filter(m => m.metadata.agentId === agentId); // Analyze creation times for peak hours const creationHours = memories .map(m => new Date(m.metadata.timestamp).getHours()) .reduce((acc, hour) => { acc[hour] = (acc[hour] || 0) + 1; return acc; }, {}); const peakHours = Object.entries(creationHours) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([hour]) => `${hour}:00`); // Analyze preferred memory types const memoryTypes = memories .map(m => m.metadata.entityType || 'general') .reduce((acc, type) => { acc[type] = (acc[type] || 0) + 1; return acc; }, {}); const preferredMemoryTypes = Object.entries(memoryTypes) .sort(([, a], [, b]) => b - a) .slice(0, 3) .map(([type]) => type); return { peakHours, preferredMemoryTypes, searchPatterns: [], // Would need search history collaborationStyle: 'individual', // Default learningStyle: 'textual' // Default }; } updatePatternWithContext(pattern, context) { // Update pattern based on current context return { ...pattern, // Could enhance pattern with real-time context data }; } async scoreMemoriesByPredictedNeed(memories, context, userPattern) { const predictions = []; for (const memory of memories) { const score = await this.calculateNeedScore(memory, context, userPattern); if (score.predictedRelevance > 0.3) { predictions.push({ memoryId: memory.id, title: this.extractTitle(memory.content), content: memory.content.substring(0, 200) + '...', predictedRelevance: score.predictedRelevance, reasoning: score.reasoning, suggestedActions: score.suggestedActions, confidence: score.confidence, timeToNeed: score.timeToNeed }); } } return predictions; } async calculateNeedScore(memory, context, userPattern) { let score = 0.0; const reasons = []; const actions = []; // Time-based scoring const timeScore = this.calculateTimeBasedScore(memory, context); score += timeScore * 0.3; if (timeScore > 0.5) { reasons.push('Recent access pattern suggests relevance'); } // Context-based scoring const contextScore = this.calculateContextScore(memory, context); score += contextScore * 0.4; if (contextScore > 0.5) { reasons.push('Matches current task context'); actions.push('Review for current task'); } // Pattern-based scoring const patternScore = this.calculatePatternScore(memory, userPattern); score += patternScore * 0.3; if (patternScore > 0.5) { reasons.push('Aligns with user behavior patterns'); } // Calculate time to need const timeToNeed = this.estimateTimeToNeed(score, context); return { predictedRelevance: Math.min(score, 1.0), reasoning: reasons.join('; ') || 'Low relevance score', suggestedActions: actions.length > 0 ? actions : ['Monitor for future relevance'], confidence: score * 0.8, // Slightly lower confidence than score timeToNeed }; } calculateTimeBasedScore(memory, context) { const now = new Date(); const memoryTime = new Date(memory.metadata.timestamp); const daysSince = (now.getTime() - memoryTime.getTime()) / (1000 * 60 * 60 * 24); // Recent memories get higher scores if (daysSince < 1) return 0.9; if (daysSince < 7) return 0.7; if (daysSince < 30) return 0.5; return 0.2; } calculateContextScore(memory, context) { let score = 0.0; // Check if memory relates to current task if (context.currentTask) { const taskWords = context.currentTask.toLowerCase().split(' '); const memoryWords = memory.content.toLowerCase().split(' '); const overlap = taskWords.filter(word => memoryWords.includes(word)).length; score += (overlap / taskWords.length) * 0.5; } // Check recent activity relevance if (context.recentActivity) { const recentMemoryIds = context.recentActivity .flatMap(a => a.memoryIds) .filter(id => id === memory.id); if (recentMemoryIds.length > 0) { score += 0.4; } } // Check working memory if (context.workingMemory && context.workingMemory.includes(memory.id)) { score += 0.3; } return Math.min(score, 1.0); } calculatePatternScore(memory, userPattern) { let score = 0.0; // Check if memory type is preferred const memoryType = memory.metadata.entityType || 'general'; if (userPattern.preferredMemoryTypes.includes(memoryType)) { score += 0.4; } // Check time pattern match const memoryHour = new Date(memory.metadata.timestamp).getHours(); const currentHour = new Date().getHours(); if (userPattern.peakHours.includes(`${currentHour}:00`)) { score += 0.3; } return Math.min(score, 1.0); } estimateTimeToNeed(score, context) { // Higher scores mean sooner need if (score > 0.8) return 5; // 5 minutes if (score > 0.6) return 30; // 30 minutes if (score > 0.4) return 120; // 2 hours return 480; // 8 hours } async generateAIPredictions(memories, context, userPattern) { if (!this.openaiClient) { return []; } try { const prompt = this.buildPredictionPrompt(memories, context, userPattern); const response = await this.openaiClient.chat.completions.create({ model: 'gpt-4', messages: [{ role: 'user', content: prompt }], temperature: 0.3, max_tokens: 2000 }); const aiResponse = response.choices[0]?.message?.content; if (!aiResponse) return []; return this.parseAIPredictions(aiResponse, memories); } catch (error) { console.error('Error generating AI predictions:', error); return []; } } buildPredictionPrompt(memories, context, userPattern) { const memoryPreview = memories.slice(0, 10).map(m => `${m.id}: ${m.content.substring(0, 100)}...`).join('\n'); return ` As an AI memory prediction system, analyze the user's current context and predict which memories they will likely need soon. Current Context: - Task: ${context.currentTask || 'Unknown'} - Time: ${context.timeOfDay} - Recent Activity: ${context.recentActivity?.length || 0} recent actions User Pattern: - Peak Hours: ${userPattern.peakHours.join(', ')} - Preferred Types: ${userPattern.preferredMemoryTypes.join(', ')} - Learning Style: ${userPattern.learningStyle} Available Memories (sample): ${memoryPreview} Predict the top 5 memories the user will likely need in the next few hours. For each, provide: 1. Memory ID 2. Relevance score (0.0-1.0) 3. Reasoning 4. Suggested actions 5. Confidence level Format as JSON array with these fields. `; } parseAIPredictions(aiResponse, memories) { try { const parsed = JSON.parse(aiResponse); if (!Array.isArray(parsed)) return []; return parsed.map(p => ({ memoryId: p.memoryId || '', title: this.extractTitle(memories.find(m => m.id === p.memoryId)?.content || ''), content: memories.find(m => m.id === p.memoryId)?.content?.substring(0, 200) + '...' || '', predictedRelevance: p.relevance || 0.5, reasoning: p.reasoning || 'AI prediction', suggestedActions: Array.isArray(p.actions) ? p.actions : ['Review'], confidence: p.confidence || 0.5, timeToNeed: 60 // Default 1 hour })).filter(p => p.memoryId); } catch (error) { console.error('Error parsing AI predictions:', error); return []; } } async combineAndRankPredictions(scoredMemories, aiPredictions, context) { // Combine predictions, giving weight to both sources const combined = new Map(); // Add scored memories scoredMemories.forEach(prediction => { combined.set(prediction.memoryId, prediction); }); // Enhance with AI predictions aiPredictions.forEach(aiPrediction => { const existing = combined.get(aiPrediction.memoryId); if (existing) { // Combine scores existing.predictedRelevance = (existing.predictedRelevance + aiPrediction.predictedRelevance) / 2; existing.confidence = Math.max(existing.confidence, aiPrediction.confidence); existing.reasoning += '; ' + aiPrediction.reasoning; } else { combined.set(aiPrediction.memoryId, aiPrediction); } }); // Sort by relevance and return top predictions return Array.from(combined.values()) .sort((a, b) => b.predictedRelevance - a.predictedRelevance) .slice(0, 10); } analyzeWorkloadTiming(context) { const workload = context?.environmentalFactors?.workload || 'moderate'; let impact = 0; let description = ''; switch (workload) { case 'light': impact = 0.3; description = 'Light workload - good time for memory creation'; break; case 'moderate': impact = 0.0; description = 'Moderate workload - standard timing'; break; case 'heavy': impact = -0.4; description = 'Heavy workload - consider delaying non-critical memories'; break; case 'critical': impact = -0.8; description = 'Critical workload - delay unless urgent'; break; } return { factor: 'Current Workload', impact, description }; } analyzeTimeOfDayOptimal(pattern, currentTime) { const currentHour = `${currentTime.getHours()}:00`; const isPeakHour = pattern.peakHours.includes(currentHour); return { factor: 'Time of Day', impact: isPeakHour ? 0.4 : -0.2, description: isPeakHour ? 'Peak productivity hour - optimal for memory creation' : 'Off-peak hour - consider waiting for better timing' }; } analyzeMemoryTypeTiming(contentType, pattern) { const isPreferredType = pattern.preferredMemoryTypes.includes(contentType); return { factor: 'Memory Type Preference', impact: isPreferredType ? 0.2 : -0.1, description: isPreferredType ? 'Preferred memory type - good timing match' : 'Non-preferred type - consider alternative timing' }; } calculateOptimalTiming(factors, context) { const totalImpact = factors.reduce((sum, factor) => sum + factor.impact, 0); const avgImpact = totalImpact / factors.length; // Calculate delay based on impact let delayMinutes = 0; if (avgImpact < -0.5) delayMinutes = 120; // 2 hours else if (avgImpact < -0.2) delayMinutes = 30; // 30 minutes else if (avgImpact > 0.3) delayMinutes = -5; // Immediate (negative delay) const recommendedTime = new Date(Date.now() + delayMinutes * 60 * 1000); const confidence = Math.min(0.9, 0.5 + Math.abs(avgImpact)); return { recommendedTime: recommendedTime.toISOString(), reasoning: this.generateTimingReasoning(factors, avgImpact), confidence, factors }; } generateTimingReasoning(factors, avgImpact) { if (avgImpact > 0.3) { return 'Excellent timing - multiple factors favor immediate memory creation'; } else if (avgImpact > 0) { return 'Good timing - conditions are favorable for memory creation'; } else if (avgImpact > -0.3) { return 'Acceptable timing - some minor factors suggest slight delay'; } else { return 'Consider delaying - current conditions not optimal for memory creation'; } } hasExistingRelationship(memory1, memory2) { return memory1.relationships.some((rel) => rel.targetMemoryId === memory2.id || rel.sourceMemoryId === memory2.id); } async predictRelationshipFormation(sourceMemory, targetMemory, timeHorizon) { try { // Calculate content similarity const contentSimilarity = await this.calculateContentSimilarity(sourceMemory.content, targetMemory.content); // Calculate temporal proximity const temporalProximity = this.calculateTemporalProximity(sourceMemory, targetMemory); // Calculate project/session alignment const contextAlignment = this.calculateContextAlignment(sourceMemory, targetMemory); // Determine relationship type and strength const relationshipType = this.predictRelationshipType(sourceMemory, targetMemory); const strength = (contentSimilarity + temporalProximity + contextAlignment) / 3; // Calculate formation probability const formationProbability = this.calculateFormationProbability(strength, contentSimilarity, temporalProximity, contextAlignment); if (formationProbability < 0.3) { return null; // Too low probability } // Estimate time to formation const timeToFormation = this.estimateFormationTime(formationProbability, timeHorizon); return { sourceMemoryId: sourceMemory.id, targetMemoryId: targetMemory.id, relationshipType, strength, formationProbability, timeToFormation, reasoning: this.generateRelationshipReasoning(contentSimilarity, temporalProximity, contextAlignment, relationshipType) }; } catch (error) { console.error('Error predicting relationship formation:', error); return null; } } async calculateContentSimilarity(content1, content2) { // Simple similarity calculation - could be enhanced with embeddings const words1 = new Set(content1.toLowerCase().split(/\s+/)); const words2 = new Set(content2.toLowerCase().split(/\s+/)); const intersection = new Set([...words1].filter(word => words2.has(word))); const union = new Set([...words1, ...words2]); return intersection.size / union.size; } calculateTemporalProximity(memory1, memory2) { const time1 = new Date(memory1.metadata.timestamp).getTime(); const time2 = new Date(memory2.metadata.timestamp).getTime(); const timeDiff = Math.abs(time1 - time2); const daysDiff = timeDiff / (1000 * 60 * 60 * 24); // Closer in time = higher proximity if (daysDiff < 1) return 0.9; if (daysDiff < 7) return 0.7; if (daysDiff < 30) return 0.5; return 0.2; } calculateContextAlignment(memory1, memory2) { let alignment = 0; // Same project if (memory1.metadata.project === memory2.metadata.project) { alignment += 0.4; } // Same session if (memory1.metadata.session === memory2.metadata.session) { alignment += 0.3; } // Same entity type if (memory1.metadata.entityType === memory2.metadata.entityType) { alignment += 0.2; } // Shared tags const tags1 = memory1.metadata.tags || []; const tags2 = memory2.metadata.tags || []; const sharedTags = tags1.filter(tag => tags2.includes(tag)); if (sharedTags.length > 0) { alignment += Math.min(0.3, sharedTags.length * 0.1); } return Math.min(alignment, 1.0); } predictRelationshipType(sourceMemory, targetMemory) { // Simple heuristics - could be enhanced with AI const time1 = new Date(sourceMemory.metadata.timestamp).getTime(); const time2 = new Date(targetMemory.metadata.timestamp).getTime(); if (time2 > time1) { return 'follows'; } else if (sourceMemory.content.includes('related') || targetMemory.content.includes('related')) { return 'related'; } else if (sourceMemory.content.includes('update') || targetMemory.content.includes('update')) { return 'updates'; } else { return 'similar'; } } calculateFormationProbability(strength, contentSimilarity, temporalProximity, contextAlignment) { // Weighted combination return (strength * 0.4 + contentSimilarity * 0.3 + temporalProximity * 0.2 + contextAlignment * 0.1); } estimateFormationTime(probability, maxDays) { // Higher probability = sooner formation if (probability > 0.8) return 1; if (probability > 0.6) return 3; if (probability > 0.4) return 7; return Math.min(maxDays, 14); } generateRelationshipReasoning(contentSimilarity, temporalProximity, contextAlignment, relationshipType) { const reasons = []; if (contentSimilarity > 0.6) { reasons.push('high content similarity'); } if (temporalProximity > 0.6) { reasons.push('temporal proximity'); } if (contextAlignment > 0.6) { reasons.push('shared context'); } const baseReason = `Predicted ${relationshipType} relationship`; return reasons.length > 0 ? `${baseReason} based on ${reasons.join(', ')}` : `${baseReason} with moderate confidence`; } async analyzeImportanceDrivers(memory) { const drivers = []; // Age factor const daysSinceCreation = (Date.now() - new Date(memory.metadata.timestamp).getTime()) / (1000 * 60 * 60 * 24); drivers.push({ factor: 'Age', impact: daysSinceCreation > 30 ? -0.1 : 0.1, description: daysSinceCreation > 30 ? 'Older memories tend to lose importance' : 'Recent memory maintains relevance', confidence: 0.8 }); // Relationship count const relationshipCount = memory.relationships.length; drivers.push({ factor: 'Connectivity', impact: relationshipCount * 0.05, description: `${relationshipCount} relationships ${relationshipCount > 3 ? 'increase' : 'maintain'} importance`, confidence: 0.7 }); // Entity type const entityType = memory.metadata.entityType; const typeImportance = this.getEntityTypeImportance(entityType); drivers.push({ factor: 'Content Type', impact: typeImportance, description: `${entityType || 'general'} content type ${typeImportance > 0 ? 'enhances' : 'reduces'} long-term importance`, confidence: 0.6 }); return drivers; } getEntityTypeImportance(entityType) { switch (entityType) { case 'decision': return 0.2; case 'plan': return 0.15; case 'task': return 0.1; case 'meeting': return 0.05; case 'note': return -0.05; default: return 0; } } generateTimePoints(days) { const points = []; const now = new Date(); // Generate points at intervals for (let i = 0; i <= days; i += Math.max(1, Math.floor(days / 10))) { points.push(new Date(now.getTime() + i * 24 * 60 * 60 * 1000)); } return points; } async forecastImportanceTrajectory(memory, drivers, timePoints) { const currentImportance = memory.metadata.importance || 0.5; const trajectory = []; for (let i = 0; i < timePoints.length; i++) { const dayOffset = i * (90 / timePoints.length); // Spread over time horizon let importance = currentImportance; // Apply driver impacts over time drivers.forEach(driver => { const timeDecay = Math.exp(-dayOffset / 30); // Exponential decay importance += driver.impact * timeDecay * driver.confidence; }); // Add some random variation to simulate uncertainty const variation = (Math.random() - 0.5) * 0.1; importance += variation; // Clamp to valid range importance = Math.max(0, Math.min(1, importance)); trajectory.push(importance); } return trajectory; } determineTrendDirection(predictions) { if (predictions.length < 2) return 'stable'; const first = predictions[0]; const last = predictions[predictions.length - 1]; if (first === undefined || last === undefined) return 'stable'; const change = last - first; // Calculate variance to detect volatility const mean = predictions.reduce((sum, val) => sum + val, 0) / predictions.length; const variance = predictions.reduce((sum, val) => sum + Math.pow(val - mean, 2), 0) / predictions.length; if (variance > 0.05) return 'volatile'; if (change > 0.1) return 'increasing'; if (change < -0.1) return 'decreasing'; return 'stable'; } calculateConfidenceIntervals(predictions, drivers) { const avgConfidence = drivers.reduce((sum, d) => sum + d.confidence, 0) / drivers.length; const margin = (1 - avgConfidence) * 0.2; // Confidence affects margin size return predictions.map(prediction => ({ min: Math.max(0, prediction - margin), max: Math.min(1, prediction + margin) })); } extractTitle(content) { // Extract first line or first 50 characters as title const firstLine = content.split('\n')[0]; if (!firstLine) return 'Untitled Memory'; return firstLine.length > 50 ? firstLine.substring(0, 47) + '...' : firstLine; } /** * Record prediction accuracy for continuous learning */ async recordPredictionOutcome(predictionId, actualOutcome, accuracy, feedback) { const history = this.predictionHistory.get(predictionId); if (history) { history.actualOutcome = actualOutcome; history.accuracy = accuracy; history.feedback = feedback; } } /** * Get prediction accuracy statistics */ getPredictionStats() { const histories = Array.from(this.predictionHistory.values()); const withAccuracy = histories.filter(h => h.accuracy !== undefined); if (withAccuracy.length === 0) { return { totalPredictions: histories.length, averageAccuracy: 0, accuracyByType: {} }; } const averageAccuracy = withAccuracy.reduce((sum, h) => sum + (h.accuracy || 0), 0) / withAccuracy.length; const accuracyByType = {}; const typeGroups = withAccuracy.reduce((groups, h) => { const type = h.type; if (!groups[type]) groups[type] = []; groups[type].push(h.accuracy || 0); return groups; }, {}); Object.entries(typeGroups).forEach(([type, accuracies]) => { accuracyByType[type] = accuracies.reduce((sum, acc) => sum + acc, 0) / accuracies.length; }); return { totalPredictions: histories.length, averageAccuracy, accuracyByType }; } } //# sourceMappingURL=predictive-engine.js.map