UNPKG

@vfarcic/dot-ai

Version:

Universal Kubernetes application deployment agent with CLI and MCP interfaces

232 lines (231 loc) 8.44 kB
"use strict"; /** * Base Vector Service * * Generic vector operations that can be extended for different data types * (patterns, capabilities, dependencies, etc.) */ Object.defineProperty(exports, "__esModule", { value: true }); exports.BaseVectorService = void 0; const vector_db_service_1 = require("./vector-db-service"); const embedding_service_1 = require("./embedding-service"); /** * Abstract base class for vector-based data services */ class BaseVectorService { vectorDB; embeddingService; collectionName; constructor(collectionName, vectorDB, embeddingService) { this.collectionName = collectionName; this.vectorDB = vectorDB || new vector_db_service_1.VectorDBService({ collectionName }); this.embeddingService = embeddingService || new embedding_service_1.EmbeddingService(); } /** * Initialize the collection */ async initialize() { // Use embedding dimensions if available, otherwise default to 1536 (OpenAI default) const dimensions = this.embeddingService.isAvailable() ? this.embeddingService.getDimensions() : 1536; await this.vectorDB.initializeCollection(dimensions); } /** * Health check for Vector DB connection */ async healthCheck() { return await this.vectorDB.healthCheck(); } /** * Store data in Vector DB with optional semantic embedding */ async storeData(data) { const searchText = this.createSearchText(data); const id = this.extractId(data); // Try to generate embedding if service is available let embedding = null; if (this.embeddingService.isAvailable()) { try { embedding = await this.embeddingService.generateEmbedding(searchText); } catch (error) { // Log but don't fail - fall back to keyword-only storage console.warn(`Failed to generate embedding for ${this.collectionName}, using keyword-only storage:`, error); } } const document = { id, payload: { ...this.createPayload(data), searchText: searchText, hasEmbedding: embedding !== null }, vector: embedding || undefined }; await this.vectorDB.upsertDocument(document); } /** * Search for data using hybrid semantic + keyword matching */ async searchData(query, options = {}) { // Extract keywords for keyword search const queryKeywords = this.extractKeywords(query); if (queryKeywords.length === 0) { return []; } const limit = options.limit || 10; const scoreThreshold = options.scoreThreshold || 0.1; // Try semantic search first if embeddings available if (this.embeddingService.isAvailable()) { try { return await this.hybridSearch(query, queryKeywords, { limit, scoreThreshold }); } catch (error) { // Fall back to keyword-only search if semantic search fails console.warn('Semantic search failed, falling back to keyword search:', error); } } // Keyword-only search (fallback or when embeddings not available) return await this.keywordOnlySearch(queryKeywords, { limit, scoreThreshold }); } /** * Get data by ID */ async getData(id) { const document = await this.vectorDB.getDocument(id); if (!document) { return null; } const data = this.payloadToData(document.payload); // Set the ID from the document data.id = document.id; return data; } /** * Delete data by ID */ async deleteData(id) { await this.vectorDB.deleteDocument(id); } /** * Get all data (limited) */ async getAllData(limit) { const documents = await this.vectorDB.getAllDocuments(limit); return documents.map(doc => { const data = this.payloadToData(doc.payload); data.id = doc.id; return data; }); } /** * Get total count of data items */ async getDataCount() { try { const info = await this.vectorDB.getCollectionInfo(); return info.points_count || 0; } catch (error) { // Fallback: get all and count const data = await this.getAllData(); return data.length; } } /** * Get current search mode (semantic vs keyword-only) */ getSearchMode() { const status = this.embeddingService.getStatus(); return { semantic: status.available, provider: status.provider || undefined, reason: status.reason || (status.available ? 'Embedding service available' : undefined) }; } // Virtual methods that can be overridden by subclasses extractKeywords(query) { return query.toLowerCase().split(/\s+/).filter(word => word.length > 2); } /** * Hybrid search combining semantic and keyword matching */ async hybridSearch(query, queryKeywords, options) { // Generate query embedding const queryEmbedding = await this.embeddingService.generateEmbedding(query); if (!queryEmbedding) { // Fall back to keyword search return await this.keywordOnlySearch(queryKeywords, options); } // Semantic search using vector similarity const semanticResults = await this.vectorDB.searchSimilar(queryEmbedding, { limit: options.limit * 2, // Get more candidates for hybrid ranking scoreThreshold: 0.5 // Lower threshold for semantic similarity }); // Keyword search const keywordResults = await this.vectorDB.searchByKeywords(queryKeywords, { limit: options.limit * 2, scoreThreshold: 0.1 }); // Combine and rank results return this.combineHybridResults(semanticResults, keywordResults, queryKeywords, options); } /** * Keyword-only search (fallback when embeddings not available) */ async keywordOnlySearch(queryKeywords, options) { const keywordResults = await this.vectorDB.searchByKeywords(queryKeywords, options); return keywordResults .map(result => { const data = this.payloadToData(result.payload); data.id = result.id; return { data, score: result.score, matchType: 'keyword' }; }) .filter(result => result.score >= options.scoreThreshold); // Apply score filtering } /** * Combine semantic and keyword results with hybrid ranking */ combineHybridResults(semanticResults, keywordResults, queryKeywords, options) { const combinedResults = new Map(); // Add semantic results for (const result of semanticResults) { const data = this.payloadToData(result.payload); data.id = result.id; combinedResults.set(result.id, { data, score: result.score * 0.7, // Weight semantic similarity matchType: 'semantic' }); } // Add or boost keyword results for (const result of keywordResults) { const existing = combinedResults.get(result.id); if (existing) { // Boost score for hybrid match existing.score = Math.max(existing.score, result.score * 0.8); existing.matchType = 'hybrid'; } else { const data = this.payloadToData(result.payload); data.id = result.id; combinedResults.set(result.id, { data, score: result.score * 0.6, // Weight keyword matching matchType: 'keyword' }); } } // Sort by score and apply limits return Array.from(combinedResults.values()) .filter(result => result.score >= options.scoreThreshold) .sort((a, b) => b.score - a.score) .slice(0, options.limit); } } exports.BaseVectorService = BaseVectorService;