UNPKG

@ever_cheng/memory-task-mcp

Version:

Memory and task management MCP Server

424 lines 15.2 kB
"use strict"; /** * Vector Store for MemTask * * ChromaDB integration for semantic search and vector similarity operations. * Handles storage and retrieval of memory chunk embeddings. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.VectorStore = exports.DEFAULT_VECTOR_STORE_CONFIG = void 0; const chromadb_1 = require("chromadb"); const logger_1 = __importDefault(require("./logger")); /** * Default vector store configuration */ exports.DEFAULT_VECTOR_STORE_CONFIG = { collectionName: 'memtask_memories', maxResults: 20, similarityThreshold: 0.5, distanceMetric: 'cosine' }; /** * Vector Store Class */ class VectorStore { constructor(config = exports.DEFAULT_VECTOR_STORE_CONFIG) { this.client = null; this.collection = null; this.initialized = false; this.config = { collectionName: config.collectionName, maxResults: config.maxResults, similarityThreshold: config.similarityThreshold, distanceMetric: config.distanceMetric }; } /** * 初始化 ChromaDB 客戶端和集合 */ async initialize() { if (this.initialized) return; try { // 創建 ChromaDB 客戶端 - 使用環境變數或默認配置 const chromaUrl = process.env.CHROMADB_URL || 'http://localhost:8000'; logger_1.default.info(`正在連接到 ChromaDB: ${chromaUrl}`); this.client = new chromadb_1.ChromaClient({ path: chromaUrl }); // 獲取或創建集合 this.collection = await this.client.getOrCreateCollection({ name: this.config.collectionName, metadata: { 'hnsw:space': this.config.distanceMetric, 'description': 'MemTask memory chunks for semantic search' }, embeddingFunction: undefined // 使用自己的 embedding }); this.initialized = true; logger_1.default.info(`✅ ChromaDB 初始化成功,集合: ${this.config.collectionName}`, { collectionName: this.config.collectionName, distanceMetric: this.config.distanceMetric }); } catch (error) { const chromaUrl = process.env.CHROMADB_URL || 'http://localhost:8000'; logger_1.default.error(`❌ ChromaDB 初始化失敗`, error, { chromaUrl }); throw new Error(`Failed to initialize ChromaDB: ${error}`); } } /** * 添加單個 memory chunk */ async addMemoryChunk(chunk) { await this.initialize(); if (!this.collection || !chunk.embedding) { throw new Error('Collection not initialized or chunk missing embedding'); } try { const metadata = { memoryId: chunk.memoryId, chunkIndex: chunk.chunkIndex, totalChunks: chunk.totalChunks, created_at: chunk.metadata.created_at, originalLength: chunk.metadata.originalLength, chunkLength: chunk.metadata.chunkLength, tags: chunk.metadata.tags.join(',') // Convert tags array to string for ChromaDB }; await this.collection.add({ ids: [chunk.id], embeddings: [chunk.embedding], metadatas: [metadata], documents: [chunk.content] }); logger_1.default.debug(`✅ Added chunk: ${chunk.id}`, { chunkId: chunk.id, memoryId: chunk.memoryId }); } catch (error) { logger_1.default.error(`❌ Failed to add chunk ${chunk.id}`, error, { chunkId: chunk.id }); throw error; } } /** * 批量添加 memory chunks */ async addMemoryChunks(chunks) { await this.initialize(); if (!this.collection) { throw new Error('Collection not initialized'); } let processed = 0; let failed = 0; const errors = []; // 過濾出有 embedding 的 chunks const validChunks = chunks.filter(chunk => chunk.embedding); if (validChunks.length === 0) { return { success: true, processed: 0, failed: 0, errors: [] }; } try { const ids = validChunks.map(chunk => chunk.id); const embeddings = validChunks.map(chunk => chunk.embedding); const metadatas = validChunks.map(chunk => ({ memoryId: chunk.memoryId, chunkIndex: chunk.chunkIndex, totalChunks: chunk.totalChunks, tags: chunk.metadata.tags.join(','), // Convert tags array to string for ChromaDB created_at: chunk.metadata.created_at, originalLength: chunk.metadata.originalLength, chunkLength: chunk.metadata.chunkLength })); const documents = validChunks.map(chunk => chunk.content); await this.collection.add({ ids, embeddings, metadatas, documents }); processed = validChunks.length; logger_1.default.info(`✅ Batch added ${processed} chunks`, { processed, totalChunks: validChunks.length }); } catch (error) { failed = validChunks.length; errors.push(String(error)); logger_1.default.error(`❌ Batch add failed`, error, { totalChunks: validChunks.length }); } return { success: failed === 0, processed, failed, errors }; } /** * 向量相似度搜尋 */ async searchSimilar(queryEmbedding, limit = this.config.maxResults, filters) { await this.initialize(); if (!this.collection) { throw new Error('Collection not initialized'); } try { // 構建查詢條件 const whereClause = {}; if (filters?.tags && filters.tags.length > 0) { whereClause.tags = { '$in': filters.tags }; } if (filters?.memoryIds && filters.memoryIds.length > 0) { whereClause.memoryId = { '$in': filters.memoryIds }; } const queryResults = await this.collection.query({ queryEmbeddings: [queryEmbedding], nResults: limit, where: Object.keys(whereClause).length > 0 ? whereClause : undefined, include: [chromadb_1.IncludeEnum.Distances, chromadb_1.IncludeEnum.Metadatas, chromadb_1.IncludeEnum.Documents] }); // 轉換結果格式 const results = []; if (queryResults.ids[0]) { for (let i = 0; i < queryResults.ids[0].length; i++) { const distance = queryResults.distances[0][i]; const similarity = this.distanceToSimilarity(distance); // 過濾低相似度結果 if (similarity >= this.config.similarityThreshold) { const metadata = queryResults.metadatas[0][i]; results.push({ chunkId: queryResults.ids[0][i], memoryId: metadata?.memoryId, similarity, distance, metadata: metadata, content: queryResults.documents[0][i] }); } } } return results; } catch (error) { logger_1.default.error('Vector search failed', error, { limit, hasFilters: !!filters }); throw error; } } /** * 根據 memory ID 搜尋相關 chunks */ async getChunksByMemoryId(memoryId) { await this.initialize(); if (!this.collection) { throw new Error('Collection not initialized'); } try { const results = await this.collection.get({ where: { memoryId }, include: [chromadb_1.IncludeEnum.Metadatas, chromadb_1.IncludeEnum.Documents] }); return results.ids?.map((id, index) => ({ chunkId: id, memoryId, similarity: 1.0, // 完全相關 distance: 0, metadata: results.metadatas[index], content: results.documents[index] })) || []; } catch (error) { logger_1.default.error(`Failed to get chunks for memory ${memoryId}`, error, { memoryId }); throw error; } } /** * 刪除 memory 的所有 chunks */ async deleteMemoryChunks(memoryId) { await this.initialize(); if (!this.collection) { throw new Error('Collection not initialized'); } try { // 先獲取所有相關的 chunk IDs const existingChunks = await this.collection.get({ where: { memoryId }, include: [] }); if (existingChunks.ids && existingChunks.ids.length > 0) { await this.collection.delete({ ids: existingChunks.ids }); logger_1.default.info(`✅ Deleted ${existingChunks.ids.length} chunks for memory ${memoryId}`, { memoryId, deletedCount: existingChunks.ids.length }); return true; } return false; } catch (error) { logger_1.default.error(`Failed to delete chunks for memory ${memoryId}`, error, { memoryId }); return false; } } /** * 更新 memory chunks(先刪除再添加) */ async updateMemoryChunks(memoryId, newChunks) { try { // 先刪除現有 chunks await this.deleteMemoryChunks(memoryId); // 添加新 chunks const result = await this.addMemoryChunks(newChunks); return result.success; } catch (error) { logger_1.default.error(`Failed to update chunks for memory ${memoryId}`, error, { memoryId }); return false; } } /** * 獲取 vector store 統計信息 */ async getStats() { await this.initialize(); if (!this.collection) { throw new Error('Collection not initialized'); } try { const result = await this.collection.get({ include: [chromadb_1.IncludeEnum.Metadatas] }); const totalChunks = result.ids?.length || 0; const uniqueMemories = new Set(result.metadatas?.map(m => m?.memoryId)).size; return { totalChunks, uniqueMemories, collectionSize: totalChunks, avgSimilarityScore: 0, // 需要額外計算 metadata: { collectionName: this.config.collectionName, distanceMetric: this.config.distanceMetric } }; } catch (error) { logger_1.default.error('Failed to get stats', error, { collectionName: this.config.collectionName }); throw error; } } /** * 清空整個集合 */ async clearCollection() { await this.initialize(); if (!this.client) { throw new Error('Client not initialized'); } try { // 刪除集合 await this.client.deleteCollection({ name: this.config.collectionName }); // 重新創建集合 this.collection = await this.client.getOrCreateCollection({ name: this.config.collectionName, metadata: { 'hnsw:space': this.config.distanceMetric, 'description': 'MemTask memory chunks for semantic search' }, embeddingFunction: undefined }); logger_1.default.info(`✅ Collection ${this.config.collectionName} cleared`, { collectionName: this.config.collectionName }); return true; } catch (error) { logger_1.default.error('Failed to clear collection', error, { collectionName: this.config.collectionName }); return false; } } /** * 將距離轉換為相似度 (0-1) */ distanceToSimilarity(distance) { if (this.config.distanceMetric === 'cosine') { // 餘弦距離轉相似度 return 1 - distance; } else { // 歐幾里得距離或曼哈頓距離的簡單轉換 return 1 / (1 + distance); } } /** * 健康檢查 */ async healthCheck() { try { await this.initialize(); // 測試集合操作 if (this.collection) { await this.collection.count(); } return { status: 'healthy', initialized: this.initialized, collectionExists: this.collection !== null }; } catch (error) { return { status: 'unhealthy', initialized: this.initialized, collectionExists: this.collection !== null, error: String(error) }; } } /** * 更新配置 */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } /** * 獲取當前配置 */ getConfig() { return { ...this.config }; } /** * 清理資源 (實現 Disposable 介面) */ async dispose() { try { // 清理 Collection 資源 if (this.collection) { // ChromaDB collection doesn't need explicit cleanup this.collection = null; } // 清理 Client 資源 if (this.client) { // ChromaDB client doesn't need explicit cleanup in current version // but we null it out for memory management this.client = null; } // 重置狀態 this.initialized = false; logger_1.default.info('✅ VectorStore disposed successfully'); } catch (error) { logger_1.default.error('❌ Error disposing VectorStore', error); throw error; } } /** * 清理資源 (向後兼容) * @deprecated Use dispose() instead */ async cleanup() { await this.dispose(); } } exports.VectorStore = VectorStore; //# sourceMappingURL=vector_store.js.map