UNPKG

semem

Version:

Semantic Memory for Intelligent Agents

172 lines (143 loc) 5.41 kB
import ContextWindowManager from './ContextWindowManager.js' import logger from 'loglevel' /** * Manages context windows and summaries for LLM interactions */ export default class ContextManager { constructor(options = {}) { this.maxTokens = options.maxTokens || 8192 this.maxTimeWindow = options.maxTimeWindow || 24 * 60 * 60 * 1000 // 24 hours this.relevanceThreshold = options.relevanceThreshold || 0.7 this.maxContextSize = options.maxContextSize || 5 this.contextBuffer = [] this.windowManager = new ContextWindowManager({ maxWindowSize: this.maxTokens, minWindowSize: Math.floor(this.maxTokens / 4), overlapRatio: options.overlapRatio || 0.1 }) } /** * Add interaction to context buffer with similarity score */ addToContext(interaction, similarity = 1.0) { if (!interaction || typeof interaction !== 'object') { logger.warn('Invalid interaction provided to context') return } this.contextBuffer.push({ ...interaction, similarity, addedAt: Date.now() }) if (this.contextBuffer.length > this.maxContextSize * 2) { this.pruneContext() } } /** * Remove old or low-relevance items from context */ pruneContext() { const now = Date.now() this.contextBuffer = this.contextBuffer .filter(item => { const age = now - item.addedAt return age < this.maxTimeWindow && item.similarity >= this.relevanceThreshold }) .sort((a, b) => b.similarity - a.similarity) .slice(0, this.maxContextSize) } /** * Create a concise summary of interactions grouped by concept */ summarizeContext(interactions) { if (!Array.isArray(interactions) || interactions.length === 0) { return '' } const groupedInteractions = {} for (const interaction of interactions) { if (!interaction) continue const mainConcept = interaction.concepts?.[0] || 'general' if (!groupedInteractions[mainConcept]) { groupedInteractions[mainConcept] = [] } groupedInteractions[mainConcept].push(interaction) } const summaries = [] for (const [concept, group] of Object.entries(groupedInteractions)) { if (group.length === 1) { summaries.push(this.formatSingleInteraction(group[0])) } else { summaries.push(this.formatGroupSummary(concept, group)) } } return summaries.join('\n\n') } /** * Format a single interaction for display */ formatSingleInteraction(interaction) { if (!interaction?.prompt || !interaction?.output) { return '' } return `Q: ${interaction.prompt}\nA: ${interaction.output}` } /** * Create summary for a group of related interactions */ formatGroupSummary(concept, interactions) { if (!Array.isArray(interactions) || interactions.length === 0) { return '' } const summaryLines = interactions .slice(0, 3) // Limit examples per group .map(i => { if (!i?.prompt || !i?.output) return null const truncatedOutput = i.output.substring(0, 50) return `- ${i.prompt} → ${truncatedOutput}${truncatedOutput.length < i.output.length ? '...' : ''}` }) .filter(Boolean) if (summaryLines.length === 0) return '' return `Topic: ${concept}\n${summaryLines.join('\n')}` } /** * Build complete context including history and current prompt */ buildContext(currentPrompt, retrievals = [], recentInteractions = [], options = {}) { if (!currentPrompt) { logger.warn('No current prompt provided to buildContext') return '' } this.pruneContext() // Add new relevant interactions retrievals?.forEach(retrieval => { if (retrieval?.interaction) { this.addToContext(retrieval.interaction, retrieval.similarity) } }) // Add recent interactions recentInteractions?.forEach(interaction => { if (interaction) { this.addToContext(interaction, 0.9) } }) const contextParts = [] // Add system context if provided if (options.systemContext) { contextParts.push(`System Context: ${options.systemContext}`) } // Add summarized historical context const historicalContext = this.summarizeContext( this.contextBuffer.slice(0, this.maxContextSize) ) if (historicalContext) { contextParts.push('Relevant Context:', historicalContext) } const fullContext = contextParts.join('\n\n') // Process through window manager if needed if (this.windowManager.estimateTokens(fullContext) > this.maxTokens) { const windows = this.windowManager.processContext(fullContext) return this.windowManager.mergeOverlappingContent(windows) } return fullContext } }