diff --git a/docs/open-source/node-quickstart.mdx b/docs/open-source/node-quickstart.mdx index 1fc1abd7..39f7cbf7 100644 --- a/docs/open-source/node-quickstart.mdx +++ b/docs/open-source/node-quickstart.mdx @@ -301,6 +301,56 @@ await memory.deleteAll({ userId: "alice" }); await memory.reset(); // Reset all memories ``` +### History Store + +Mem0 TypeScript SDK support history stores to run on a serverless environment: + +We recommend using `Supabase` as a history store for serverless environments or disable history store to run on a serverless environment. + + +```typescript Supabase +import { Memory } from 'mem0ai/oss'; + +const memory = new Memory({ + historyStore: { + provider: 'supabase', + config: { + supabaseUrl: process.env.SUPABASE_URL || '', + supabaseKey: process.env.SUPABASE_KEY || '', + tableName: 'memory_history', + }, + }, +}); +``` + +```typescript Disable History +import { Memory } from 'mem0ai/oss'; + +const memory = new Memory({ + disableHistory: true, +}); +``` + + +Mem0 uses SQLite as a default history store. + +#### Create Memory History Table in Supabase + +You may need to create a memory history table in Supabase to store the history of memories. Use the following SQL command in `SQL Editor` on the Supabase project dashboard to create a memory history table: + +```sql +create table memory_history ( + id text primary key, + memory_id text not null, + previous_value text, + new_value text, + action text not null, + created_at timestamp with time zone default timezone('utc', now()), + updated_at timestamp with time zone, + is_deleted integer default 0 +); +``` + ## Configuration Parameters Mem0 offers extensive configuration options to customize its behavior according to your needs. These configurations span across different components like vector stores, language models, embedders, and graph stores. @@ -352,6 +402,14 @@ Mem0 offers extensive configuration options to customize its behavior according | `customPrompt` | Custom prompt for memory processing | None | + +| Parameter | Description | Default | +|------------------|--------------------------------------|----------------------------| +| `provider` | History store provider | "sqlite" | +| `config` | History store configuration | None (Defaults to SQLite) | +| `disableHistory` | Disable history store | false | + + ```typescript const config = { @@ -377,7 +435,15 @@ const config = { model: 'gpt-4-turbo-preview', }, }, - historyDbPath: 'memory.db', + historyStore: { + provider: 'supabase', + config: { + supabaseUrl: process.env.SUPABASE_URL || '', + supabaseKey: process.env.SUPABASE_KEY || '', + tableName: 'memories', + }, + }, + disableHistory: false, // This is false by default customPrompt: "I'm a virtual assistant. I'm here to help you with your queries.", } ``` diff --git a/mem0-ts/package.json b/mem0-ts/package.json index f3f89eb7..5f432bf8 100644 --- a/mem0-ts/package.json +++ b/mem0-ts/package.json @@ -1,6 +1,6 @@ { "name": "mem0ai", - "version": "2.1.9", + "version": "2.1.10", "description": "The Memory Layer For Your AI Apps", "main": "./dist/index.js", "module": "./dist/index.mjs", diff --git a/mem0-ts/src/oss/src/config/defaults.ts b/mem0-ts/src/oss/src/config/defaults.ts index e2606923..a855e5b3 100644 --- a/mem0-ts/src/oss/src/config/defaults.ts +++ b/mem0-ts/src/oss/src/config/defaults.ts @@ -1,6 +1,7 @@ import { MemoryConfig } from "../types"; export const DEFAULT_MEMORY_CONFIG: MemoryConfig = { + disableHistory: false, version: "v1.1", embedder: { provider: "openai", @@ -38,5 +39,10 @@ export const DEFAULT_MEMORY_CONFIG: MemoryConfig = { }, }, }, - historyDbPath: "memory.db", + historyStore: { + provider: "sqlite", + config: { + historyDbPath: "memory.db", + }, + }, }; diff --git a/mem0-ts/src/oss/src/config/manager.ts b/mem0-ts/src/oss/src/config/manager.ts index 6dc713aa..fa8daed7 100644 --- a/mem0-ts/src/oss/src/config/manager.ts +++ b/mem0-ts/src/oss/src/config/manager.ts @@ -51,6 +51,12 @@ export class ConfigManager { ...DEFAULT_MEMORY_CONFIG.graphStore, ...userConfig.graphStore, }, + historyStore: { + ...DEFAULT_MEMORY_CONFIG.historyStore, + ...userConfig.historyStore, + }, + disableHistory: + userConfig.disableHistory || DEFAULT_MEMORY_CONFIG.disableHistory, enableGraph: userConfig.enableGraph || DEFAULT_MEMORY_CONFIG.enableGraph, }; diff --git a/mem0-ts/src/oss/src/memory/index.ts b/mem0-ts/src/oss/src/memory/index.ts index a164798d..eaddaddc 100644 --- a/mem0-ts/src/oss/src/memory/index.ts +++ b/mem0-ts/src/oss/src/memory/index.ts @@ -12,6 +12,7 @@ import { EmbedderFactory, LLMFactory, VectorStoreFactory, + HistoryManagerFactory, } from "../utils/factory"; import { getFactRetrievalMessages, @@ -19,7 +20,7 @@ import { parseMessages, removeCodeBlocks, } from "../prompts"; -import { SQLiteManager } from "../storage"; +import { DummyHistoryManager } from "../storage/DummyHistoryManager"; import { Embedder } from "../embeddings/base"; import { LLM } from "../llms/base"; import { VectorStore } from "../vector_stores/base"; @@ -32,14 +33,14 @@ import { GetAllMemoryOptions, } from "./memory.types"; import { parse_vision_messages } from "../utils/memory"; - +import { HistoryManager } from "../storage/base"; export class Memory { private config: MemoryConfig; private customPrompt: string | undefined; private embedder: Embedder; private vectorStore: VectorStore; private llm: LLM; - private db: SQLiteManager; + private db: HistoryManager; private collectionName: string; private apiVersion: string; private graphMemory?: MemoryGraph; @@ -62,7 +63,25 @@ export class Memory { this.config.llm.provider, this.config.llm.config, ); - this.db = new SQLiteManager(this.config.historyDbPath || ":memory:"); + if (this.config.disableHistory) { + this.db = new DummyHistoryManager(); + } else { + const defaultConfig = { + provider: "sqlite", + config: { + historyDbPath: this.config.historyDbPath || ":memory:", + }, + }; + + this.db = + this.config.historyStore && !this.config.disableHistory + ? HistoryManagerFactory.create( + this.config.historyStore.provider, + this.config.historyStore, + ) + : HistoryManagerFactory.create("sqlite", defaultConfig); + } + this.collectionName = this.config.vectorStore.config.collectionName; this.apiVersion = this.config.version || "v1.0"; this.enableGraph = this.config.enableGraph || false; diff --git a/mem0-ts/src/oss/src/storage/DummyHistoryManager.ts b/mem0-ts/src/oss/src/storage/DummyHistoryManager.ts new file mode 100644 index 00000000..56bf7add --- /dev/null +++ b/mem0-ts/src/oss/src/storage/DummyHistoryManager.ts @@ -0,0 +1,27 @@ +export class DummyHistoryManager { + constructor() {} + + async addHistory( + memoryId: string, + previousValue: string | null, + newValue: string | null, + action: string, + createdAt?: string, + updatedAt?: string, + isDeleted: number = 0, + ): Promise { + return; + } + + async getHistory(memoryId: string): Promise { + return []; + } + + async reset(): Promise { + return; + } + + close(): void { + return; + } +} diff --git a/mem0-ts/src/oss/src/storage/MemoryHistoryManager.ts b/mem0-ts/src/oss/src/storage/MemoryHistoryManager.ts new file mode 100644 index 00000000..dfc321b6 --- /dev/null +++ b/mem0-ts/src/oss/src/storage/MemoryHistoryManager.ts @@ -0,0 +1,58 @@ +import { v4 as uuidv4 } from "uuid"; +import { HistoryManager } from "./base"; +interface HistoryEntry { + id: string; + memory_id: string; + previous_value: string | null; + new_value: string | null; + action: string; + created_at: string; + updated_at: string | null; + is_deleted: number; +} + +export class MemoryHistoryManager implements HistoryManager { + private memoryStore: Map = new Map(); + + async addHistory( + memoryId: string, + previousValue: string | null, + newValue: string | null, + action: string, + createdAt?: string, + updatedAt?: string, + isDeleted: number = 0, + ): Promise { + const historyEntry: HistoryEntry = { + id: uuidv4(), + memory_id: memoryId, + previous_value: previousValue, + new_value: newValue, + action: action, + created_at: createdAt || new Date().toISOString(), + updated_at: updatedAt || null, + is_deleted: isDeleted, + }; + + this.memoryStore.set(historyEntry.id, historyEntry); + } + + async getHistory(memoryId: string): Promise { + return Array.from(this.memoryStore.values()) + .filter((entry) => entry.memory_id === memoryId) + .sort( + (a, b) => + new Date(b.created_at).getTime() - new Date(a.created_at).getTime(), + ) + .slice(0, 100); + } + + async reset(): Promise { + this.memoryStore.clear(); + } + + close(): void { + // No need to close anything for in-memory storage + return; + } +} diff --git a/mem0-ts/src/oss/src/storage/SQLiteManager.ts b/mem0-ts/src/oss/src/storage/SQLiteManager.ts index ef268f93..32143b37 100644 --- a/mem0-ts/src/oss/src/storage/SQLiteManager.ts +++ b/mem0-ts/src/oss/src/storage/SQLiteManager.ts @@ -1,7 +1,7 @@ import sqlite3 from "sqlite3"; -import { promisify } from "util"; +import { HistoryManager } from "./base"; -export class SQLiteManager { +export class SQLiteManager implements HistoryManager { private db: sqlite3.Database; constructor(dbPath: string) { diff --git a/mem0-ts/src/oss/src/storage/SupabaseHistoryManager.ts b/mem0-ts/src/oss/src/storage/SupabaseHistoryManager.ts new file mode 100644 index 00000000..d8cf0e4c --- /dev/null +++ b/mem0-ts/src/oss/src/storage/SupabaseHistoryManager.ts @@ -0,0 +1,121 @@ +import { createClient, SupabaseClient } from "@supabase/supabase-js"; +import { v4 as uuidv4 } from "uuid"; +import { HistoryManager } from "./base"; + +interface HistoryEntry { + id: string; + memory_id: string; + previous_value: string | null; + new_value: string | null; + action: string; + created_at: string; + updated_at: string | null; + is_deleted: number; +} + +interface SupabaseHistoryConfig { + supabaseUrl: string; + supabaseKey: string; + tableName?: string; +} + +export class SupabaseHistoryManager implements HistoryManager { + private supabase: SupabaseClient; + private readonly tableName: string; + + constructor(config: SupabaseHistoryConfig) { + this.tableName = config.tableName || "memory_history"; + this.supabase = createClient(config.supabaseUrl, config.supabaseKey); + this.initializeSupabase().catch(console.error); + } + + private async initializeSupabase(): Promise { + // Check if table exists + const { error } = await this.supabase + .from(this.tableName) + .select("id") + .limit(1); + + if (error) { + console.error( + "Error: Table does not exist. Please run this SQL in your Supabase SQL Editor:", + ); + console.error(` +create table ${this.tableName} ( + id text primary key, + memory_id text not null, + previous_value text, + new_value text, + action text not null, + created_at timestamp with time zone default timezone('utc', now()), + updated_at timestamp with time zone, + is_deleted integer default 0 +); + `); + throw error; + } + } + + async addHistory( + memoryId: string, + previousValue: string | null, + newValue: string | null, + action: string, + createdAt?: string, + updatedAt?: string, + isDeleted: number = 0, + ): Promise { + const historyEntry: HistoryEntry = { + id: uuidv4(), + memory_id: memoryId, + previous_value: previousValue, + new_value: newValue, + action: action, + created_at: createdAt || new Date().toISOString(), + updated_at: updatedAt || null, + is_deleted: isDeleted, + }; + + const { error } = await this.supabase + .from(this.tableName) + .insert(historyEntry); + + if (error) { + console.error("Error adding history to Supabase:", error); + throw error; + } + } + + async getHistory(memoryId: string): Promise { + const { data, error } = await this.supabase + .from(this.tableName) + .select("*") + .eq("memory_id", memoryId) + .order("created_at", { ascending: false }) + .limit(100); + + if (error) { + console.error("Error getting history from Supabase:", error); + throw error; + } + + return data || []; + } + + async reset(): Promise { + const { error } = await this.supabase + .from(this.tableName) + .delete() + .neq("id", ""); + + if (error) { + console.error("Error resetting Supabase history:", error); + throw error; + } + } + + close(): void { + // No need to close anything as connections are handled by the client + return; + } +} diff --git a/mem0-ts/src/oss/src/storage/base.ts b/mem0-ts/src/oss/src/storage/base.ts new file mode 100644 index 00000000..786b7a2c --- /dev/null +++ b/mem0-ts/src/oss/src/storage/base.ts @@ -0,0 +1,14 @@ +export interface HistoryManager { + addHistory( + memoryId: string, + previousValue: string | null, + newValue: string | null, + action: string, + createdAt?: string, + updatedAt?: string, + isDeleted?: number, + ): Promise; + getHistory(memoryId: string): Promise; + reset(): Promise; + close(): void; +} diff --git a/mem0-ts/src/oss/src/storage/index.ts b/mem0-ts/src/oss/src/storage/index.ts index c6de412d..59324401 100644 --- a/mem0-ts/src/oss/src/storage/index.ts +++ b/mem0-ts/src/oss/src/storage/index.ts @@ -1 +1,5 @@ export * from "./SQLiteManager"; +export * from "./DummyHistoryManager"; +export * from "./SupabaseHistoryManager"; +export * from "./MemoryHistoryManager"; +export * from "./base"; diff --git a/mem0-ts/src/oss/src/types/index.ts b/mem0-ts/src/oss/src/types/index.ts index 62d2d473..9bd356bf 100644 --- a/mem0-ts/src/oss/src/types/index.ts +++ b/mem0-ts/src/oss/src/types/index.ts @@ -24,6 +24,16 @@ export interface VectorStoreConfig { [key: string]: any; } +export interface HistoryStoreConfig { + provider: string; + config: { + historyDbPath?: string; + supabaseUrl?: string; + supabaseKey?: string; + tableName?: string; + }; +} + export interface LLMConfig { provider?: string; config?: Record; @@ -58,6 +68,8 @@ export interface MemoryConfig { provider: string; config: LLMConfig; }; + historyStore?: HistoryStoreConfig; + disableHistory?: boolean; historyDbPath?: string; customPrompt?: string; graphStore?: GraphStoreConfig; @@ -137,4 +149,11 @@ export const MemoryConfigSchema = z.object({ customPrompt: z.string().optional(), }) .optional(), + historyStore: z + .object({ + provider: z.string(), + config: z.record(z.string(), z.any()), + }) + .optional(), + disableHistory: z.boolean().optional(), }); diff --git a/mem0-ts/src/oss/src/utils/factory.ts b/mem0-ts/src/oss/src/utils/factory.ts index 5fb7caf8..62cfc78a 100644 --- a/mem0-ts/src/oss/src/utils/factory.ts +++ b/mem0-ts/src/oss/src/utils/factory.ts @@ -5,7 +5,12 @@ import { OpenAIStructuredLLM } from "../llms/openai_structured"; import { AnthropicLLM } from "../llms/anthropic"; import { GroqLLM } from "../llms/groq"; import { MemoryVectorStore } from "../vector_stores/memory"; -import { EmbeddingConfig, LLMConfig, VectorStoreConfig } from "../types"; +import { + EmbeddingConfig, + HistoryStoreConfig, + LLMConfig, + VectorStoreConfig, +} from "../types"; import { Embedder } from "../embeddings/base"; import { LLM } from "../llms/base"; import { VectorStore } from "../vector_stores/base"; @@ -13,6 +18,10 @@ import { Qdrant } from "../vector_stores/qdrant"; import { RedisDB } from "../vector_stores/redis"; import { OllamaLLM } from "../llms/ollama"; import { SupabaseDB } from "../vector_stores/supabase"; +import { SQLiteManager } from "../storage/SQLiteManager"; +import { MemoryHistoryManager } from "../storage/MemoryHistoryManager"; +import { SupabaseHistoryManager } from "../storage/SupabaseHistoryManager"; +import { HistoryManager } from "../storage/base"; export class EmbedderFactory { static create(provider: string, config: EmbeddingConfig): Embedder { @@ -62,3 +71,22 @@ export class VectorStoreFactory { } } } + +export class HistoryManagerFactory { + static create(provider: string, config: HistoryStoreConfig): HistoryManager { + switch (provider.toLowerCase()) { + case "sqlite": + return new SQLiteManager(config.config.historyDbPath || ":memory:"); + case "supabase": + return new SupabaseHistoryManager({ + supabaseUrl: config.config.supabaseUrl || "", + supabaseKey: config.config.supabaseKey || "", + tableName: config.config.tableName || "memory_history", + }); + case "memory": + return new MemoryHistoryManager(); + default: + throw new Error(`Unsupported history store provider: ${provider}`); + } + } +} diff --git a/mem0-ts/src/oss/src/vector_stores/supabase.ts b/mem0-ts/src/oss/src/vector_stores/supabase.ts index fc05d302..f61beb8b 100644 --- a/mem0-ts/src/oss/src/vector_stores/supabase.ts +++ b/mem0-ts/src/oss/src/vector_stores/supabase.ts @@ -97,6 +97,11 @@ export class SupabaseDB implements VectorStore { try { // Verify table exists and vector operations work by attempting a test insert const testVector = Array(1536).fill(0); + try { + await this.client.from(this.tableName).delete().eq("id", "test_vector"); + } catch (error) { + console.warn("No test vector to delete, safe to ignore."); + } const { error: testError } = await this.client .from(this.tableName) .insert({ @@ -113,6 +118,50 @@ export class SupabaseDB implements VectorStore { 1. The vector extension is enabled 2. The table "${this.tableName}" exists with correct schema 3. The match_vectors function is created + +RUN THE FOLLOWING SQL IN YOUR SUPABASE SQL EDITOR: + +-- Enable the vector extension +create extension if not exists vector; + +-- Create the memories table +create table if not exists memories ( + id text primary key, + embedding vector(1536), + metadata jsonb, + created_at timestamp with time zone default timezone('utc', now()), + updated_at timestamp with time zone default timezone('utc', now()) +); + +-- Create the vector similarity search function +create or replace function match_vectors( + query_embedding vector(1536), + match_count int, + filter jsonb default '{}'::jsonb +) +returns table ( + id text, + similarity float, + metadata jsonb +) +language plpgsql +as $$ +begin + return query + select + id, + similarity, + metadata + from memories + where case + when filter::text = '{}'::text then true + else metadata @> filter + end + order by embedding <=> query_embedding + limit match_count; +end; +$$; + See the SQL migration instructions in the code comments.`, ); }