Skip to main content
Glama
database-service.ts7.81 kB
import { QdrantClient } from '@qdrant/js-client-rest'; import { ChromaClient, Collection, IncludeEnum, IEmbeddingFunction } from 'chromadb'; import { generateEmbedding } from '../utils/embedding'; import { VECTOR_SIZE, QDRANT_URL, QDRANT_API_KEY } from '../configs/qdrant'; import { CHROMA_URL } from '../configs/chroma'; import { COLLECTION_NAME, DatabaseType, DATABASE_TYPE } from '../configs/common'; import { FormattedResult } from '../types/qdrant'; import dotenv from 'dotenv'; // Load environment variables dotenv.config(); // Simple embedding function implementation for Chroma class CustomEmbeddingFunction implements IEmbeddingFunction { async generate(texts: string[]): Promise<number[][]> { const embeddings: number[][] = []; for (const text of texts) { const embedding = await generateEmbedding(text); embeddings.push(embedding); } return embeddings; } } // Database service class export class DatabaseService { private qdrantClient?: QdrantClient; private chromaClient?: ChromaClient; private chromaCollection?: Collection; private dbType: DatabaseType; private collectionName: string; private embeddingFunction: IEmbeddingFunction; constructor() { // Use the database type from common config this.dbType = DATABASE_TYPE; this.collectionName = COLLECTION_NAME; this.embeddingFunction = new CustomEmbeddingFunction(); console.log(`Using database type: ${this.dbType}`); } getDbType(): DatabaseType { return this.dbType; } async initialize(): Promise<void> { if (this.dbType === DatabaseType.QDRANT) { await this.initializeQdrant(); } else { await this.initializeChroma(); } } private async initializeQdrant(): Promise<void> { this.qdrantClient = new QdrantClient({ url: QDRANT_URL, apiKey: QDRANT_API_KEY, }); try { const collections = await this.qdrantClient.getCollections(); const collectionExists = collections.collections.some(c => c.name === this.collectionName); if (!collectionExists) { console.log(`Creating Qdrant collection ${this.collectionName}...`); await this.qdrantClient.createCollection(this.collectionName, { vectors: { size: VECTOR_SIZE, distance: 'Cosine', } }); console.log(`Qdrant collection ${this.collectionName} created.`); } else { console.log(`Qdrant collection ${this.collectionName} already exists.`); } } catch (error) { console.error('Error ensuring Qdrant collection exists:', error); throw error; } } private async initializeChroma(): Promise<void> { this.chromaClient = new ChromaClient({ path: CHROMA_URL }); try { const collections = await this.chromaClient.listCollections(); const collectionExists = collections.some((collection: any) => collection.name === this.collectionName); if (!collectionExists) { console.log(`Creating Chroma collection ${this.collectionName}...`); this.chromaCollection = await this.chromaClient.createCollection({ name: this.collectionName, metadata: { 'description': 'MCP Server collection' }, embeddingFunction: this.embeddingFunction }); console.log(`Chroma collection ${this.collectionName} created.`); } else { console.log(`Chroma collection ${this.collectionName} already exists.`); this.chromaCollection = await this.chromaClient.getCollection({ name: this.collectionName, embeddingFunction: this.embeddingFunction }); } } catch (error) { console.error('Error ensuring Chroma collection exists:', error); throw error; } } async search(query: string, limit: number = 3, scoreThreshold: number = 0.7): Promise<FormattedResult[]> { const queryEmbedding = await generateEmbedding(query); if (this.dbType === DatabaseType.QDRANT) { return this.searchQdrant(queryEmbedding, limit, scoreThreshold); } else { return this.searchChroma(queryEmbedding, limit); } } private async searchQdrant(queryEmbedding: number[], limit: number, scoreThreshold: number): Promise<FormattedResult[]> { if (!this.qdrantClient) { throw new Error('Qdrant client not initialized'); } try { const searchResults = await this.qdrantClient.search(this.collectionName, { vector: queryEmbedding, limit: limit, score_threshold: scoreThreshold, with_payload: true, }); return searchResults.map(result => ({ text: String(result.payload?.text || ''), metadata: { source: String(result.payload?.source || ''), score: result.score, ...result.payload }, })); } catch (error) { console.error(`Error searching Qdrant collection ${this.collectionName}:`, error); return []; } } private async searchChroma(queryEmbedding: number[], limit: number): Promise<FormattedResult[]> { if (!this.chromaCollection) { throw new Error('Chroma collection not initialized'); } const searchResults = await this.chromaCollection.query({ queryEmbeddings: [queryEmbedding], nResults: limit, include: [IncludeEnum.Documents, IncludeEnum.Metadatas, IncludeEnum.Distances] }); const formattedResults: FormattedResult[] = []; if (searchResults.documents && searchResults.documents.length > 0 && searchResults.metadatas && searchResults.distances) { const docs = searchResults.documents[0] || []; const metas = searchResults.metadatas[0] || []; const distances = searchResults.distances[0] || []; for (let i = 0; i < docs.length; i++) { const similarityScore = 1 - (distances[i] || 0); const docText = docs[i] !== null && docs[i] !== undefined ? String(docs[i]) : ''; const metaObj = metas[i] && typeof metas[i] === 'object' ? metas[i] as Record<string, any> : {}; formattedResults.push({ text: docText, metadata: { source: String(metaObj.source || ''), score: similarityScore, ...metaObj } }); } } return formattedResults; } async storeDomainKnowledge( text: string, domain: string, metadata: Record<string, any> = {} ): Promise<string> { const pointId = crypto.randomUUID(); const timestamp = new Date().toISOString(); const enhancedMetadata = { ...metadata, domain, timestamp, type: 'domain_knowledge', version: 1 }; if (this.dbType === DatabaseType.QDRANT) { await this.storeDocumentQdrant(text, enhancedMetadata); } else { await this.storeDocumentChroma(text, enhancedMetadata); } return pointId; } private async storeDocumentQdrant(text: string, metadata: Record<string, any>): Promise<void> { if (!this.qdrantClient) { throw new Error('Qdrant client not initialized'); } const embedding = await generateEmbedding(text); const pointId = crypto.randomUUID(); await this.qdrantClient.upsert(this.collectionName, { points: [{ id: pointId, vector: embedding, payload: { text, embedding_type: 'fastembed', ...metadata } }] }); } private async storeDocumentChroma(text: string, metadata: Record<string, any>): Promise<void> { if (!this.chromaCollection) { throw new Error('Chroma collection not initialized'); } await this.chromaCollection.add({ ids: [crypto.randomUUID()], documents: [text], metadatas: [metadata] }); } }

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/hadv/wisdomforge'

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