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