Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
nornicdb-vs-neo4j-fastrp.bench.ts10.8 kB
/** * NornicDB - FastRP (Fast Random Projection) Benchmark Suite * * Tests node embedding capabilities using GDS-compatible procedures. * FastRP creates vector embeddings for nodes based on graph structure and properties. * * Run with: npm run bench:fastrp */ import { describe, bench, beforeAll, afterAll } from 'vitest'; import neo4j, { Driver, Session } from 'neo4j-driver'; // ============================================================================ // CONFIGURATION // ============================================================================ const NORNICDB_URI = process.env.NORNICDB_URI || 'bolt://localhost:7687'; const NORNICDB_USER = process.env.NORNICDB_USER || 'admin'; const NORNICDB_PASSWORD = process.env.NORNICDB_PASSWORD || 'admin'; // ============================================================================ // DATABASE CONNECTION // ============================================================================ let nornicdbDriver: Driver; let nornicdbSession: Session; let nornicdbSupportsGDS = false; // ============================================================================ // DATASET LOADER (Social Network for FastRP) // ============================================================================ async function loadSocialNetworkDataset(session: Session): Promise<void> { // Clear existing data await session.run('MATCH (n) DETACH DELETE n'); // Create a social network with age properties (for feature-based embeddings) await session.run(` CREATE (dan:Person {name: 'Dan', age: 18}), (annie:Person {name: 'Annie', age: 12}), (matt:Person {name: 'Matt', age: 22}), (jeff:Person {name: 'Jeff', age: 51}), (brie:Person {name: 'Brie', age: 45}), (elsa:Person {name: 'Elsa', age: 65}), (john:Person {name: 'John', age: 64}), (alice:Person {name: 'Alice', age: 28}), (bob:Person {name: 'Bob', age: 35}), (carol:Person {name: 'Carol', age: 42}), (david:Person {name: 'David', age: 33}), (eve:Person {name: 'Eve', age: 29}), (frank:Person {name: 'Frank', age: 55}), (grace:Person {name: 'Grace', age: 47}), (henry:Person {name: 'Henry', age: 38}), (iris:Person {name: 'Iris', age: 31}), (jack:Person {name: 'Jack', age: 26}), (kate:Person {name: 'Kate', age: 40}), (leo:Person {name: 'Leo', age: 52}), (mary:Person {name: 'Mary', age: 36}), (dan)-[:KNOWS {weight: 1.0}]->(annie), (dan)-[:KNOWS {weight: 1.0}]->(matt), (annie)-[:KNOWS {weight: 1.0}]->(matt), (annie)-[:KNOWS {weight: 1.0}]->(jeff), (annie)-[:KNOWS {weight: 1.0}]->(brie), (matt)-[:KNOWS {weight: 3.5}]->(brie), (brie)-[:KNOWS {weight: 1.0}]->(elsa), (brie)-[:KNOWS {weight: 2.0}]->(jeff), (john)-[:KNOWS {weight: 1.0}]->(jeff), (alice)-[:KNOWS {weight: 2.0}]->(bob), (alice)-[:KNOWS {weight: 1.5}]->(carol), (bob)-[:KNOWS {weight: 1.0}]->(david), (carol)-[:KNOWS {weight: 2.5}]->(eve), (david)-[:KNOWS {weight: 1.0}]->(frank), (eve)-[:KNOWS {weight: 1.0}]->(grace), (frank)-[:KNOWS {weight: 3.0}]->(henry), (grace)-[:KNOWS {weight: 1.0}]->(iris), (henry)-[:KNOWS {weight: 2.0}]->(jack), (iris)-[:KNOWS {weight: 1.0}]->(kate), (jack)-[:KNOWS {weight: 1.5}]->(leo), (kate)-[:KNOWS {weight: 1.0}]->(mary), (leo)-[:KNOWS {weight: 2.0}]->(mary), (alice)-[:KNOWS {weight: 1.0}]->(dan), (bob)-[:KNOWS {weight: 1.0}]->(matt), (carol)-[:KNOWS {weight: 1.0}]->(brie), (david)-[:KNOWS {weight: 1.0}]->(jeff), (eve)-[:KNOWS {weight: 1.0}]->(elsa), (frank)-[:KNOWS {weight: 1.0}]->(john) `); } // ============================================================================ // GDS GRAPH PROJECTION (if supported) // ============================================================================ async function createGDSProjection(session: Session, graphName: string): Promise<boolean> { try { // Check if graph already exists const existingGraphs = await session.run(` CALL gds.graph.list() YIELD graphName RETURN graphName `); const graphExists = existingGraphs.records.some(r => r.get('graphName') === graphName); if (graphExists) { await session.run(`CALL gds.graph.drop('${graphName}')`); } // Create undirected graph projection with node properties and relationship weights await session.run(` MATCH (source:Person)-[r:KNOWS]->(target:Person) RETURN gds.graph.project( '${graphName}', source, target, { sourceNodeProperties: source { .age }, targetNodeProperties: target { .age }, relationshipProperties: r { .weight } }, { undirectedRelationshipTypes: ['*'] } ) `); return true; } catch (error) { console.log(` ⚠️ GDS graph projection not supported: ${error}`); return false; } } // ============================================================================ // SETUP AND TEARDOWN // ============================================================================ beforeAll(async () => { console.log('\n╔════════════════════════════════════════════════════════════════════╗'); console.log('║ FastRP Node Embeddings Benchmark Suite ║'); console.log('║ NornicDB GDS Compatibility Test ║'); console.log('╚════════════════════════════════════════════════════════════════════╝\n'); // Connect to NornicDB console.log(`Connecting to NornicDB at ${NORNICDB_URI}...`); try { nornicdbDriver = neo4j.driver(NORNICDB_URI, neo4j.auth.basic(NORNICDB_USER, NORNICDB_PASSWORD)); nornicdbSession = nornicdbDriver.session(); await nornicdbSession.run('RETURN 1'); console.log('✓ Connected to NornicDB'); console.log('Loading social network dataset into NornicDB...'); await loadSocialNetworkDataset(nornicdbSession); const result1 = await nornicdbSession.run('MATCH (n:Person) RETURN count(n) as count'); console.log(` → ${result1.records[0].get('count')} people created in NornicDB`); // Check GDS support console.log('Checking Graph Data Science (GDS) support in NornicDB...'); try { await nornicdbSession.run('CALL gds.version() YIELD version RETURN version'); nornicdbSupportsGDS = true; console.log(' ✓ NornicDB supports GDS procedures'); // Create GDS projection console.log('Creating GDS graph projection in NornicDB...'); await createGDSProjection(nornicdbSession, 'persons-nornicdb'); } catch (error) { console.log(' ⚠️ NornicDB does not support GDS procedures'); nornicdbSupportsGDS = false; } } catch (error) { console.error(`✗ Failed to connect to NornicDB: ${error}`); } console.log('\n' + '─'.repeat(72) + '\n'); }); afterAll(async () => { console.log('\n' + '─'.repeat(72)); console.log('Cleaning up...'); // Drop GDS projections if (nornicdbSession && nornicdbSupportsGDS) { try { await nornicdbSession.run("CALL gds.graph.drop('persons-nornicdb')"); } catch (e) {} } // Clear data if (nornicdbSession) { await nornicdbSession.run('MATCH (n) DETACH DELETE n').catch(() => {}); await nornicdbSession.close(); } if (nornicdbDriver) await nornicdbDriver.close(); console.log('✓ Cleanup complete\n'); }); // ============================================================================ // NORNICDB BENCHMARKS // ============================================================================ describe('NornicDB - FastRP Embeddings', () => { if (nornicdbSupportsGDS) { // Basic FastRP - Stream mode bench('FastRP stream (dim=8)', async () => { await nornicdbSession.run(` CALL gds.fastRP.stream('persons-nornicdb', { embeddingDimension: 8, randomSeed: 42 }) YIELD nodeId, embedding RETURN nodeId, embedding `); }); bench('FastRP stream (dim=128)', async () => { await nornicdbSession.run(` CALL gds.fastRP.stream('persons-nornicdb', { embeddingDimension: 128, randomSeed: 42 }) YIELD nodeId, embedding RETURN nodeId, embedding `); }); bench('FastRP stream with weights (dim=64)', async () => { await nornicdbSession.run(` CALL gds.fastRP.stream('persons-nornicdb', { embeddingDimension: 64, randomSeed: 42, relationshipWeightProperty: 'weight' }) YIELD nodeId, embedding RETURN nodeId, embedding `); }); bench('FastRP with node features (age)', async () => { await nornicdbSession.run(` CALL gds.fastRP.stream('persons-nornicdb', { embeddingDimension: 32, randomSeed: 42, propertyRatio: 0.5, featureProperties: ['age'] }) YIELD nodeId, embedding RETURN nodeId, embedding `); }); bench('FastRP stats (performance check)', async () => { await nornicdbSession.run(` CALL gds.fastRP.stats('persons-nornicdb', { embeddingDimension: 64, randomSeed: 42 }) YIELD nodeCount RETURN nodeCount `); }); } else { // Fallback: Manual embedding-like queries (no GDS) bench('Manual: Aggregate neighbor ages', async () => { await nornicdbSession.run(` MATCH (p:Person) OPTIONAL MATCH (p)-[:KNOWS]-(neighbor:Person) RETURN p.name, avg(neighbor.age) as avg_neighbor_age, count(neighbor) as neighbor_count `); }); bench('Manual: 2-hop neighborhood features', async () => { await nornicdbSession.run(` MATCH (p:Person) OPTIONAL MATCH (p)-[:KNOWS*1..2]-(neighbor:Person) WHERE p <> neighbor RETURN p.name, avg(neighbor.age) as avg_neighbor_age, count(DISTINCT neighbor) as reach `); }); bench('Manual: Weighted neighbor aggregation', async () => { await nornicdbSession.run(` MATCH (p:Person)-[r:KNOWS]-(neighbor:Person) RETURN p.name, sum(r.weight * neighbor.age) / sum(r.weight) as weighted_avg_age, sum(r.weight) as total_weight `); }); } });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server