npm package: @langgraph-js/memory
Description: A memory management system based on PostgreSQL + pgvector for LangGraph workflows
Latest version: 1.4.1

# LangGraph-Memory

A memory management system based on PostgreSQL + pgvector, designed for memory storage and retrieval in LangGraph
workflows.

## Features

-   **Vector Similarity Search**: Support for HNSW and IVFFlat indexes for efficient vector similarity search
-   **LLM-Powered Memory Management**: Intelligent memory merging and deduplication using large language models
-   **Multi-dimensional Filtering**: Advanced querying capabilities with multiple filter options
-   **Complete CRUD Operations**: Full create, read, update, and delete operations for memory items
-   **LangChain Integration**: Seamless integration with LangChain's embedding and LLM capabilities, including
    `LangChainEmbedder` wrapper
-   **Extensible Embedder Interface**: Support for custom embedding providers beyond LangChain
-   **Memory Expiration**: Support for time-based memory expiration
-   **Immutable Memories**: Option to mark memories as read-only
-   **Metadata Support**: Flexible metadata storage for enhanced memory organization
-   **Multi-tenant Support**: Organization-based isolation for enterprise use cases

## Prerequisites

-   Node.js 18+
-   PostgreSQL 15+ with pgvector extension
-   OpenAI API Key (required for testing and examples)

## Installation and Setup

### 1. Install Dependencies

```bash
pnpm install
```

### 2. Start PostgreSQL with pgvector

```bash
docker-compose up -d
```

### 3. Create Test Database

```bash
createdb langgraph_memory_test
```

### 4. Set Environment Variables

```bash
export OPENAI_API_KEY="your-openai-api-key-here"
```

## Testing

Run the complete test suite (requires internet connection and OpenAI API):

```bash
pnpm test
```

Run a specific test file:

```bash
pnpm test memory-database.test.ts
```

## Usage Examples

### Basic Setup

```typescript
import { MemoryDataBase } from './src/MemoryDatabase';
import { PostgresVectorStore } from './src/vector-store/pg';
import { ChatOpenAI, OpenAIEmbeddings } from '@langchain/openai';
import { Pool } from 'pg';

// Initialize database connection
const pool = new Pool({
    host: 'localhost',
    port: 5432,
    user: 'postgres',
    password: 'postgres',
    database: 'langgraph_memory',
});

// Initialize vector store
const vectorStore = new PostgresVectorStore({
    pool,
    tableName: 'memory_vectors',
    dimension: 1536, // text-embedding-3-small dimension
});

// Create embedder (direct implementation)
const embedder = {
    embed: async (text: string) => {
        const openaiEmbedder = new OpenAIEmbeddings({
            modelName: 'text-embedding-3-small',
        });
        return await openaiEmbedder.embedQuery(text);
    },
    embedBatch: async (texts: string[]) => {
        const openaiEmbedder = new OpenAIEmbeddings({
            modelName: 'text-embedding-3-small',
        });
        const embeddings = await openaiEmbedder.embedDocuments(texts);
        return embeddings.map((embedding, index) => ({
            embedding,
            original: texts[index],
        }));
    },
};

// Initialize memory database
const memoryDB = new MemoryDataBase('your-org-id', new ChatOpenAI({ modelName: 'gpt-4o-mini' }), embedder, vectorStore);

// Setup database schema
await vectorStore.initialize();
```

### Adding Memories

```typescript
import { HumanMessage, AIMessage } from '@langchain/core/messages';

// Add conversation memories
// Note: At least one of userId, agentId, or runId is required
const messages = [
    new HumanMessage('What is TypeScript?'),
    new AIMessage('TypeScript is a programming language developed by Microsoft...'),
];

const result = await memoryDB.add(messages, {
    userId: 'user123',
    agentId: 'agent456',
    metadata: {
        topic: 'programming',
        language: 'typescript',
    },
});

console.log('Added memories:', result.results.length);
```

### Searching Memories

```typescript
// Search with text query
// Note: At least one of userId, agentId, or runId is required
const searchResult = await memoryDB.search('programming languages', {
    userId: 'user123',
    limit: 5,
    filters: {
        categories: 'technical', // Filter by category
        createdAtAfter: '2024-01-01T00:00:00Z', // Filter by creation time
    },
});

console.log('Search results:', searchResult.results);
```

### Advanced Filtering

The memory system supports comprehensive filtering capabilities:

