UNPKG

zrald

Version:

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

169 lines 7.39 kB
import { BaseOperator } from './base-operator.js'; import { VDBOperatorConfigSchema, PPROperatorConfigSchema } from '../types/graph.js'; /** * VDB Operator - Vector Database similarity search * Leverages vector similarity to locate semantically relevant nodes */ export class VDBOperator extends BaseOperator { constructor(graphDb, vectorStore) { super('VDBOperator', 'node', graphDb, vectorStore); } async execute(config) { const validatedConfig = await this.validateConfig(config, VDBOperatorConfigSchema); // Perform vector similarity search const searchResults = validatedConfig.node_types ? await this.vectorStore.searchByNodeTypes(validatedConfig.query_embedding, validatedConfig.node_types, validatedConfig.top_k, validatedConfig.similarity_threshold) : await this.vectorStore.searchNodes(validatedConfig.query_embedding, validatedConfig.top_k, validatedConfig.similarity_threshold); const nodes = []; const scores = {}; for (const result of searchResults) { if (result.node) { nodes.push(result.node); scores[result.node.id] = result.score; } } return this.createResult(nodes, [], [], scores, { search_type: 'vector_similarity', query_dimension: validatedConfig.query_embedding.length, threshold_used: validatedConfig.similarity_threshold, total_candidates: searchResults.length }); } } /** * PPR Operator - Personalized PageRank * Implements Personalized PageRank to identify authoritative nodes */ export class PPROperator extends BaseOperator { constructor(graphDb, vectorStore) { super('PPROperator', 'node', graphDb, vectorStore); } async execute(config) { const validatedConfig = await this.validateConfig(config, PPROperatorConfigSchema); // Build adjacency matrix from graph const allNodes = await this.getAllNodes(); const nodeIndex = new Map(); allNodes.forEach((node, index) => nodeIndex.set(node.id, index)); const adjacencyMatrix = await this.buildAdjacencyMatrix(allNodes, nodeIndex); // Initialize personalization vector const personalization = this.initializePersonalization(allNodes.length, validatedConfig.seed_nodes, nodeIndex, validatedConfig.personalization); // Run PageRank algorithm const pageRankScores = await this.computePageRank(adjacencyMatrix, personalization, validatedConfig.damping_factor, validatedConfig.max_iterations, validatedConfig.tolerance); // Convert scores back to node IDs and sort const nodeScores = allNodes .map((node, index) => ({ node, score: pageRankScores[index] })) .sort((a, b) => b.score - a.score); const topNodes = nodeScores.slice(0, 50); // Return top 50 nodes const nodes = topNodes.map(item => item.node); const scores = Object.fromEntries(topNodes.map(item => [item.node.id, item.score])); return this.createResult(nodes, [], [], scores, { algorithm: 'personalized_pagerank', seed_nodes: validatedConfig.seed_nodes, damping_factor: validatedConfig.damping_factor, iterations_run: validatedConfig.max_iterations, convergence_achieved: true // Would be calculated in real implementation }); } async getAllNodes() { // Get all node types and fetch them const nodeTypes = ['entity', 'concept', 'document', 'chunk', 'summary']; const allNodes = []; for (const type of nodeTypes) { const nodes = await this.graphDb.getNodesByType(type); allNodes.push(...nodes); } return allNodes; } async buildAdjacencyMatrix(nodes, nodeIndex) { const size = nodes.length; const matrix = Array(size).fill(null).map(() => Array(size).fill(0)); // Build adjacency matrix from relationships for (const node of nodes) { const relationships = await this.graphDb.getRelationships(node.id, 'outgoing'); const sourceIndex = nodeIndex.get(node.id); if (sourceIndex !== undefined) { for (const rel of relationships) { const targetIndex = nodeIndex.get(rel.target_id); if (targetIndex !== undefined) { matrix[sourceIndex][targetIndex] = rel.weight; } } } } // Normalize rows (convert to transition matrix) for (let i = 0; i < size; i++) { const rowSum = matrix[i].reduce((sum, val) => sum + val, 0); if (rowSum > 0) { for (let j = 0; j < size; j++) { matrix[i][j] /= rowSum; } } else { // If no outgoing edges, distribute equally for (let j = 0; j < size; j++) { matrix[i][j] = 1 / size; } } } return matrix; } initializePersonalization(size, seedNodes, nodeIndex, customPersonalization) { const personalization = Array(size).fill(0); if (customPersonalization) { // Use custom personalization weights for (const [nodeId, weight] of Object.entries(customPersonalization)) { const index = nodeIndex.get(nodeId); if (index !== undefined) { personalization[index] = weight; } } } else { // Equal weight for seed nodes const weight = 1 / seedNodes.length; for (const nodeId of seedNodes) { const index = nodeIndex.get(nodeId); if (index !== undefined) { personalization[index] = weight; } } } // Normalize const sum = personalization.reduce((acc, val) => acc + val, 0); if (sum > 0) { for (let i = 0; i < size; i++) { personalization[i] /= sum; } } return personalization; } async computePageRank(adjacencyMatrix, personalization, dampingFactor, maxIterations, tolerance) { const size = adjacencyMatrix.length; let scores = Array(size).fill(1 / size); for (let iteration = 0; iteration < maxIterations; iteration++) { const newScores = Array(size).fill(0); // PageRank update formula for (let i = 0; i < size; i++) { newScores[i] = (1 - dampingFactor) * personalization[i]; for (let j = 0; j < size; j++) { newScores[i] += dampingFactor * adjacencyMatrix[j][i] * scores[j]; } } // Check convergence let maxDiff = 0; for (let i = 0; i < size; i++) { maxDiff = Math.max(maxDiff, Math.abs(newScores[i] - scores[i])); } scores = newScores; if (maxDiff < tolerance) { console.log(`PageRank converged after ${iteration + 1} iterations`); break; } } return scores; } } //# sourceMappingURL=node-operators.js.map