UNPKG

zrald

Version:

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

207 lines 10.2 kB
import { BaseOperator } from './base-operator.js'; import { OneHopOperatorConfigSchema, AggregatorOperatorConfigSchema } from '../types/graph.js'; /** * OneHop Operator - Direct neighborhood exploration * Performs direct neighborhood exploration, retrieving all relationships connected to specified nodes */ export class OneHopOperator extends BaseOperator { constructor(graphDb, vectorStore) { super('OneHopOperator', 'relationship', graphDb, vectorStore); } async execute(config) { const validatedConfig = await this.validateConfig(config, OneHopOperatorConfigSchema); const allRelationships = []; const connectedNodes = []; const seenNodeIds = new Set(); const seenRelIds = new Set(); const scores = {}; // Process each source node for (const sourceNodeId of validatedConfig.source_nodes) { const sourceNode = await this.graphDb.getNode(sourceNodeId); if (!sourceNode) continue; if (!seenNodeIds.has(sourceNodeId)) { connectedNodes.push(sourceNode); seenNodeIds.add(sourceNodeId); scores[sourceNodeId] = 1.0; // Source nodes get max score } // Get relationships for this node const relationships = await this.graphDb.getRelationships(sourceNodeId, validatedConfig.direction); for (const relationship of relationships) { // Filter by relationship type if specified if (validatedConfig.relationship_types && !validatedConfig.relationship_types.includes(relationship.type)) { continue; } if (!seenRelIds.has(relationship.id)) { allRelationships.push(relationship); seenRelIds.add(relationship.id); } // Add connected nodes const targetNodeId = relationship.source_id === sourceNodeId ? relationship.target_id : relationship.source_id; if (!seenNodeIds.has(targetNodeId)) { const targetNode = await this.graphDb.getNode(targetNodeId); if (targetNode) { connectedNodes.push(targetNode); seenNodeIds.add(targetNodeId); // Score based on relationship weight and confidence scores[targetNodeId] = relationship.weight * relationship.confidence * 0.8; } } } } // Apply depth filtering if max_depth > 1 if (validatedConfig.max_depth > 1) { await this.expandToMaxDepth(validatedConfig, connectedNodes, allRelationships, seenNodeIds, seenRelIds, scores, 1); } return this.createResult(connectedNodes, allRelationships, [], this.normalizeScores(scores), { exploration_type: 'one_hop', direction: validatedConfig.direction, max_depth: validatedConfig.max_depth, relationship_types_filter: validatedConfig.relationship_types, total_hops_explored: Math.min(validatedConfig.max_depth, 1) }); } async expandToMaxDepth(config, nodes, relationships, seenNodeIds, seenRelIds, scores, currentDepth) { if (currentDepth >= config.max_depth) return; const currentLevelNodes = nodes.filter(node => !config.source_nodes.includes(node.id) && scores[node.id] > 0.5 / (currentDepth + 1)); for (const node of currentLevelNodes) { const nodeRelationships = await this.graphDb.getRelationships(node.id, config.direction); for (const relationship of nodeRelationships) { if (config.relationship_types && !config.relationship_types.includes(relationship.type)) { continue; } if (!seenRelIds.has(relationship.id)) { relationships.push(relationship); seenRelIds.add(relationship.id); } const targetNodeId = relationship.source_id === node.id ? relationship.target_id : relationship.source_id; if (!seenNodeIds.has(targetNodeId)) { const targetNode = await this.graphDb.getNode(targetNodeId); if (targetNode) { nodes.push(targetNode); seenNodeIds.add(targetNodeId); // Decay score with depth const depthDecay = Math.pow(0.7, currentDepth); scores[targetNodeId] = relationship.weight * relationship.confidence * depthDecay; } } } } // Recurse to next depth await this.expandToMaxDepth(config, nodes, relationships, seenNodeIds, seenRelIds, scores, currentDepth + 1); } } /** * Aggregator Operator - Multi-relationship synthesis * Synthesizes information across multiple relationships, combining evidence from various connections */ export class AggregatorOperator extends BaseOperator { constructor(graphDb, vectorStore) { super('AggregatorOperator', 'relationship', graphDb, vectorStore); } async execute(config) { const validatedConfig = await this.validateConfig(config, AggregatorOperatorConfigSchema); const aggregatedNodes = []; const aggregatedRelationships = []; const aggregatedScores = {}; const relationshipGroups = new Map(); // Collect all relationships for each source node for (const sourceNodeId of validatedConfig.source_nodes) { const sourceNode = await this.graphDb.getNode(sourceNodeId); if (!sourceNode) continue; if (!aggregatedNodes.find(n => n.id === sourceNodeId)) { aggregatedNodes.push(sourceNode); } const relationships = await this.graphDb.getRelationships(sourceNodeId, 'both'); // Group relationships by target node for (const relationship of relationships) { const targetNodeId = relationship.source_id === sourceNodeId ? relationship.target_id : relationship.source_id; if (!relationshipGroups.has(targetNodeId)) { relationshipGroups.set(targetNodeId, []); } relationshipGroups.get(targetNodeId).push(relationship); } } // Aggregate scores for each target node for (const [targetNodeId, relationships] of relationshipGroups) { const targetNode = await this.graphDb.getNode(targetNodeId); if (!targetNode) continue; if (!aggregatedNodes.find(n => n.id === targetNodeId)) { aggregatedNodes.push(targetNode); } // Add unique relationships for (const rel of relationships) { if (!aggregatedRelationships.find(r => r.id === rel.id)) { aggregatedRelationships.push(rel); } } // Calculate aggregated score const aggregatedScore = this.calculateAggregatedScore(relationships, validatedConfig.aggregation_method, validatedConfig.relationship_weights, validatedConfig.normalize_scores); aggregatedScores[targetNodeId] = aggregatedScore; } // Apply relationship-specific scoring const relationshipScores = {}; for (const relationship of aggregatedRelationships) { const weight = validatedConfig.relationship_weights?.[relationship.type] || 1.0; relationshipScores[relationship.id] = relationship.weight * relationship.confidence * weight; } return this.createResult(aggregatedNodes, aggregatedRelationships, [], { ...aggregatedScores, ...relationshipScores }, { aggregation_method: validatedConfig.aggregation_method, total_relationship_groups: relationshipGroups.size, normalization_applied: validatedConfig.normalize_scores, relationship_types_processed: [...new Set(aggregatedRelationships.map(r => r.type))] }); } calculateAggregatedScore(relationships, method, weights, normalize = true) { if (relationships.length === 0) return 0; const scores = relationships.map(rel => { const baseScore = rel.weight * rel.confidence; const typeWeight = weights?.[rel.type] || 1.0; return baseScore * typeWeight; }); let aggregatedScore; switch (method) { case 'sum': aggregatedScore = scores.reduce((sum, score) => sum + score, 0); break; case 'average': aggregatedScore = scores.reduce((sum, score) => sum + score, 0) / scores.length; break; case 'max': aggregatedScore = Math.max(...scores); break; case 'weighted': // Weighted average with relationship confidence as weights const totalWeight = relationships.reduce((sum, rel) => sum + rel.confidence, 0); aggregatedScore = totalWeight > 0 ? scores.reduce((sum, score, i) => sum + score * relationships[i].confidence, 0) / totalWeight : 0; break; default: aggregatedScore = scores.reduce((sum, score) => sum + score, 0) / scores.length; } // Apply normalization if requested if (normalize && method === 'sum') { // Normalize sum by number of relationships to prevent bias toward highly connected nodes aggregatedScore = aggregatedScore / Math.sqrt(relationships.length); } return Math.min(1.0, Math.max(0.0, aggregatedScore)); } } //# sourceMappingURL=relationship-operators.js.map