@symindx/cli
Version:
SYMindX - AI Agent Framework CLI with NyX agent
278 lines โข 11.4 kB
JavaScript
/**
* 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