```typescript
// Get memories with multiple filters
const filteredMemories = await memoryDB.getAll({
    userId: 'user123',
    categories: ['hobby', 'work'], // Must contain both categories (AND operation)
    createdAtBefore: new Date().toISOString(),
    createdAtAfter: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), // Last 7 days
    limit: 20,
});

// Search with category filter
const hobbyMemories = await memoryDB.search('interests', {
    userId: 'user123',
    filters: {
        categories: 'hobby', // Single category filter
    },
    limit: 10,
});

// Filter by time ranges
const recentMemories = await memoryDB.getAll({
    userId: 'user123',
    updatedAtBefore: new Date().toISOString(),
    updatedAtAfter: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), // Last 24 hours
});

// Filter by expiration date
const expiringMemories = await memoryDB.getAll({
    userId: 'user123',
    expirationDateBefore: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(), // Expires within 7 days
});
```

### Memory Management

```typescript
// Get a specific memory
const memory = await memoryDB.get('memory-id-123');

// Update memory content
await memoryDB.update('memory-id-123', 'Updated memory content');

// Delete a specific memory
await memoryDB.delete('memory-id-123');

// Delete all memories for a user
await memoryDB.deleteAll({
    userId: 'user123',
});

// Delete memories by category
await memoryDB.deleteAll({
    userId: 'user123',
    categories: 'temporary',
});

// Delete memories by time range
await memoryDB.deleteAll({
    userId: 'user123',
    createdAtBefore: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), // Older than 30 days
});

// Delete expired memories
await memoryDB.deleteAll({
    expirationDateBefore: new Date().toISOString(),
});

// Get all memories with pagination
const allMemories = await memoryDB.getAll({
    userId: 'user123',
    limit: 10,
});
```

## API Reference

### PostgresVectorStore Class

```typescript
constructor(config: {
    pool: Pool;
    tableName?: string;
    dimension?: number;
})
```

**Parameters:**

-   `pool`: PostgreSQL connection pool
-   `tableName`: Table name for vector storage (default: 'memories')
-   `dimension`: Vector dimension (default: 1536)

#### Methods

-   `initialize(): Promise<void>` - Create tables and indexes
-   `insert(id, orgId, memory, embedding, metadata?): Promise<void>` - Insert vector data
-   `search(queryEmbedding, config): Promise<VectorSearchResult[]>` - Search similar vectors
-   `delete(id): Promise<void>` - Delete vector by ID
-   `reset(): Promise<void>` - Clear all data
-   `close(): Promise<void>` - Close connections

### Embedder Interface

```typescript
interface Embedder {
    embed(text: string): Promise<number[]>;
    embedBatch(texts: string[]): Promise<
        {
            embedding: number[];
            original: string;
        }[]
    >;
}
```

The embedder interface provides text-to-vector conversion methods. You can implement this interface with any embedding
provider (OpenAI, HuggingFace, etc.).

#### Direct Implementation

```typescript
const embedder: Embedder = {
    embed: async (text: string) => {
        const openaiEmbedder = new OpenAIEmbeddings({
            modelName: 'text-embedding-3-small',
        });
        return await openaiEmbedder.embedQuery(text);
    },
    embedBatch: async (texts: string[]) => {
        const openaiEmbedder = new OpenAIEmbeddings({
            modelName: 'text-embedding-3-small',
        });
        const embeddings = await openaiEmbedder.embedDocuments(texts);
        return embeddings.map((embedding, index) => ({
            embedding,
            original: texts[index],
        }));
    },
};
```

#### Custom Embedder Implementation

If you need to use a different embedding provider, you can implement the `Embedder` interface directly:

```typescript
class CustomEmbedder implements Embedder {
    async embed(text: string): Promise<number[]> {
        // Your custom embedding logic
        return [
            /* embedding vector */
        ];
    }

    async embedBatch(texts: string[]): Promise<{ embedding: number[]; original: string }[]> {
        // Your custom batch embedding logic
        return texts.map((text) => ({
            embedding: [
                /* embedding vector */
            ],
            original: text,
        }));
    }
}

const embedder = new CustomEmbedder();
```

### MemoryDataBase Class

#### Constructor

```typescript
constructor(
    org_id: string,
    llm: BaseChatModel,
    embedder: Embedder,
    vectorStore: PostgresVectorStore,
    customPrompt?: string
)
```

**Parameters:**

-   `org_id`: Organization identifier for multi-tenant support
-   `llm`: Language model for memory processing and deduplication
-   `embedder`: Embedder implementation with `embed` and `embedBatch` methods
-   `vectorStore`: PostgreSQL vector store instance
-   `customPrompt`: Optional custom prompt for memory extraction

#### Methods

