UNPKG

zrald

Version:

Advanced Graph RAG MCP Server with sophisticated graph structures, operators, and agentic capabilities for AI agents

411 lines 17.5 kB
import { VDBOperator, PPROperator } from '../operators/node-operators.js'; import { OneHopOperator, AggregatorOperator } from '../operators/relationship-operators.js'; import { FromRelOperator, OccurrenceOperator } from '../operators/chunk-operators.js'; import { KHopPathOperator, SteinerOperator } from '../operators/subgraph-operators.js'; export class OperatorExecutor { operators = new Map(); graphDb; vectorStore; constructor(graphDb, vectorStore) { this.graphDb = graphDb; this.vectorStore = vectorStore; this.initializeOperators(); } initializeOperators() { // Node operators this.operators.set('VDBOperator', new VDBOperator(this.graphDb, this.vectorStore)); this.operators.set('PPROperator', new PPROperator(this.graphDb, this.vectorStore)); // Relationship operators this.operators.set('OneHopOperator', new OneHopOperator(this.graphDb, this.vectorStore)); this.operators.set('AggregatorOperator', new AggregatorOperator(this.graphDb, this.vectorStore)); // Chunk operators this.operators.set('FromRelOperator', new FromRelOperator(this.graphDb, this.vectorStore)); this.operators.set('OccurrenceOperator', new OccurrenceOperator(this.graphDb, this.vectorStore)); // Subgraph operators this.operators.set('KHopPathOperator', new KHopPathOperator(this.graphDb, this.vectorStore)); this.operators.set('SteinerOperator', new SteinerOperator(this.graphDb, this.vectorStore)); } async executeChain(chain) { const startTime = Date.now(); try { let result; switch (chain.execution_pattern) { case 'sequential': result = await this.executeSequential(chain); break; case 'parallel': result = await this.executeParallel(chain); break; case 'adaptive': result = await this.executeAdaptive(chain); break; default: throw new Error(`Unknown execution pattern: ${chain.execution_pattern}`); } const executionTime = Date.now() - startTime; result.metadata = { ...result.metadata, total_execution_time_ms: executionTime, execution_pattern: chain.execution_pattern, fusion_strategy: chain.fusion_strategy, operators_executed: chain.operators.length }; return result; } catch (error) { console.error('Error executing operator chain:', error); throw error; } } async executeSequential(chain) { let currentResult = { nodes: [], relationships: [], chunks: [], scores: {}, metadata: {} }; const executionOrder = this.resolveDependencies(chain.operators); for (const operatorConfig of executionOrder) { const operator = this.operators.get(operatorConfig.type); if (!operator) { throw new Error(`Unknown operator type: ${operatorConfig.type}`); } // Merge previous results into current config if needed const enrichedConfig = this.enrichConfigWithPreviousResults(operatorConfig.config, currentResult, operatorConfig.dependencies); const operatorResult = await operator.run(enrichedConfig); // Chain results sequentially currentResult = this.fuseResults([currentResult, operatorResult], 'union'); } return currentResult; } async executeParallel(chain) { const dependencyGroups = this.groupByDependencies(chain.operators); let currentResults = []; for (const group of dependencyGroups) { const groupPromises = group.map(async (operatorConfig) => { const operator = this.operators.get(operatorConfig.type); if (!operator) { throw new Error(`Unknown operator type: ${operatorConfig.type}`); } // Enrich config with results from previous groups const enrichedConfig = this.enrichConfigWithPreviousResults(operatorConfig.config, currentResults.length > 0 ? this.fuseResults(currentResults, 'union') : { nodes: [], relationships: [], chunks: [], scores: {}, metadata: {} }, operatorConfig.dependencies); return await operator.run(enrichedConfig); }); const groupResults = await Promise.all(groupPromises); currentResults = groupResults; } return this.fuseResults(currentResults, chain.fusion_strategy); } async executeAdaptive(chain) { // Adaptive execution analyzes intermediate results and adjusts the execution plan const executionPlan = await this.createAdaptiveExecutionPlan(chain); let currentResult = { nodes: [], relationships: [], chunks: [], scores: {}, metadata: {} }; for (const step of executionPlan.steps) { if (step.condition && !this.evaluateCondition(step.condition, currentResult)) { console.log(`Skipping step ${step.operatorType} due to condition: ${step.condition}`); continue; } const operator = this.operators.get(step.operatorType); if (!operator) { throw new Error(`Unknown operator type: ${step.operatorType}`); } const adaptedConfig = await this.adaptConfigBasedOnResults(step.config, currentResult, step.adaptationRules); const stepResult = await operator.run(adaptedConfig); // Decide how to merge based on step strategy if (step.mergeStrategy === 'replace') { currentResult = stepResult; } else { currentResult = this.fuseResults([currentResult, stepResult], step.mergeStrategy || 'union'); } // Check if we should terminate early if (step.terminationCondition && this.evaluateCondition(step.terminationCondition, currentResult)) { console.log(`Early termination triggered: ${step.terminationCondition}`); break; } } return currentResult; } resolveDependencies(operators) { const resolved = []; const visited = new Set(); const visiting = new Set(); const visit = (op) => { if (visiting.has(op.type)) { throw new Error(`Circular dependency detected involving ${op.type}`); } if (visited.has(op.type)) { return; } visiting.add(op.type); for (const depType of op.dependencies || []) { const dependency = operators.find(o => o.type === depType); if (dependency) { visit(dependency); } } visiting.delete(op.type); visited.add(op.type); resolved.push(op); }; for (const op of operators) { visit(op); } return resolved; } groupByDependencies(operators) { const groups = []; const processed = new Set(); while (processed.size < operators.length) { const currentGroup = operators.filter(op => !processed.has(op.type) && (op.dependencies || []).every((dep) => processed.has(dep))); if (currentGroup.length === 0) { throw new Error('Cannot resolve dependencies - possible circular dependency'); } groups.push(currentGroup); currentGroup.forEach(op => processed.add(op.type)); } return groups; } enrichConfigWithPreviousResults(config, previousResult, dependencies) { const enrichedConfig = { ...config }; // Add node IDs from previous results if the operator needs them if (previousResult.nodes.length > 0) { if (!enrichedConfig.source_nodes && dependencies.length > 0) { enrichedConfig.source_nodes = previousResult.nodes.map(n => n.id); } if (!enrichedConfig.seed_nodes && dependencies.length > 0) { enrichedConfig.seed_nodes = previousResult.nodes.slice(0, 5).map(n => n.id); } } // Add relationship IDs if needed if (previousResult.relationships.length > 0 && !enrichedConfig.relationship_ids) { enrichedConfig.relationship_ids = previousResult.relationships.map(r => r.id); } // Add entity IDs for co-occurrence analysis if (previousResult.nodes.length > 0 && !enrichedConfig.entities) { enrichedConfig.entities = previousResult.nodes .filter(n => n.type === 'entity') .map(n => n.id); } return enrichedConfig; } fuseResults(results, strategy) { if (results.length === 0) { return { nodes: [], relationships: [], chunks: [], scores: {}, metadata: {} }; } if (results.length === 1) { return results[0]; } switch (strategy) { case 'union': return this.fuseUnion(results); case 'intersection': return this.fuseIntersection(results); case 'weighted_average': return this.fuseWeightedAverage(results); default: return this.fuseUnion(results); } } fuseUnion(results) { const allNodes = []; const allRelationships = []; const allChunks = []; const combinedScores = {}; const combinedMetadata = {}; const seenNodeIds = new Set(); const seenRelIds = new Set(); const seenChunkIds = new Set(); for (const result of results) { // Merge nodes for (const node of result.nodes) { if (!seenNodeIds.has(node.id)) { allNodes.push(node); seenNodeIds.add(node.id); } } // Merge relationships for (const rel of result.relationships) { if (!seenRelIds.has(rel.id)) { allRelationships.push(rel); seenRelIds.add(rel.id); } } // Merge chunks if (result.chunks) { for (const chunk of result.chunks) { if (!seenChunkIds.has(chunk.id)) { allChunks.push(chunk); seenChunkIds.add(chunk.id); } } } // Merge scores (take maximum) if (result.scores) { for (const [id, score] of Object.entries(result.scores)) { combinedScores[id] = Math.max(combinedScores[id] || 0, score); } } // Merge metadata if (result.metadata) { Object.assign(combinedMetadata, result.metadata); } } return { nodes: allNodes, relationships: allRelationships, chunks: allChunks, scores: combinedScores, metadata: { ...combinedMetadata, fusion_strategy: 'union', results_fused: results.length } }; } fuseIntersection(results) { if (results.length === 0) { return { nodes: [], relationships: [], chunks: [], scores: {}, metadata: {} }; } let intersectionNodes = results[0].nodes; let intersectionRels = results[0].relationships; let intersectionChunks = results[0].chunks || []; for (let i = 1; i < results.length; i++) { const currentResult = results[i]; intersectionNodes = intersectionNodes.filter(node => currentResult.nodes.some(n => n.id === node.id)); intersectionRels = intersectionRels.filter(rel => currentResult.relationships.some(r => r.id === rel.id)); if (currentResult.chunks) { intersectionChunks = intersectionChunks.filter(chunk => currentResult.chunks.some(c => c.id === chunk.id)); } } // Average scores for intersecting items const averageScores = {}; const allIds = new Set([ ...intersectionNodes.map(n => n.id), ...intersectionRels.map(r => r.id), ...intersectionChunks.map(c => c.id) ]); for (const id of allIds) { const scores = results .map(r => r.scores?.[id] || 0) .filter(s => s > 0); if (scores.length > 0) { averageScores[id] = scores.reduce((sum, s) => sum + s, 0) / scores.length; } } return { nodes: intersectionNodes, relationships: intersectionRels, chunks: intersectionChunks, scores: averageScores, metadata: { fusion_strategy: 'intersection', results_fused: results.length, intersection_size: { nodes: intersectionNodes.length, relationships: intersectionRels.length, chunks: intersectionChunks.length } } }; } fuseWeightedAverage(results) { // Use union for structure, but weighted average for scores const unionResult = this.fuseUnion(results); // Recalculate scores as weighted averages const weightedScores = {}; const weights = results.map((_, i) => 1 / (i + 1)); // Decreasing weights const totalWeight = weights.reduce((sum, w) => sum + w, 0); for (const id of Object.keys(unionResult.scores || {})) { let weightedSum = 0; let applicableWeight = 0; for (let i = 0; i < results.length; i++) { const score = results[i].scores?.[id]; if (score !== undefined) { weightedSum += score * weights[i]; applicableWeight += weights[i]; } } if (applicableWeight > 0) { weightedScores[id] = weightedSum / applicableWeight; } } return { ...unionResult, scores: weightedScores, metadata: { ...unionResult.metadata, fusion_strategy: 'weighted_average', weights_used: weights } }; } async createAdaptiveExecutionPlan(chain) { // Create an adaptive execution plan based on the operator chain const steps = chain.operators.map(op => ({ operatorType: op.type, config: op.config, mergeStrategy: 'union', adaptationRules: { // Rules for adapting configuration based on intermediate results adjustTopK: true, adjustThresholds: true, filterByRelevance: true } })); return { steps }; } evaluateCondition(condition, result) { // Simple condition evaluation - in practice, this would be more sophisticated if (condition.includes('nodes_count >')) { const threshold = parseInt(condition.split('>')[1].trim()); return result.nodes.length > threshold; } if (condition.includes('avg_score >')) { const threshold = parseFloat(condition.split('>')[1].trim()); const scores = Object.values(result.scores || {}); const avgScore = scores.length > 0 ? scores.reduce((sum, s) => sum + s, 0) / scores.length : 0; return avgScore > threshold; } return true; } async adaptConfigBasedOnResults(config, currentResult, adaptationRules) { const adaptedConfig = { ...config }; if (adaptationRules.adjustTopK && currentResult.nodes.length > 0) { // Adjust top_k based on current result size const currentSize = currentResult.nodes.length; if (currentSize < 5) { adaptedConfig.top_k = Math.max(adaptedConfig.top_k || 10, 20); } else if (currentSize > 50) { adaptedConfig.top_k = Math.min(adaptedConfig.top_k || 10, 5); } } if (adaptationRules.adjustThresholds && currentResult.scores) { // Adjust similarity thresholds based on score distribution const scores = Object.values(currentResult.scores); if (scores.length > 0) { const avgScore = scores.reduce((sum, s) => sum + s, 0) / scores.length; if (avgScore < 0.3) { adaptedConfig.similarity_threshold = Math.max(0.1, (adaptedConfig.similarity_threshold || 0.7) - 0.2); } } } return adaptedConfig; } } //# sourceMappingURL=operator-executor.js.map