search_short_term_memories
Retrieve relevant short-term memories from recent conversations to maintain context and continuity in ongoing discussions.
Instructions
Search and retrieve relevant short-term memories based on recent conversation context. Returns top relevant, next relevant, and random flashback memories.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| recentMessages | Yes | Recent messages to search against | |
| conversation_id | Yes | Current conversation ID | |
| roleWeights | No |
Implementation Reference
- src/tools/short-term-tools.js:78-111 (handler)The async handler function that executes the tool logic. It invokes ShortTermMemoryManager.searchRelevantMemories with the provided arguments, formats the results into topRelevant, nextRelevant, and randomFlashback arrays, saves the memories, and handles errors.handler: async (args) => { try { const results = await memoryManager.searchRelevantMemories( args.recentMessages, args.conversation_id, { roleWeights: args.roleWeights } ); // Format memories for output const formatMemory = (item) => ({ text: item.memory.text, conversation_id: item.memory.conversation_id, timestamp: item.memory.time_stamp.toISOString(), score: item.memory.score, relevance: item.relevance, keywords: item.memory.keywords.slice(0, 10).map(kw => kw.word), modalities: item.memory.modalities || [], attachments: item.memory.attachments || [] }); await storageManager.saveShortTermMemories(memoryManager.getMemories()); return { topRelevant: results.topRelevant.map(formatMemory), nextRelevant: results.nextRelevant.map(formatMemory), randomFlashback: results.randomFlashback.map(formatMemory), totalSearched: memoryManager.getMemories().length }; } catch (error) { return { error: error.message }; } }
- src/tools/short-term-tools.js:66-77 (schema)Zod schema defining the input parameters for the tool: recentMessages (array of role/content), conversation_id (string), and optional roleWeights (object).inputSchema: z.object({ recentMessages: z.array(z.object({ role: z.enum(['user', 'assistant', 'system']), content: z.string() })).describe('Recent messages to search against'), conversation_id: z.string().describe('Current conversation ID'), roleWeights: z.object({ user: z.number().optional(), assistant: z.number().optional(), system: z.number().optional() }).optional() }),
- src/index.js:153-154 (registration)Registers all short-term tools, including search_short_term_memories, by calling createShortTermTools and iterating with registerTool (default managers). Note: tools are dynamically recreated per-conversation during execution.const shortTermTools = createShortTermTools(defaultShortTermManager, defaultStorageManager); shortTermTools.forEach(tool => registerTool(tool, 'short-term'));
- src/memory/short-term.js:414-549 (helper)Core search logic in ShortTermMemoryManager: extracts keywords from recentMessages and modalities, calculates relevance scores (keywords match + time decay + memory score + vector similarity), filters recent/same-convo duplicates, selects top relevant (max 2), next (max 1), random flashbacks (max 2) with recency/score weights, updates scores on activation.async searchRelevantMemories(recentMessages, conversationId, options = {}) { const currentTimeStamp = Date.now(); const queryModalities = normalizeModalities(options.queryModalities ?? options.attachments ?? []); // 提取当前对话的关键词 const messageKeywords = await this.extractMessageKeywords(recentMessages, options.roleWeights); const modalityKeywords = collectModalityKeywords(queryModalities); const currentKeywords = [...messageKeywords, ...modalityKeywords]; const searchOptions = { ...options, queryModalities }; if ('attachments' in searchOptions) { delete searchOptions.attachments; } // 计算所有记忆的相关性 const scoredMemories = this.memories .map((mem, index) => ({ memory: mem, relevance: this.calculateRelevance(mem, currentKeywords, currentTimeStamp, searchOptions), index })) .filter(item => item.relevance >= RELEVANCE_THRESHOLD) .sort((a, b) => b.relevance - a.relevance); // 选择相关和次相关记忆 const selectedIndices = new Set(); const finalTopRelevant = []; const finalNextRelevant = []; const allSelectedRelevantMemories = []; for (const candidateMemory of scoredMemories) { if (finalTopRelevant.length >= MAX_TOP_RELEVANT && finalNextRelevant.length >= MAX_NEXT_RELEVANT) { break; } const isFromSameConversation = candidateMemory.memory.conversation_id === conversationId; const timeDiffSinceMemory = currentTimeStamp - candidateMemory.memory.time_stamp.getTime(); // 跳过同对话的近期记忆 if (isFromSameConversation && timeDiffSinceMemory < MIN_TIME_DIFFERENCE_SAME_CONVERSATION_MS) { continue; } // 检查是否与已选记忆时间过近 let isTooCloseToSelected = false; for (const selectedMem of allSelectedRelevantMemories) { if (Math.abs(candidateMemory.memory.time_stamp.getTime() - selectedMem.memory.time_stamp.getTime()) < MIN_TIME_DIFFERENCE_ANY_MS) { isTooCloseToSelected = true; break; } } if (isTooCloseToSelected) continue; // 分配到 Top 或 Next if (finalTopRelevant.length < MAX_TOP_RELEVANT) { finalTopRelevant.push(candidateMemory); allSelectedRelevantMemories.push(candidateMemory); selectedIndices.add(candidateMemory.index); } else if (finalNextRelevant.length < MAX_NEXT_RELEVANT) { finalNextRelevant.push(candidateMemory); allSelectedRelevantMemories.push(candidateMemory); selectedIndices.add(candidateMemory.index); } } // 随机闪回选择 const finalRandomFlashback = []; let availableForRandomPool = this.memories .map((mem, index) => ({ memory: mem, index })) .filter(item => !selectedIndices.has(item.index)); for (let i = 0; i < MAX_RANDOM_FLASHBACK && availableForRandomPool.length; i++) { const currentCandidates = availableForRandomPool.filter(candidate => { const isFromSameConversation = candidate.memory.conversation_id === conversationId; const timeDiffSinceMemory = currentTimeStamp - candidate.memory.time_stamp.getTime(); if (isFromSameConversation && timeDiffSinceMemory < MIN_TIME_DIFFERENCE_SAME_CONVERSATION_MS) { return false; } const allPreviouslySelected = [...allSelectedRelevantMemories, ...finalRandomFlashback]; for (const selectedItem of allPreviouslySelected) { if (Math.abs(candidate.memory.time_stamp.getTime() - selectedItem.memory.time_stamp.getTime()) < MIN_TIME_DIFFERENCE_ANY_MS) { return false; } } return true; }); if (!currentCandidates.length) break; // 计算权重 const weights = currentCandidates.map(item => { const ageFactor = Math.max(0, 1 - (currentTimeStamp - item.memory.time_stamp.getTime()) / MEMORY_TTL_MS); const cappedScore = Math.max(0, Math.min(item.memory.score, MAX_SCORE_FOR_RANDOM_WEIGHT)); const normalizedScoreFactor = MAX_SCORE_FOR_RANDOM_WEIGHT > 0 ? cappedScore / MAX_SCORE_FOR_RANDOM_WEIGHT : 0; const weight = BASE_RANDOM_WEIGHT + ageFactor * RANDOM_WEIGHT_RECENCY_FACTOR + normalizedScoreFactor * RANDOM_WEIGHT_SCORE_FACTOR; return Math.max(0, weight); }); const selectedRandomItem = this.selectOneWeightedRandom(currentCandidates, weights); if (selectedRandomItem) { selectedRandomItem.relevance = this.calculateRelevance(selectedRandomItem.memory, currentKeywords, currentTimeStamp, searchOptions); finalRandomFlashback.push(selectedRandomItem); availableForRandomPool = availableForRandomPool.filter(item => item.index !== selectedRandomItem.index); } else { break; } } // 强化被激活的记忆 finalTopRelevant.forEach(item => { const memoryToUpdate = this.memories[item.index]; if (memoryToUpdate) { memoryToUpdate.score = Math.min(memoryToUpdate.score + SCORE_INCREMENT_TOP, 100); } }); finalNextRelevant.forEach(item => { const memoryToUpdate = this.memories[item.index]; if (memoryToUpdate) { memoryToUpdate.score = Math.min(memoryToUpdate.score + SCORE_INCREMENT_NEXT, 100); } }); return { topRelevant: finalTopRelevant, nextRelevant: finalNextRelevant, randomFlashback: finalRandomFlashback }; }