-   `setup(): Promise<void>` - Initialize database schema and indexes
-   `add(messages, config): Promise<SearchResult>` - Add new memories from conversation messages
-   `get(memoryId: string): Promise<MemoryItem | null>` - Retrieve a specific memory
-   `search(query: string, config): Promise<SearchResult>` - Search memories by text similarity
-   `update(memoryId: string, data: string): Promise<{ message: string }>` - Update memory content
-   `delete(memoryId: string): Promise<{ message: string }>` - Delete a specific memory
-   `deleteAll(config: DeleteAllMemoryOptions): Promise<{ message: string }>` - Delete all memories matching filters
-   `reset(): Promise<void>` - Reset the entire memory database
-   `getAll(config: GetAllMemoryOptions): Promise<SearchResult>` - Get all memories with optional filtering

### MemoryItem Interface

```typescript
interface MemoryItem {
    id: string;
    org_id: string;
    agent_id?: string;
    user_id?: string;
    app_id?: string;
    run_id?: string;
    immutable?: boolean;
    memory: string;
    categories?: string[];
    metadata?: Record<string, any>;
    score?: number;
    updated_at: string;
    created_at: string;
    expiration_date?: string;
}
```

### Configuration Options

#### MemoryFilters Interface

```typescript
interface MemoryFilters extends IdSet {
    categories?: string[] | string; // Single category or array of categories
    createdAtBefore?: string; // ISO date string
    createdAtAfter?: string; // ISO date string
    updatedAtBefore?: string; // ISO date string
    updatedAtAfter?: string; // ISO date string
    expirationDateBefore?: string; // ISO date string
    expirationDateAfter?: string; // ISO date string
    [key: string]: any; // Additional custom filters
}
```

#### Add Configuration

```typescript
interface AddConfig extends IdSet {
    metadata?: Record<string, any>; // Additional metadata to store
    filters?: MemoryFilters; // Filters for the operation
    infer?: boolean; // Whether to infer categories (default: true)
}
```

#### Search Configuration

```typescript
interface SearchConfig extends IdSet {
    limit?: number; // Maximum number of results (default: 100)
    filters?: MemoryFilters; // Additional filters to apply
}
```

#### GetAll Configuration

```typescript
interface GetAllMemoryOptions extends MemoryFilters {
    limit?: number; // Maximum number of results (default: 100)
}
```

#### DeleteAll Configuration

```typescript
interface DeleteAllMemoryOptions extends MemoryFilters {
    // Same as MemoryFilters - at least one filter is required
}
```

## Advanced Features

### Multi-tenant Organization Isolation

The memory system provides complete organization-based isolation, ensuring that different organizations cannot access
each other's memories:

```typescript
// Create separate memory databases for different organizations
const orgAMemoryDB = new MemoryDataBase('org-a', llm, embedder, vectorStore);
const orgBMemoryDB = new MemoryDataBase('org-b', llm, embedder, vectorStore);

// Add memories for different organizations
await orgAMemoryDB.add([new HumanMessage('Organization A data')], { userId: 'user1' });
await orgBMemoryDB.add([new HumanMessage('Organization B data')], { userId: 'user1' });

// Each organization can only access their own data
const orgAData = await orgAMemoryDB.getAll({ userId: 'user1' }); // Only sees org-a data
const orgBData = await orgBMemoryDB.getAll({ userId: 'user1' }); // Only sees org-b data

// Reset only affects the current organization
await orgAMemoryDB.reset(); // Only clears org-a data
```

### Memory Merging and Deduplication

The system uses LLM to intelligently merge and deduplicate memories when adding new content. This prevents duplicate
information while preserving important context.

### Custom Prompts

You can customize the behavior of memory extraction and merging by providing custom prompts:

```typescript
const memoryDB = new MemoryDataBase(
    'your-org-id',
    llm,
    embedder,
    vectorStore,
    'Your custom prompt for memory processing...',
);
```

### Vector Store Configuration

The vector store supports various configuration options for performance optimization:

```typescript
const vectorStore = new PostgresVectorStore(pool, {
    tableName: 'memory_vectors',
    dimension: 1536,
    indexType: 'hnsw', // 'hnsw' or 'ivfflat'
    hnswM: 16, // HNSW parameter
    hnswEfConstruction: 64, // HNSW parameter
    ivfflatLists: 100, // IVFFlat parameter
});
```

## Performance Considerations

-   **Indexing**: Choose appropriate vector indexes based on your data size and query patterns
-   **Batch Operations**: Use batch embedding for better performance when adding multiple memories
-   **Connection Pooling**: Configure PostgreSQL connection pooling for production use
-   **Memory Expiration**: Regularly clean up expired memories to maintain optimal performance

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Ensure all tests pass
6. Submit a pull request

## Acknowledgments

This project draws inspiration from the [mem0](https://github.com/mem0ai/mem0) project's source code.

## License

Apache License 2.0