@ever_cheng/memory-task-mcp
Version:
Memory and task management MCP Server
291 lines • 10.7 kB
JavaScript
/**
* 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
;