Chat History & MemoryLesson 5

PostgreSQL Chat Message History

After Redis, this lesson stores turns in PostgreSQL instead. RunnableWithMessageHistory and get_session_history stay the same — return PostgresChatMessageHistory from langchain_postgres. Connect with psycopg, run create_tables once, then pass the connection into each history instance.

Why SQL?

Rows stay after a restart and you can inspect them with standard SQL — useful when Redis TTL or in-memory storage is not enough.

Redis or in-memory

History lost on restart
(Redis keeps data, but TTL expires)

PostgreSQL

Rows in chat_history table
Survives restarts & deploys
Query / audit with SQL
Same RunnableWithMessageHistory wrapper — only get_session_history changes to return PostgresChatMessageHistory.

Install packages

Activate the venv from Project Setup, then install the PostgreSQL integration and driver. You also need a PostgreSQL server — see PostgreSQL install docs or run Docker: docker run -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres.

pip install langchain-postgres psycopg[binary]

Connect and create tables

Set POSTGRES_URL in your .env file. The demo defaults to postgresql://postgres:postgres@localhost:5432/chatdb. Run create_tables once to ensure the chat_history table exists.

import os
import psycopg
from langchain_postgres import PostgresChatMessageHistory

POSTGRES_URL = os.getenv(
    "POSTGRES_URL",
    "postgresql://postgres:postgres@localhost:5432/chatdb",
)
TABLE_NAME = "chat_history"

conn = psycopg.connect(POSTGRES_URL)
PostgresChatMessageHistory.create_tables(conn, TABLE_NAME)

chat_history table (created by create_tables)

session_id  | message (JSON)     | created_at
------------|--------------------|-----------
demo-1      | {"type":"human",…} | 2026-06-09
demo-1      | {"type":"ai",…}    | 2026-06-09
demo-2      | {"type":"human",…} | 2026-06-09
Each session_id gets its own rows. Query the table directly when you need to verify what was stored.

Each invoke

Connect and ensure the table exists once at startup. On each invoke, the wrapper reads rows for session_id, runs the chain, then inserts the new turn.

Steps inside one invoke (session_id=demo-1):

connect

psycopg.connect(POSTGRES_URL)

schema

create_tables(conn, "chat_history")

load

SELECT rows for session_id

chain

prompt | model

save

INSERT human + reply rows

Re-run the script with the same session_id — prior turns load from the table on the next invoke.

get_session_history

Return a PostgresChatMessageHistory instance for each session_id. The wrapper calls this on every invoke — same interface as in-memory or Redis backends.

def get_session_history(session_id: str):
    return PostgresChatMessageHistory(
        TABLE_NAME,
        session_id,
        sync_connection=conn,
    )

Wrap and invoke

Same wrapper and invoke call as RunnableWithMessageHistory (In-Memory) — only get_session_history changed.

chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history",
)
config = {"configurable": {"session_id": "demo-1"}}

reply = chain_with_history.invoke(
    {"input": question},
    config=config,
)

The demo script

postgresql_chat_message_history_demo.py runs the same two HTML <a> questions with session_id="demo-1" stored in PostgreSQL.

postgresql_chat_message_history_demo.py
"""postgresql_chat_message_history_demo.py"""
psycopg.connect · create_tables
PostgresChatMessageHistory(TABLE_NAME, session_id, …)
RunnableWithMessageHistory(chain, get_session_history, …)

Download the code

postgresql_chat_message_history_demo.py

Two HTML questions — PostgreSQL backend

Download .py
Requires a running PostgreSQL instance (install PostgreSQL) and POSTGRES_URL in .env. Venv from Project Setup.

Run it

Start PostgreSQL locally (Docker: docker run -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres), create the chatdb database, then:

python postgresql_chat_message_history_demo.py
PowerShell — (.venv) active
(.venv) PS C:\projects\langchain-course> python postgresql_chat_message_history_demo.py
Human: What does the HTML <a> tag do?
AI: The <a> tag creates a hyperlink to another page or resource.
Human: What attribute opens the link in a new tab?
AI: Use target="_blank" on the <a> tag.
History stored in PostgreSQL table 'chat_history'
The second answer works because both invokes use the same session_id and rows persist in PostgreSQL.

Quick reference

  • PostgresChatMessageHistory — from langchain_postgres; implements the same interface as InMemoryChatMessageHistory.
  • create_tables(conn, table_name) — run once before first use; safe to call on every startup.
  • POSTGRES_URL — connection string; override in .env for production.
  • sync_connection=conn — pass the open psycopg connection to each history instance.
  • Install: pip install langchain-postgres psycopg[binary].
  • Docs: PostgresChatMessageHistory · RunnableWithMessageHistory · Message history how-to · psycopg3 · PostgreSQL installation.

What's Next

Next: MongoDB Chat Message History — store turns in a document database instead of SQL.