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