LangMem with Django and PostgreSQL

Configure the database

Create an admin user and database named langmem. (See PostgreSQL.)

Log into the langmem database with the admin user.

psql -U admin -d langmem

Create a migrations database user. This user will CRUD tables for database migrations.

-- Create the user with a password
CREATE USER langmem_migrations WITH ENCRYPTED PASSWORD 'secure_password';
-- Grant database connection and schema access
GRANT CONNECT ON DATABASE langmem TO langmem_migrations;
GRANT USAGE, CREATE ON SCHEMA public TO langmem_migrations;

-- Grant CRUD on existing tables (optional, for data manipulation during migrations)
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO langmem_migrations;

-- Automatically grant CRUD on future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO langmem_migrations;

Create a service account database user. This user will access the database as the application runs, updating records.

-- Create the user with a password
CREATE USER langmem_app WITH ENCRYPTED PASSWORD 'secure_password';
-- Grant database connection and schema access
GRANT CONNECT ON DATABASE langmem TO langmem_app;
GRANT USAGE ON SCHEMA public TO langmem_app;

-- Grant CRUD on existing tables
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO langmem_app;

-- Automatically grant CRUD on future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO langmem_app;

Create database migration script

Add LangMem and PostgreSQL dependencies.

poetry add pgconnstr langmem langgraph-checkpoint-postgres 

Create the migration script.

from decouple import config
from langgraph.store.postgres import PostgresStore
from pgconnstr import ConnectionString

DB_HOST = config("DB_HOST", default="localhost")
DB_NAME = config("DB_NAME")
DB_PORT = config("DB_PORT", default=5432, cast=int)
DB_USER = config("DB_USER")
DB_PASSWORD = config("DB_PASSWORD")

CONN = ConnectionString(
    host=DB_HOST,
    dbname=DB_NAME,
    port=DB_PORT,
    user=DB_USER,
    password=DB_PASSWORD,
)


def migrate_db():
    """Run migrations to set up the memory table."""
    with PostgresStore.from_conn_string(str(CONN)) as store:
        store.setup()


def main():
    migrate_db()


if __name__ == "__main__":
    main()

After running, LangMem will create these tables in the database:

  • store
  • store_migrations

Create Django webapp

Navigate to the current package (within a mono-repo).

cd packages/sai_langmem

Add Django.

poetry add django

Create the web app.

poetry run django-admin startproject langmem_django src

Change the database connection settings in settings.py.

code packages/sai_langmem/src/langmem_django/settings.py
DB_HOST = config("HOST", default="localhost")
DB_NAME = config("DB_NAME")
DB_PORT = config("DB_PORT", default=5432, cast=int)
DB_USER = config("DB_USER")
DB_PASSWORD = config("DB_PASSWORD")

POSTGRES = {
    "ENGINE": "django.db.backends.postgresql",
    "NAME": DB_NAME,
    "DB_USER": DB_USER,
    "DB_PASSWORD": DB_PASSWORD,
    "HOST": DB_HOST,
    "DB_PORT": DB_PORT,
}

DATABASES = {
    "default": POSTGRES,
    "sqlite": {
        "ENGINE": "django.db.backends.sqlite3",
        "NAME": BASE_DIR / "db.sqlite3",
    },
}

Migrate the database. Django will create many tables, including auth_group, auth_user, and others.

poetry run src/manage.py migrate

Run the web app server.

poetry run src/manage.py runserver

Create Django admin user

Poetry shell

Enable the Poetry shell to type commands without poetry run...

poetry shell

Navigate the web app source directory.

cd src

Create an admin user.

python manage.py createsuperuser

Create Django app

Django webapp vs. app

A Django webapp is the whole project. Django apps are installable components of a web app.

python manage.py startapp langmem

Replace the singular models.py file with models/__init__.py. Put your models here.

store.py

from django.db import models
from django.contrib import admin

class Store(models.Model):
    prefix = models.TextField()
    key = models.TextField()
    value = models.JSONField()
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()
    expires_at = models.DateTimeField(null=True, blank=True)
    ttl_minutes = models.IntegerField(null=True, blank=True)

    class Meta:
        # Map to the existing 'store' table
        db_table = 'store'
        # Disable migrations for this model
        managed = False

    def __str__(self):
        return f"{self.prefix} - {self.key}"

# Register the model in the Django admin app
@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
    list_display = ('prefix', 'key', 'created_at', 'updated_at', 'expires_at', 'ttl_minutes')
    search_fields = ('prefix', 'key')

store_migrations.py

from django.db import models


class StoreMigration(models.Model):
    v = models.IntegerField(primary_key=True)

    class Meta:
        db_table = "store_migrations"
        managed = False
        verbose_name = "store migration"
        verbose_name_plural = "store migrations"

    def __str__(self):
        return f"Migration version {self.v}"

Update the admin.py file.

admin.py

from django.contrib import admin

from .models import Store, StoreMigration


@admin.register(Store)
class StoreAdmin(admin.ModelAdmin):
    list_display = (
        "prefix",
        "key",
        "created_at",
        "updated_at",
        "expires_at",
        "ttl_minutes",
    )
    search_fields = ("prefix", "key")

    # Make all fields read-only. Only LangMem will update the database.
    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False


@admin.register(StoreMigration)
class StoreMigrationAdmin(admin.ModelAdmin):
    list_display = ("v",)

    # Make all fields read-only
    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

Run the Django database migrations

Info

Even though the models are not managed by Django (managed = False), Django migrations still sees them.

Make the database migrations.

python manage.py makemigrations

Migrate the tables.

python manage.py migrate

Resources

LangMem with Django and PostgreSQL
Interactive graph
On this page
Configure the database
Create database migration script
Create Django webapp
Create Django admin user
Create Django app
Run the Django database migrations
Resources