UNPKG

zrald

Version:

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

204 lines 7.62 kB
import { VectorStore } from '../core/vector-store.js'; export class BaseOperator { graphDb; vectorStore; name; type; constructor(name, type, graphDb, vectorStore) { this.name = name; this.type = type; this.graphDb = graphDb; this.vectorStore = vectorStore; } async validateConfig(config, schema) { try { return schema.parse(config); } catch (error) { throw new Error(`Invalid configuration for ${this.name}: ${error}`); } } createResult(nodes = [], relationships = [], chunks = [], scores = {}, metadata = {}) { return { nodes, relationships, chunks, scores, metadata: { ...metadata, operator: this.name, type: this.type, timestamp: new Date().toISOString(), execution_time_ms: 0 // Will be set by executor } }; } async measureExecutionTime(operation) { const startTime = Date.now(); const result = await operation(); const executionTime = Date.now() - startTime; return { result, executionTime }; } normalizeScores(scores) { const values = Object.values(scores); if (values.length === 0) return scores; const max = Math.max(...values); const min = Math.min(...values); const range = max - min; if (range === 0) { // All scores are the same return Object.fromEntries(Object.entries(scores).map(([key]) => [key, 1.0])); } return Object.fromEntries(Object.entries(scores).map(([key, value]) => [ key, (value - min) / range ])); } combineScores(scores1, scores2, weight1 = 0.5, weight2 = 0.5) { const combined = {}; const allKeys = new Set([...Object.keys(scores1), ...Object.keys(scores2)]); for (const key of allKeys) { const score1 = scores1[key] || 0; const score2 = scores2[key] || 0; combined[key] = (score1 * weight1) + (score2 * weight2); } return combined; } async getConnectedNodes(nodeIds, direction = 'both') { const connectedNodes = []; const seenIds = new Set(); for (const nodeId of nodeIds) { const relationships = await this.graphDb.getRelationships(nodeId, direction); for (const rel of relationships) { const targetId = rel.source_id === nodeId ? rel.target_id : rel.source_id; if (!seenIds.has(targetId)) { const node = await this.graphDb.getNode(targetId); if (node) { connectedNodes.push(node); seenIds.add(targetId); } } } } return connectedNodes; } async filterNodesByType(nodes, types) { return nodes.filter(node => types.includes(node.type)); } async enrichWithEmbeddings(nodes) { const enrichedNodes = []; for (const node of nodes) { if (!node.embedding) { // Try to get embedding from vector store const storedNode = await this.vectorStore.getNodeById(node.id); if (storedNode?.embedding) { enrichedNodes.push({ ...node, embedding: storedNode.embedding }); } else { enrichedNodes.push(node); } } else { enrichedNodes.push(node); } } return enrichedNodes; } calculateRelevanceScore(node, queryEmbedding, contextNodes) { let score = 0.5; // Base score // Embedding similarity if (queryEmbedding && node.embedding) { const similarity = VectorStore.cosineSimilarity(queryEmbedding, node.embedding); score += similarity * 0.4; } // Context relevance if (contextNodes && contextNodes.length > 0) { const contextScore = contextNodes.reduce((sum, contextNode) => { if (contextNode.embedding && node.embedding) { return sum + VectorStore.cosineSimilarity(contextNode.embedding, node.embedding); } return sum; }, 0) / contextNodes.length; score += contextScore * 0.3; } // Node properties boost if (node.properties.importance) { score += node.properties.importance * 0.2; } if (node.properties.frequency) { score += Math.log(node.properties.frequency + 1) * 0.1; } return Math.min(1.0, Math.max(0.0, score)); } async logExecution(config, result, executionTime) { const logEntry = { operator: this.name, type: this.type, timestamp: new Date().toISOString(), execution_time_ms: executionTime, config_hash: this.hashConfig(config), result_summary: { nodes_count: result.nodes.length, relationships_count: result.relationships.length, chunks_count: result.chunks?.length || 0, avg_score: this.calculateAverageScore(result.scores || {}) } }; // In a production system, this would go to a proper logging system console.log('Operator execution:', JSON.stringify(logEntry, null, 2)); } hashConfig(config) { // Simple hash function for configuration return Buffer.from(JSON.stringify(config)).toString('base64').slice(0, 16); } calculateAverageScore(scores) { const values = Object.values(scores); return values.length > 0 ? values.reduce((sum, score) => sum + score, 0) / values.length : 0; } // Utility methods for subclasses async batchProcess(items, processor, batchSize = 10) { const results = []; for (let i = 0; i < items.length; i += batchSize) { const batch = items.slice(i, i + batchSize); const batchResults = await Promise.all(batch.map(processor)); results.push(...batchResults); } return results; } async retryOperation(operation, maxRetries = 3, delay = 1000) { let lastError; for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error; if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt))); } } } throw lastError; } // Abstract methods that can be overridden by specific operators async preProcess(config) { return config; } async postProcess(result) { return result; } // Template method pattern async run(config) { const processedConfig = await this.preProcess(config); const { result, executionTime } = await this.measureExecutionTime(() => this.execute(processedConfig)); result.metadata = { ...result.metadata, execution_time_ms: executionTime }; const finalResult = await this.postProcess(result); await this.logExecution(processedConfig, finalResult, executionTime); return finalResult; } } //# sourceMappingURL=base-operator.js.map