Skip to main content
Glama
vector-search-channel.ts5.65 kB
/** * Vector Search Channel - Semantic Similarity Engine * Single responsibility: Vector similarity operations using Neo4j GDS * * CORRECTED IMPLEMENTATION: GDS is REQUIRED, not optional * ZERO-FALLBACK POLICY: If GDS fails, search fails. Period. * * Based on system documentation: * - DozerDB setup includes GDS plugin installation * - GDS functions are documented as standard features * - Verification command provided: RETURN gds.similarity.cosine([1,2,3], [2,3,4]) */ import { Session } from 'neo4j-driver'; import neo4j from 'neo4j-driver'; import { calculateEmbedding, Vector } from '../../utilities'; import { MCPServiceError, MCPErrorCodes } from '../../errors'; export interface VectorCandidate { id: string; score: number; } export class VectorSearchChannel { private gdsVerified: boolean | null = null; constructor(private session: Session) {} /** * Execute vector similarity search using Neo4j GDS * ZERO-FALLBACK: Either GDS works or we fail fast with setup instructions */ async search( query: string, limit: number, threshold: number, memoryTypes?: string[] ): Promise<VectorCandidate[]> { // Ensure GDS is available before proceeding await this.ensureGDSAvailable(); const queryVector = await calculateEmbedding(query); return this.searchWithGDS(queryVector, limit, threshold, memoryTypes); } /** * Verify GDS is properly configured */ private async ensureGDSAvailable(): Promise<void> { if (this.gdsVerified === true) { return; // Already verified in this session } try { // Use the exact verification command from README.NEO4J.md const result = await this.session.run( 'RETURN gds.similarity.cosine([1,2,3], [2,3,4]) AS similarity' ); const similarity = result.records[0]?.get('similarity'); if (typeof similarity !== 'number') { throw new MCPServiceError( 'GDS function returned invalid result', MCPErrorCodes.SERVICE_MISCONFIGURED, { service: 'neo4j-gds', expectedType: 'number', actualType: typeof similarity } ); } this.gdsVerified = true; // Silent verification - no operational noise } catch (error) { this.gdsVerified = false; const errorMessage = error instanceof Error ? error.message : String(error); // ZERO-FALLBACK: Provide clear setup instructions instead of silently degrading throw new MCPServiceError( 'Neo4j Graph Data Science (GDS) plugin is required but not installed', MCPErrorCodes.REQUIRED_SERVICE_MISSING, { service: 'neo4j-gds', installUrl: 'https://dozerdb.org/', verifyCommand: 'RETURN gds.similarity.cosine([1,2,3], [2,3,4])', originalError: errorMessage } ); } } /** * Execute vector search using GDS plugin * ASSUMES GDS is available - ensureGDSAvailable() must be called first */ private async searchWithGDS( queryVector: Vector, limit: number, threshold: number, memoryTypes?: string[] ): Promise<VectorCandidate[]> { let whereClause = ''; if (memoryTypes && memoryTypes.length > 0) { whereClause = 'WHERE m.memoryType IN $memoryTypes'; } const cypher = ` MATCH (m:Memory) ${whereClause} // Calculate name embedding score using GDS WITH m, CASE WHEN m.nameEmbedding IS NOT NULL THEN gds.similarity.cosine(m.nameEmbedding, $queryVector) ELSE 0.0 END AS nameScore // Calculate best observation embedding score using GDS OPTIONAL MATCH (m)-[:HAS_OBSERVATION]->(o:Observation) WITH m, nameScore, CASE WHEN o.embedding IS NOT NULL THEN gds.similarity.cosine(o.embedding, $queryVector) ELSE 0.0 END AS obsScore // Get the maximum score between name and all observations WITH m, nameScore, max(obsScore) AS maxObsScore WITH m, CASE WHEN nameScore >= coalesce(maxObsScore, 0.0) THEN nameScore ELSE coalesce(maxObsScore, 0.0) END AS bestScore WHERE bestScore >= $threshold ORDER BY bestScore DESC LIMIT $limit RETURN m.id as id, bestScore as score `; try { const result = await this.session.run(cypher, { queryVector, threshold, limit: neo4j.int(limit), memoryTypes }); return result.records.map(record => ({ id: record.get('id'), score: record.get('score') })); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Check if it's a GDS-specific error if (errorMessage.includes('gds.similarity') || errorMessage.includes('Unknown function')) { throw new MCPServiceError( 'GDS vector search failed. The plugin may have been disabled or removed', MCPErrorCodes.SERVICE_UNAVAILABLE, { service: 'neo4j-gds', originalError: errorMessage, verifyCommand: 'RETURN gds.similarity.cosine([1,2,3], [2,3,4])' } ); } // Re-throw other errors with context throw new MCPServiceError( `Vector search query failed: ${errorMessage}`, MCPErrorCodes.SERVICE_ERROR, { service: 'vector-search', query: 'gds-similarity' } ); } } /** * Get GDS verification status for monitoring */ isGDSVerified(): boolean | null { return this.gdsVerified; } }

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/sylweriusz/mcp-neo4j-memory-server'

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