UNPKG

@ever_cheng/memory-task-mcp

Version:

Memory and task management MCP Server

291 lines 10.7 kB
"use strict"; /** * Semantic Search Service for MemTask * * Provides high-level semantic search functionality by coordinating * embedding generation, vector search, and result aggregation. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SemanticSearchService = exports.DEFAULT_SEMANTIC_SEARCH_CONFIG = void 0; const logger_1 = __importDefault(require("./logger")); /** * Default semantic search configuration */ exports.DEFAULT_SEMANTIC_SEARCH_CONFIG = { defaultLimit: 10, similarityThreshold: 0.6, aggregationStrategy: 'weighted', boostFactors: { recentness: 0.1, tagMatch: 0.2, completeness: 0.1 } }; /** * Semantic Search Service Class */ class SemanticSearchService { constructor(memoryManager, embeddingService, vectorStore, config = exports.DEFAULT_SEMANTIC_SEARCH_CONFIG) { this.memoryManager = memoryManager; this.embeddingService = embeddingService; this.vectorStore = vectorStore; this.queryStats = new Map(); this.performanceHistory = []; this.config = config; } /** * 執行語義搜尋 */ async search(query) { const startTime = Date.now(); try { // 記錄查詢統計 this.recordQuery(query.query); // 執行語義搜尋 let results = await this.semanticSearch(query); // 應用後處理增强 results = this.applyBoostFactors(results, query); // 排序和截取結果 const limit = query.limit ?? this.config.defaultLimit; results = results .sort((a, b) => b.relevanceScore - a.relevanceScore) .slice(0, limit); // 更新性能統計 const processingTime = Date.now() - startTime; this.recordPerformance(processingTime); // 添加搜尋元數據 results.forEach(result => { result.searchMetadata.processingTime = processingTime; }); return results; } catch (error) { logger_1.default.error('Semantic search failed', error); throw error; } } /** * 語義搜尋 */ async semanticSearch(query) { const embeddingStartTime = Date.now(); // 生成查詢 embedding const embeddingResult = await this.embeddingService.generateEmbedding(query.query); const embeddingTime = Date.now() - embeddingStartTime; const vectorSearchStartTime = Date.now(); // 執行向量搜尋 const vectorResults = await this.vectorStore.searchSimilar(embeddingResult.embedding, (query.limit ?? this.config.defaultLimit) * 3, // 取更多結果用於聚合 { tags: query.tags, memoryIds: query.memoryIds }); const vectorSearchTime = Date.now() - vectorSearchStartTime; const aggregationStartTime = Date.now(); // 按 memory 聚合結果 const aggregatedResults = await this.aggregateChunkResults(vectorResults, query); const aggregationTime = Date.now() - aggregationStartTime; // 添加搜尋元數據 return aggregatedResults.map(result => ({ ...result, searchMetadata: { searchType: 'semantic', processingTime: 0, // 會在外層更新 totalChunks: vectorResults.length, boostFactors: {} } })); } /** * 按 memory 聚合 chunk 搜尋結果 */ async aggregateChunkResults(vectorResults, query) { // 按 memoryId 分組 const memoryGroups = new Map(); for (const result of vectorResults) { const memoryId = result.memoryId; if (!memoryGroups.has(memoryId)) { memoryGroups.set(memoryId, []); } memoryGroups.get(memoryId).push(result); } // 聚合每個 memory 的結果 const aggregatedResults = []; for (const [memoryId, chunks] of memoryGroups) { const memory = await this.memoryManager.getMemory(memoryId); if (!memory) continue; // 應用聚合策略 const aggregatedSimilarity = this.aggregateSimilarityScores(chunks.map(c => c.similarity)); // 過濾低相似度結果 if (aggregatedSimilarity < (query.similarityThreshold ?? this.config.similarityThreshold)) { continue; } // 創建 chunk matches const matchedChunks = chunks.map(chunk => ({ chunkId: chunk.chunkId, chunkIndex: chunk.metadata.chunkIndex, similarity: chunk.similarity, matchedContent: chunk.content || '', context: `第 ${chunk.metadata.chunkIndex + 1} 部分,共 ${chunk.metadata.totalChunks} 部分` })); aggregatedResults.push({ memory, similarity: aggregatedSimilarity, relevanceScore: aggregatedSimilarity, matchedChunks, searchMetadata: { searchType: 'semantic', processingTime: 0, totalChunks: chunks.length, boostFactors: {} } }); } return aggregatedResults; } /** * 聚合相似度分數 */ aggregateSimilarityScores(scores) { if (scores.length === 0) return 0; switch (this.config.aggregationStrategy) { case 'max': return Math.max(...scores); case 'avg': return scores.reduce((sum, score) => sum + score, 0) / scores.length; case 'weighted': { // 給最高分更大權重 const sortedScores = [...scores].sort((a, b) => b - a); let weightedSum = 0; let totalWeight = 0; for (let i = 0; i < sortedScores.length; i++) { const weight = 1 / (i + 1); // 遞減權重 weightedSum += sortedScores[i] * weight; totalWeight += weight; } return weightedSum / totalWeight; } default: return Math.max(...scores); } } /** * 應用增強因子 */ applyBoostFactors(results, query) { const now = new Date(); return results.map(result => { const boostFactors = {}; let boostMultiplier = 1.0; // 時間新近性增強 const createdAt = new Date(result.memory.metadata.created_at); const daysSinceCreated = (now.getTime() - createdAt.getTime()) / (1000 * 60 * 60 * 24); const recencyBoost = Math.exp(-daysSinceCreated / 30) * this.config.boostFactors.recentness; boostFactors.recentness = recencyBoost; boostMultiplier += recencyBoost; // 標籤匹配增強 if (query.tags && query.tags.length > 0) { const matchingTags = result.memory.metadata.tags.filter(tag => query.tags.includes(tag)); const tagBoost = (matchingTags.length / query.tags.length) * this.config.boostFactors.tagMatch; boostFactors.tagMatch = tagBoost; boostMultiplier += tagBoost; } // 內容完整性增強 const contentLength = result.memory.content.length; const completenessBoost = Math.min(contentLength / 1000, 1) * this.config.boostFactors.completeness; boostFactors.completeness = completenessBoost; boostMultiplier += completenessBoost; return { ...result, relevanceScore: result.relevanceScore * boostMultiplier, searchMetadata: { ...result.searchMetadata, boostFactors } }; }); } /** * 記錄查詢統計 */ recordQuery(query) { const normalizedQuery = query.toLowerCase().trim(); const currentCount = this.queryStats.get(normalizedQuery) || 0; this.queryStats.set(normalizedQuery, currentCount + 1); } /** * 記錄性能統計 */ recordPerformance(processingTime) { this.performanceHistory.push(processingTime); // 保持最近 1000 次記錄 if (this.performanceHistory.length > 1000) { this.performanceHistory.shift(); } } /** * 獲取搜尋統計 */ getSearchStats() { const totalQueries = Array.from(this.queryStats.values()).reduce((sum, count) => sum + count, 0); const averageResponseTime = this.performanceHistory.length > 0 ? this.performanceHistory.reduce((sum, time) => sum + time, 0) / this.performanceHistory.length : 0; const popularQueries = Array.from(this.queryStats.entries()) .sort((a, b) => b[1] - a[1]) .slice(0, 10) .map(([query, count]) => ({ query, count })); return { totalQueries, averageResponseTime, popularQueries, topTags: [], // 需要額外實現 performanceMetrics: { embeddingTime: 0, // 需要額外計算 vectorSearchTime: 0, aggregationTime: 0 } }; } /** * 更新配置 */ updateConfig(newConfig) { this.config = { ...this.config, ...newConfig }; } /** * 獲取當前配置 */ getConfig() { return { ...this.config }; } /** * 清理統計數據 */ clearStats() { this.queryStats.clear(); this.performanceHistory = []; } /** * 清理資源 (實現 Disposable 介面) */ async dispose() { try { // 清理統計數據 this.clearStats(); // 清理服務引用(不主動 dispose 外部服務,只清除引用) // 外部服務應該由其創建者負責清理 logger_1.default.info('✅ SemanticSearchService disposed successfully'); } catch (error) { logger_1.default.error('❌ Error disposing SemanticSearchService', error); throw error; } } } exports.SemanticSearchService = SemanticSearchService; //# sourceMappingURL=semantic_search.js.map