UNPKG

@symindx/cli

Version:

SYMindX - AI Agent Framework CLI with NyX agent

278 lines โ€ข 11.4 kB
/** * SQLite Memory Provider for CLI SYMindX */ import { MemoryType, MemoryDuration } from '../types/memory.js'; import { BaseMemoryProvider } from './base-memory-provider.js'; import Database from 'better-sqlite3'; export class SQLiteMemoryProvider extends BaseMemoryProvider { db; constructor(config) { const metadata = { id: 'sqlite', name: 'SQLite Memory Provider', description: 'A memory provider that stores memories in a local SQLite database', version: '1.0.0', author: 'SYMindX Team', supportsVectorSearch: true, isPersistent: true }; super(config, metadata); this.db = new Database(config.dbPath); if (config.createTables !== false) { this.initializeDatabase(); } } initializeDatabase() { // Create memories table this.db.exec(` CREATE TABLE IF NOT EXISTS memories ( id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, type TEXT NOT NULL, content TEXT NOT NULL, embedding BLOB, metadata TEXT, importance REAL NOT NULL DEFAULT 0.5, timestamp INTEGER NOT NULL, tags TEXT, duration TEXT NOT NULL DEFAULT 'long_term', expires_at TEXT ) `); // Create indexes for better performance this.db.exec(` CREATE INDEX IF NOT EXISTS idx_memories_agent_id ON memories(agent_id); CREATE INDEX IF NOT EXISTS idx_memories_type ON memories(type); CREATE INDEX IF NOT EXISTS idx_memories_timestamp ON memories(timestamp); CREATE INDEX IF NOT EXISTS idx_memories_importance ON memories(importance); CREATE INDEX IF NOT EXISTS idx_memories_duration ON memories(duration); CREATE INDEX IF NOT EXISTS idx_memories_expires_at ON memories(expires_at); `); console.log('โœ… SQLite memory database initialized'); } async store(agentId, memory) { const stmt = this.db.prepare(` INSERT OR REPLACE INTO memories ( id, agent_id, type, content, embedding, metadata, importance, timestamp, tags, duration, expires_at ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `); const embeddingBuffer = memory.embedding ? Buffer.from(new Float32Array(memory.embedding).buffer) : null; const metadataJson = JSON.stringify(memory.metadata); const tagsJson = JSON.stringify(memory.tags); stmt.run(memory.id, agentId, memory.type, memory.content, embeddingBuffer, metadataJson, memory.importance, memory.timestamp.getTime(), tagsJson, memory.duration || 'long_term', memory.expiresAt ? memory.expiresAt.getTime() : null); // Only log conversation memories from user interactions if (memory.type === MemoryType.INTERACTION && (memory.metadata?.source === 'chat_command' || memory.metadata?.source === 'chat_command_fallback' || memory.metadata?.messageType === 'user_input' || memory.metadata?.messageType === 'agent_response')) { console.log(`๐Ÿ’พ Stored ${memory.duration || 'long_term'} memory: ${memory.type} for agent ${agentId}`); } } async retrieve(agentId, query, limit = 10) { let stmt; let params; // Base condition to filter out expired short-term memories const now = Date.now(); const baseCondition = `agent_id = ? AND (duration != 'short_term' OR expires_at IS NULL OR expires_at > ${now})`; if (query === 'recent') { // Get most recent memories stmt = this.db.prepare(` SELECT * FROM memories WHERE ${baseCondition} ORDER BY timestamp DESC LIMIT ? `); params = [agentId, limit]; } else if (query === 'important') { // Get most important memories stmt = this.db.prepare(` SELECT * FROM memories WHERE ${baseCondition} ORDER BY importance DESC LIMIT ? `); params = [agentId, limit]; } else if (query === 'short_term') { // Get only short-term memories that haven't expired stmt = this.db.prepare(` SELECT * FROM memories WHERE agent_id = ? AND duration = 'short_term' AND (expires_at IS NULL OR expires_at > ${now}) ORDER BY timestamp DESC LIMIT ? `); params = [agentId, limit]; } else if (query === 'long_term') { // Get only long-term memories stmt = this.db.prepare(` SELECT * FROM memories WHERE agent_id = ? AND duration = 'long_term' ORDER BY importance DESC LIMIT ? `); params = [agentId, limit]; } else { // Text search in content stmt = this.db.prepare(` SELECT * FROM memories WHERE ${baseCondition} AND content LIKE ? ORDER BY importance DESC, timestamp DESC LIMIT ? `); params = [agentId, `%${query}%`, limit]; } const rows = stmt.all(...params); return rows.map(row => this.rowToMemoryRecord(row)); } async search(agentId, embedding, limit = 10) { const now = Date.now(); const baseCondition = `agent_id = ? AND embedding IS NOT NULL AND (duration != 'short_term' OR expires_at IS NULL OR expires_at > ${now})`; try { // First, get all memories with embeddings for this agent const stmt = this.db.prepare(` SELECT * FROM memories WHERE ${baseCondition} ORDER BY timestamp DESC `); const rows = stmt.all(agentId); if (rows.length === 0) { console.log('๐Ÿ” No memories with embeddings found, falling back to recent memories'); return this.retrieve(agentId, 'recent', limit); } // Calculate cosine similarity for each memory with an embedding const similarities = []; for (const row of rows) { if (row.embedding) { const memoryEmbedding = this.bufferToEmbedding(row.embedding); if (memoryEmbedding) { const similarity = this.cosineSimilarity(embedding, memoryEmbedding); similarities.push({ memory: this.rowToMemoryRecord(row), similarity }); } } } // Sort by similarity and return top results similarities.sort((a, b) => b.similarity - a.similarity); const results = similarities.slice(0, limit).map(item => item.memory); console.log(`๐ŸŽฏ Vector search found ${results.length} similar memories (avg similarity: ${(similarities.slice(0, limit).reduce((sum, item) => sum + item.similarity, 0) / Math.min(limit, similarities.length)).toFixed(3)})`); return results; } catch (error) { console.warn('โš ๏ธ Vector search failed, falling back to recent memories:', error); return this.retrieve(agentId, 'recent', limit); } } async delete(agentId, memoryId) { const stmt = this.db.prepare(` DELETE FROM memories WHERE agent_id = ? AND id = ? `); const result = stmt.run(agentId, memoryId); if (result.changes === 0) { throw new Error(`Memory ${memoryId} not found for agent ${agentId}`); } console.log(`๐Ÿ—‘๏ธ Deleted memory: ${memoryId} for agent ${agentId}`); } async clear(agentId) { const stmt = this.db.prepare(` DELETE FROM memories WHERE agent_id = ? `); const result = stmt.run(agentId); console.log(`๐Ÿงน Cleared ${result.changes} memories for agent ${agentId}`); } async getStats(agentId) { const totalStmt = this.db.prepare(` SELECT COUNT(*) as count FROM memories WHERE agent_id = ? `); const typeStmt = this.db.prepare(` SELECT type, COUNT(*) as count FROM memories WHERE agent_id = ? GROUP BY type `); const totalResult = totalStmt.get(agentId); const total = totalResult?.count || 0; const typeRows = typeStmt.all(agentId); const byType = {}; typeRows.forEach((row) => { byType[row.type] = row.count; }); return { total, byType }; } async cleanup(agentId, retentionDays) { const now = Date.now(); const cutoffTime = now - (retentionDays * 24 * 60 * 60 * 1000); // First, clean up expired short-term memories const expiredStmt = this.db.prepare(` DELETE FROM memories WHERE agent_id = ? AND duration = 'short_term' AND expires_at IS NOT NULL AND expires_at < ? `); const expiredResult = expiredStmt.run(agentId, now); console.log(`๐Ÿงน Cleaned up ${expiredResult.changes} expired short-term memories for agent ${agentId}`); // Then, clean up old memories based on retention days const oldStmt = this.db.prepare(` DELETE FROM memories WHERE agent_id = ? AND timestamp < ? `); const oldResult = oldStmt.run(agentId, cutoffTime); console.log(`๐Ÿงน Cleaned up ${oldResult.changes} old memories for agent ${agentId}`); } rowToMemoryRecord(row) { let embedding = undefined; if (row.embedding) { embedding = this.bufferToEmbedding(row.embedding); } return { id: row.id, agentId: row.agent_id, type: row.type ? MemoryType[row.type.toUpperCase()] || MemoryType.EXPERIENCE : MemoryType.EXPERIENCE, content: row.content, embedding, metadata: JSON.parse(row.metadata || '{}'), importance: row.importance, timestamp: new Date(row.timestamp), tags: JSON.parse(row.tags || '[]'), duration: (row.duration && typeof row.duration === 'string') ? MemoryDuration[row.duration.toUpperCase()] || MemoryDuration.LONG_TERM : MemoryDuration.LONG_TERM, expiresAt: row.expires_at ? new Date(row.expires_at) : undefined }; } bufferToEmbedding(buffer) { try { const floatArray = new Float32Array(buffer.buffer, buffer.byteOffset, buffer.byteLength / Float32Array.BYTES_PER_ELEMENT); return Array.from(floatArray); } catch (error) { console.warn('โš ๏ธ Failed to convert buffer to embedding:', error); return undefined; } } cosineSimilarity(a, b) { if (a.length !== b.length) { console.warn(`โš ๏ธ Vector dimension mismatch: ${a.length} vs ${b.length}`); return 0; } let dotProduct = 0; let normA = 0; let normB = 0; for (let i = 0; i < a.length; i++) { dotProduct += a[i] * b[i]; normA += a[i] * a[i]; normB += b[i] * b[i]; } const magnitude = Math.sqrt(normA) * Math.sqrt(normB); if (magnitude === 0) return 0; return dotProduct / magnitude; } } export function createSQLiteMemoryProvider(config) { return new SQLiteMemoryProvider(config); } //# sourceMappingURL=sqlite-provider.js.map