@langgraph-js/memory
v1.4.1
Published
A memory management system based on PostgreSQL + pgvector for LangGraph workflows
Downloads
15
Maintainers
Readme
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
LangChainEmbedderwrapper - 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
pnpm install2. Start PostgreSQL with pgvector
docker-compose up -d3. Create Test Database
createdb langgraph_memory_test4. Set Environment Variables
export OPENAI_API_KEY="your-openai-api-key-here"Testing
Run the complete test suite (requires internet connection and OpenAI API):
pnpm testRun a specific test file:
pnpm test memory-database.test.tsUsage Examples
Basic Setup
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
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
// 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:
// 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
// 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
constructor(config: {
pool: Pool;
tableName?: string;
dimension?: number;
})Parameters:
pool: PostgreSQL connection pooltableName: Table name for vector storage (default: 'memories')dimension: Vector dimension (default: 1536)
Methods
initialize(): Promise<void>- Create tables and indexesinsert(id, orgId, memory, embedding, metadata?): Promise<void>- Insert vector datasearch(queryEmbedding, config): Promise<VectorSearchResult[]>- Search similar vectorsdelete(id): Promise<void>- Delete vector by IDreset(): Promise<void>- Clear all dataclose(): Promise<void>- Close connections
Embedder Interface
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
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:
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
constructor(
org_id: string,
llm: BaseChatModel,
embedder: Embedder,
vectorStore: PostgresVectorStore,
customPrompt?: string
)Parameters:
org_id: Organization identifier for multi-tenant supportllm: Language model for memory processing and deduplicationembedder: Embedder implementation withembedandembedBatchmethodsvectorStore: PostgreSQL vector store instancecustomPrompt: Optional custom prompt for memory extraction
Methods
setup(): Promise<void>- Initialize database schema and indexesadd(messages, config): Promise<SearchResult>- Add new memories from conversation messagesget(memoryId: string): Promise<MemoryItem | null>- Retrieve a specific memorysearch(query: string, config): Promise<SearchResult>- Search memories by text similarityupdate(memoryId: string, data: string): Promise<{ message: string }>- Update memory contentdelete(memoryId: string): Promise<{ message: string }>- Delete a specific memorydeleteAll(config: DeleteAllMemoryOptions): Promise<{ message: string }>- Delete all memories matching filtersreset(): Promise<void>- Reset the entire memory databasegetAll(config: GetAllMemoryOptions): Promise<SearchResult>- Get all memories with optional filtering
MemoryItem Interface
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
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
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
interface SearchConfig extends IdSet {
limit?: number; // Maximum number of results (default: 100)
filters?: MemoryFilters; // Additional filters to apply
}GetAll Configuration
interface GetAllMemoryOptions extends MemoryFilters {
limit?: number; // Maximum number of results (default: 100)
}DeleteAll Configuration
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:
// 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 dataMemory 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:
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:
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
- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
Acknowledgments
This project draws inspiration from the mem0 project's source code.
License
Apache License 2.0
