UNPKG

optivise

Version:

Optivise - The Ultimate Optimizely Development Assistant with AI-powered features, zero-config setup, and comprehensive development support

353 lines 13.4 kB
/** * ChromaDB Integration Service * Manages vector database operations for Optimizely documentation storage */ import { ChromaClient, OpenAIEmbeddingFunction } from 'chromadb'; import { openAIClient } from './openai-client.js'; import { CircuitBreaker } from '../utils/circuit-breaker.js'; export class ChromaDBService { client = null; collections = new Map(); config; breaker = new CircuitBreaker({ failureThreshold: 3, cooldownMs: 20000 }); // Optimizely product collections static COLLECTIONS = { COMMERCE: 'optimizely_commerce_docs', CMS_PAAS: 'optimizely_cms_paas_docs', CMS_SAAS: 'optimizely_cms_saas_docs', EXPERIMENTATION: 'optimizely_experimentation_docs', DXP: 'optimizely_dxp_docs', PLATFORM: 'optimizely_platform_docs' }; constructor(config) { this.config = { host: 'localhost', port: 8000, ssl: false, ...config }; } /** * Initialize ChromaDB client and collections */ async initialize() { try { if (!this.breaker.canAttempt()) { return false; } // Initialize ChromaDB client this.client = new ChromaClient({ path: this.config.ssl ? `https://${this.config.host}:${this.config.port}` : `http://${this.config.host}:${this.config.port}` }); // Test connection const heartbeat = await this.client.heartbeat(); if (!heartbeat) { // Silently return false during initialization return false; } // Initialize collections for each Optimizely product await this.initializeCollections(); // ChromaDB initialized successfully this.breaker.onSuccess(); return true; } catch (error) { this.breaker.onFailure(); // Silently return false during initialization return false; } } /** * Initialize collections for each Optimizely product */ async initializeCollections() { if (!this.client) { throw new Error('ChromaDB client not initialized'); } const embeddingFunction = new OpenAIEmbeddingFunction({ openai_api_key: process.env.OPENAI_API_KEY || '', openai_model: 'text-embedding-ada-002' }); for (const [productKey, collectionName] of Object.entries(ChromaDBService.COLLECTIONS)) { try { let collection; try { // Try to get existing collection collection = await this.client.getCollection({ name: collectionName, embeddingFunction }); } catch (error) { // Collection doesn't exist, create it collection = await this.client.createCollection({ name: collectionName, embeddingFunction, metadata: { product: productKey.toLowerCase(), description: `Optimizely ${productKey} documentation and examples`, created: new Date().toISOString() } }); } this.collections.set(collectionName, collection); console.log(`Collection ${collectionName} ready`); } catch (error) { console.error(`Failed to initialize collection ${collectionName}:`, error); } } } /** * Add document chunks to the appropriate collection */ async addDocuments(documents) { if (!this.client || documents.length === 0) { return false; } if (!this.breaker.canAttempt()) return false; try { // Group documents by product for batch insertion const documentsByProduct = new Map(); for (const doc of documents) { const collectionName = this.getCollectionName(doc.metadata.product); if (!documentsByProduct.has(collectionName)) { documentsByProduct.set(collectionName, []); } documentsByProduct.get(collectionName).push(doc); } // Insert documents into appropriate collections for (const [collectionName, docs] of documentsByProduct) { const collection = this.collections.get(collectionName); if (!collection) { console.warn(`Collection ${collectionName} not found, skipping documents`); continue; } // Generate embeddings using our OpenAI client const embeddings = []; for (const doc of docs) { const embedding = await openAIClient.generateEmbedding({ text: doc.content }); if (embedding) { embeddings.push(embedding.embedding); } else { console.warn(`Failed to generate embedding for document ${doc.id}`); embeddings.push(new Array(1536).fill(0)); // Default embedding size for ada-002 } } // Add to collection (convert tags array to string for ChromaDB compatibility) const metadatas = docs.map(doc => ({ ...doc.metadata, tags: doc.metadata.tags?.join(',') || '' })); await collection.add({ ids: docs.map(doc => doc.id), documents: docs.map(doc => doc.content), metadatas: metadatas, embeddings: embeddings.length === docs.length ? embeddings : undefined }); console.log(`Added ${docs.length} documents to ${collectionName}`); } this.breaker.onSuccess(); return true; } catch (error) { console.error('Failed to add documents to ChromaDB:', error); this.breaker.onFailure(); return false; } } /** * Search for similar documents across all collections or specific product */ async searchDocuments(query, options = {}) { if (!this.client) { return []; } if (!this.breaker.canAttempt()) return []; try { const { product, limit = 10, threshold = 0.7, contentTypes } = options; const results = []; // Determine which collections to search const collectionsToSearch = product ? [this.getCollectionName(product)] : Array.from(this.collections.keys()); // Search each collection for (const collectionName of collectionsToSearch) { const collection = this.collections.get(collectionName); if (!collection) continue; try { const searchResults = await collection.query({ queryTexts: [query], nResults: Math.min(limit, 50), // ChromaDB limit per query where: contentTypes ? { contentType: { $in: contentTypes } } : undefined }); // Process results if (searchResults.ids?.[0] && searchResults.distances?.[0]) { const ids = searchResults.ids[0]; const distances = searchResults.distances[0]; for (let i = 0; i < ids.length; i++) { const distance = distances[i]; const similarity = distance !== undefined ? 1 - distance // Convert distance to similarity : 0; if (similarity >= threshold) { const rawMetadata = searchResults.metadatas?.[0]?.[i]; // Convert tags string back to array const metadata = rawMetadata ? { ...rawMetadata, tags: rawMetadata.tags ? rawMetadata.tags.split(',') : [] } : {}; results.push({ id: ids[i] || `unknown_${i}`, content: searchResults.documents?.[0]?.[i] || '', metadata, similarity }); } } } } catch (error) { console.error(`Search failed for collection ${collectionName}:`, error); } } // Sort by similarity and limit results this.breaker.onSuccess(); return results .sort((a, b) => b.similarity - a.similarity) .slice(0, limit); } catch (error) { console.error('Search operation failed:', error); this.breaker.onFailure(); return []; } } /** * Get collection statistics */ async getCollectionStats() { if (!this.client) { return {}; } const stats = {}; for (const [collectionName, collection] of this.collections) { try { const count = await collection.count(); stats[collectionName] = { documentCount: count, status: 'active' }; } catch (error) { stats[collectionName] = { documentCount: 0, status: 'error', error: error instanceof Error ? error.message : 'Unknown error' }; } } return stats; } /** * Delete documents by IDs */ async deleteDocuments(ids, product) { if (!this.client || ids.length === 0) { return false; } try { const collectionsToUpdate = product ? [this.getCollectionName(product)] : Array.from(this.collections.keys()); for (const collectionName of collectionsToUpdate) { const collection = this.collections.get(collectionName); if (!collection) continue; try { await collection.delete({ ids }); console.log(`Deleted ${ids.length} documents from ${collectionName}`); } catch (error) { console.error(`Failed to delete from ${collectionName}:`, error); } } return true; } catch (error) { console.error('Failed to delete documents:', error); return false; } } /** * Clear all documents from a collection */ async clearCollection(product) { const collectionName = this.getCollectionName(product); const collection = this.collections.get(collectionName); if (!collection) { return false; } try { // Get all document IDs and delete them const allDocs = await collection.get(); if (allDocs.ids && allDocs.ids.length > 0) { await collection.delete({ ids: allDocs.ids }); } console.log(`Cleared collection ${collectionName}`); return true; } catch (error) { console.error(`Failed to clear collection ${collectionName}:`, error); return false; } } /** * Get collection name for a product */ getCollectionName(product) { const productKey = product.toUpperCase().replace(/[^A-Z]/g, '_'); return Object.values(ChromaDBService.COLLECTIONS).find(name => name.includes(productKey.toLowerCase())) || ChromaDBService.COLLECTIONS.PLATFORM; } /** * Check if ChromaDB is available */ isAvailable() { return this.client !== null && this.collections.size > 0; } getCircuitState() { return this.breaker.state(); } /** * Test ChromaDB connection */ async testConnection() { if (!this.client) { return false; } try { const heartbeat = await this.client.heartbeat(); return !!heartbeat; } catch (error) { console.error('ChromaDB connection test failed:', error); return false; } } /** * Cleanup resources */ async cleanup() { this.collections.clear(); this.client = null; } } // Singleton instance for global use export const chromaDBService = new ChromaDBService(); //# sourceMappingURL=chromadb-client.js.map