UNPKG

@vfarcic/dot-ai

Version:

Universal Kubernetes application deployment agent with CLI and MCP interfaces

300 lines (299 loc) 10.6 kB
"use strict"; /** * Vector DB Service * * Handles Qdrant Vector Database integration for semantic search and storage */ Object.defineProperty(exports, "__esModule", { value: true }); exports.VectorDBService = void 0; const js_client_rest_1 = require("@qdrant/js-client-rest"); class VectorDBService { client = null; config; collectionName; constructor(config = {}) { this.config = { url: config.url !== undefined ? config.url : (process.env.QDRANT_URL || 'http://localhost:6333'), apiKey: config.apiKey || process.env.QDRANT_API_KEY, collectionName: config.collectionName || 'patterns' }; this.collectionName = this.config.collectionName; this.validateConfig(); if (this.shouldInitializeClient()) { this.client = new js_client_rest_1.QdrantClient({ url: this.config.url, apiKey: this.config.apiKey }); } } validateConfig() { // Allow test-friendly initialization if (this.config.url === 'test-url' || this.config.url === 'mock-url') { return; // Allow test configurations } if (!this.config.url || this.config.url.trim() === '') { throw new Error('Qdrant URL is required for Vector DB integration'); } } shouldInitializeClient() { // Don't initialize for test configurations const testUrls = ['test-url', 'mock-url']; return !testUrls.includes(this.config.url || ''); } /** * Initialize the collection if it doesn't exist */ async initializeCollection(vectorSize = 384) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { // Check if collection exists const collections = await this.client.getCollections(); const collectionExists = collections.collections.some(col => col.name === this.collectionName); if (collectionExists) { // Verify existing collection has correct vector dimensions try { const collectionInfo = await this.client.getCollection(this.collectionName); const existingVectorSize = collectionInfo.config?.params?.vectors?.size; if (existingVectorSize && existingVectorSize !== vectorSize) { // Dimension mismatch - recreate collection console.warn(`Vector dimension mismatch: existing collection has ${existingVectorSize} dimensions, but ${vectorSize} expected. Recreating collection.`); await this.client.deleteCollection(this.collectionName); await this.createCollection(vectorSize); } } catch (error) { // If we can't get collection info, assume it's corrupted and recreate console.warn(`Failed to get collection info, recreating collection: ${error}`); await this.client.deleteCollection(this.collectionName); await this.createCollection(vectorSize); } } else { // Create new collection await this.createCollection(vectorSize); } } catch (error) { throw new Error(`Failed to initialize collection: ${error}`); } } /** * Create collection with specified vector size */ async createCollection(vectorSize) { if (!this.client) { throw new Error('Vector DB client not initialized'); } await this.client.createCollection(this.collectionName, { vectors: { size: vectorSize, distance: 'Cosine', on_disk: true // Enable on-disk storage for better performance with large collections }, // Enable payload indexing for better keyword search performance optimizers_config: { default_segment_number: 2 } }); } /** * Store a document with optional vector */ async upsertDocument(document) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { const point = { id: document.id, payload: document.payload }; // For documents without vectors, use a zero vector as placeholder // This allows us to store documents in collections configured for vectors if (document.vector && document.vector.length > 0) { point.vector = document.vector; } else { // Create a zero vector with the expected dimensions (384) point.vector = new Array(384).fill(0); } await this.client.upsert(this.collectionName, { wait: true, points: [point] }); } catch (error) { throw new Error(`Failed to upsert document: ${error}`); } } /** * Search for similar documents using vector similarity */ async searchSimilar(vector, options = {}) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { const searchResult = await this.client.search(this.collectionName, { vector, limit: options.limit || 10, score_threshold: options.scoreThreshold || 0.5, with_payload: true }); return searchResult.map(result => ({ id: result.id.toString(), score: result.score, payload: result.payload || {} })); } catch (error) { throw new Error(`Failed to search documents: ${error}`); } } /** * Search for documents using payload filtering (keyword search) */ async searchByKeywords(keywords, options = {}) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { // Fallback to JavaScript-based filtering due to Qdrant filter syntax issues // Get all documents and filter in JavaScript for keyword matching const scrollResult = await this.client.scroll(this.collectionName, { limit: 1000, // Get all documents for filtering with_payload: true, with_vector: false }); // Filter documents by checking if any keyword matches any trigger const matchedPoints = scrollResult.points.filter(point => { if (!point.payload || !point.payload.triggers || !Array.isArray(point.payload.triggers)) { return false; } const triggers = point.payload.triggers.map((t) => t.toLowerCase()); return keywords.some(keyword => triggers.some(trigger => trigger.includes(keyword.toLowerCase()) || keyword.toLowerCase().includes(trigger))); }); // Apply limit after filtering const limitedResults = matchedPoints.slice(0, options.limit || 10); return limitedResults.map(point => ({ id: point.id.toString(), score: 1.0, // Keyword matches get full score payload: point.payload || {} })); } catch (error) { throw new Error(`Failed to search by keywords: ${error}`); } } /** * Get a document by ID */ async getDocument(id) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { const result = await this.client.retrieve(this.collectionName, { ids: [id], with_payload: true, with_vector: true }); if (result.length === 0) { return null; } const point = result[0]; return { id: point.id.toString(), payload: point.payload || {}, vector: point.vector || undefined }; } catch (error) { throw new Error(`Failed to get document: ${error}`); } } /** * Delete a document by ID */ async deleteDocument(id) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { await this.client.delete(this.collectionName, { wait: true, points: [id] }); } catch (error) { throw new Error(`Failed to delete document: ${error}`); } } /** * Get all documents (for listing) */ async getAllDocuments(limit = 100) { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { const scrollResult = await this.client.scroll(this.collectionName, { limit, with_payload: true, with_vector: false }); return scrollResult.points.map(point => ({ id: point.id.toString(), payload: point.payload || {} })); } catch (error) { throw new Error(`Failed to get all documents: ${error}`); } } /** * Get collection info and statistics */ async getCollectionInfo() { if (!this.client) { throw new Error('Vector DB client not initialized'); } try { return await this.client.getCollection(this.collectionName); } catch (error) { throw new Error(`Failed to get collection info: ${error}`); } } /** * Check if Vector DB is available and responsive */ async healthCheck() { if (!this.client) { return false; } try { await this.client.getCollections(); return true; } catch (error) { return false; } } /** * Check if client is initialized */ isInitialized() { return this.client !== null; } /** * Get configuration (for debugging) */ getConfig() { return { ...this.config }; } } exports.VectorDBService = VectorDBService;