zrald
Version:
Advanced Graph RAG MCP Server with sophisticated graph structures, operators, and agentic capabilities for AI agents
411 lines • 17.5 kB
JavaScript
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