Skip to main content
Glama
memory.ts9.72 kB
import Database from 'better-sqlite3'; import { randomUUID } from 'crypto'; import { generateEmbedding, OllamaConfig } from './ollama.js'; import { initializeDatabase, Memory, Relationship, DatabaseConfig } from './database.js'; export interface VectorMemoryConfig { database?: DatabaseConfig; ollama?: OllamaConfig; } export interface SearchOptions { limit?: number; threshold?: number; includeEmbeddings?: boolean; } export interface RelationshipQuery { memoryId?: string; relationshipType?: string; direction?: 'from' | 'to' | 'both'; minStrength?: number; limit?: number; } let db: Database.Database; let ollamaConfig: OllamaConfig; export function initializeMemory(config: VectorMemoryConfig = {}): void { db = initializeDatabase(config.database); ollamaConfig = config.ollama || {}; } export function addMemory(content: string, metadata: Record<string, any> = {}): Promise<string> { return new Promise(async (resolve, reject) => { try { const id = randomUUID(); const now = new Date().toISOString(); const embedding = await generateEmbedding(content, ollamaConfig); const insertMemory = db.prepare(` INSERT INTO memories (id, content, metadata, created_at, updated_at) VALUES (?, ?, ?, ?, ?) `); insertMemory.run( id, content, JSON.stringify(metadata), now, now ); try { const insertEmbedding = db.prepare(` INSERT INTO memory_embeddings (id, embedding) VALUES (?, ?) `); const embeddingBuffer = new Float32Array(embedding); insertEmbedding.run(id, Buffer.from(embeddingBuffer.buffer)); } catch { // Vector table insert failed - vector search will not be available for this memory } resolve(id); } catch (error) { reject(error); } }); } export function getMemory(id: string): Memory | null { const stmt = db.prepare(` SELECT id, content, metadata, created_at, updated_at FROM memories WHERE id = ? `); const row = stmt.get(id) as any; if (!row) return null; return { id: row.id, content: row.content, metadata: JSON.parse(row.metadata || '{}'), created_at: new Date(row.created_at), updated_at: new Date(row.updated_at) }; } export function updateMemory(id: string, content?: string, metadata?: Record<string, any>): Promise<boolean> { return new Promise(async (resolve, reject) => { try { const memory = getMemory(id); if (!memory) { resolve(false); return; } const updates: string[] = []; const values: any[] = []; if (content !== undefined) { updates.push('content = ?'); values.push(content); const embedding = await generateEmbedding(content, ollamaConfig); try { const updateEmbedding = db.prepare(` UPDATE memory_embeddings SET embedding = ? WHERE id = ? `); const embeddingBuffer = new Float32Array(embedding); updateEmbedding.run(Buffer.from(embeddingBuffer.buffer), id); } catch { // Vector table update failed - vector search may be stale for this memory } } if (metadata !== undefined) { updates.push('metadata = ?'); values.push(JSON.stringify(metadata)); } if (updates.length === 0) { resolve(true); return; } updates.push('updated_at = CURRENT_TIMESTAMP'); values.push(id); const stmt = db.prepare(` UPDATE memories SET ${updates.join(', ')} WHERE id = ? `); const result = stmt.run(...values); resolve(result.changes > 0); } catch (error) { reject(error); } }); } export function deleteMemory(id: string): boolean { const stmt = db.prepare('DELETE FROM memories WHERE id = ?'); const result = stmt.run(id); try { const deleteEmbedding = db.prepare('DELETE FROM memory_embeddings WHERE id = ?'); deleteEmbedding.run(id); } catch { // Vector table delete failed - stale embedding may remain } return result.changes > 0; } export function searchMemories(query: string, options: SearchOptions = {}): Promise<Memory[]> { return new Promise(async (resolve, reject) => { try { const { limit = 10, threshold = 0.5 } = options; try { const queryEmbedding = await generateEmbedding(query, ollamaConfig); const queryBuffer = new Float32Array(queryEmbedding); const stmt = db.prepare(` SELECT m.id, m.content, m.metadata, m.created_at, m.updated_at, vec_distance_cosine(v.embedding, ?) as distance FROM memories m JOIN memory_embeddings v ON m.id = v.id WHERE vec_distance_cosine(v.embedding, ?) < ? ORDER BY distance ASC LIMIT ? `); const rows = stmt.all(Buffer.from(queryBuffer.buffer), Buffer.from(queryBuffer.buffer), 1 - threshold, limit) as any[]; const memories = rows.map(row => ({ id: row.id, content: row.content, metadata: JSON.parse(row.metadata || '{}'), created_at: new Date(row.created_at), updated_at: new Date(row.updated_at) })); resolve(memories); } catch (vectorError) { const stmt = db.prepare(` SELECT id, content, metadata, created_at, updated_at FROM memories WHERE content LIKE ? ORDER BY created_at DESC LIMIT ? `); const rows = stmt.all(`%${query}%`, limit) as any[]; const memories = rows.map(row => ({ id: row.id, content: row.content, metadata: JSON.parse(row.metadata || '{}'), created_at: new Date(row.created_at), updated_at: new Date(row.updated_at) })); resolve(memories); } } catch (error) { reject(error); } }); } export function addRelationship( fromMemoryId: string, toMemoryId: string, relationshipType: string, strength: number = 1.0, metadata: Record<string, any> = {} ): string { const id = randomUUID(); const stmt = db.prepare(` INSERT INTO relationships (id, from_memory_id, to_memory_id, relationship_type, strength, metadata) VALUES (?, ?, ?, ?, ?, ?) `); stmt.run(id, fromMemoryId, toMemoryId, relationshipType, strength, JSON.stringify(metadata)); return id; } export function getRelationships(query: RelationshipQuery = {}): Relationship[] { const { memoryId, relationshipType, direction = 'both', minStrength = 0, limit = 100 } = query; let whereClause = 'WHERE strength >= ?'; const params: any[] = [minStrength]; if (memoryId) { if (direction === 'from') { whereClause += ' AND from_memory_id = ?'; params.push(memoryId); } else if (direction === 'to') { whereClause += ' AND to_memory_id = ?'; params.push(memoryId); } else { whereClause += ' AND (from_memory_id = ? OR to_memory_id = ?)'; params.push(memoryId, memoryId); } } if (relationshipType) { whereClause += ' AND relationship_type = ?'; params.push(relationshipType); } params.push(limit); const stmt = db.prepare(` SELECT id, from_memory_id, to_memory_id, relationship_type, strength, metadata, created_at FROM relationships ${whereClause} ORDER BY strength DESC, created_at DESC LIMIT ? `); const rows = stmt.all(...params) as any[]; return rows.map(row => ({ id: row.id, from_memory_id: row.from_memory_id, to_memory_id: row.to_memory_id, relationship_type: row.relationship_type, strength: row.strength, metadata: JSON.parse(row.metadata || '{}'), created_at: new Date(row.created_at) })); } export function updateRelationship(id: string, strength?: number, metadata?: Record<string, any>): boolean { const updates: string[] = []; const values: any[] = []; if (strength !== undefined) { updates.push('strength = ?'); values.push(strength); } if (metadata !== undefined) { updates.push('metadata = ?'); values.push(JSON.stringify(metadata)); } if (updates.length === 0) return true; values.push(id); const stmt = db.prepare(` UPDATE relationships SET ${updates.join(', ')} WHERE id = ? `); const result = stmt.run(...values); return result.changes > 0; } export function deleteRelationship(id: string): boolean { const stmt = db.prepare('DELETE FROM relationships WHERE id = ?'); const result = stmt.run(id); return result.changes > 0; } export function getConnectedMemories(memoryId: string, maxDepth: number = 2): Memory[] { const visited = new Set<string>([memoryId]); const queue: Array<{ id: string; depth: number }> = [{ id: memoryId, depth: 0 }]; const connectedIds = new Set<string>(); while (queue.length > 0) { const { id, depth } = queue.shift()!; if (depth >= maxDepth) continue; const relationships = getRelationships({ memoryId: id }); for (const rel of relationships) { const nextId = rel.from_memory_id === id ? rel.to_memory_id : rel.from_memory_id; if (!visited.has(nextId)) { visited.add(nextId); connectedIds.add(nextId); queue.push({ id: nextId, depth: depth + 1 }); } } } return Array.from(connectedIds) .map(id => getMemory(id)) .filter((memory): memory is Memory => memory !== null); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/dlasky/mcp-memory-vec'

If you have feedback or need assistance with the MCP directory API, please join our Discord server