LangChain Mastery

Course Content

LangChain Mastery

7 sections · 109 lessons

Write a function to store conversation history in a database.


What you need to know

Why a database

In-memory history lives in one process. With several workers behind a load balancer, turn 2 may land on a different worker that has no history, and every deploy or crash erases everything. A shared database fixes both.

Option A: checkpointer for agents and graphs

Python
from langchain.agents import create_agentfrom langgraph.checkpoint.postgres import PostgresSaver   # pip install langgraph-checkpoint-postgresDB_URI = "postgresql://app:***@db.internal:5432/support?sslmode=require"def run_chat(user_id: str, chat_id: str, text: str) -> str:    with PostgresSaver.from_conn_string(DB_URI) as saver:        agent = create_agent(model, tools=TOOLS, checkpointer=saver)        cfg = {"configurable": {"thread_id": f"{user_id}:{chat_id}"}}        out = agent.invoke({"messages": [{"role": "user", "content": text}]}, cfg)        return out["messages"][-1].content# Once, at deploy time:with PostgresSaver.from_conn_string(DB_URI) as saver:    saver.setup()          # creates the checkpoint tables

Opening a connection per request keeps the example short. In a real server, create one connection pool at startup (a psycopg_pool.ConnectionPool opened with autocommit=True and row_factory=dict_row, passed to PostgresSaver(pool)) and build the agent once. For async servers use AsyncPostgresSaver. There are also SQLite, Redis and MongoDB checkpointers.

The checkpointer stores the whole graph state per step, including tool calls, tool results and any custom state fields.

Legacy: a SQL message history

Code written in 2024–2025 often stores chat history with SQLChatMessageHistory from langchain-community, plugged into RunnableWithMessageHistory:

Python
from langchain_community.chat_message_histories import SQLChatMessageHistoryfrom langchain_core.runnables.history import RunnableWithMessageHistory   # deprecateddef get_history(session_id: str) -> SQLChatMessageHistory:    return SQLChatMessageHistory(session_id=session_id, connection=engine,                                 table_name="chat_history")chat = RunnableWithMessageHistory(chain, get_history,        input_messages_key="question", history_messages_key="history")

SQLChatMessageHistory creates its table on first use and stores one row per message as JSON, keyed by session_id. It still works, but the wrapper around it is deprecated since langchain-core 1.3.3, and it only stores chat messages, not the agent's full state. When migrating, move the chain into a graph with a checkpointer; old transcripts can be loaded once and written into new threads with update_state.

Production checklist

  • Index the session or thread column (the checkpointer's setup() does this for you).
  • Retention — delete or archive threads older than your policy, for example 90 days.
  • Encryption — at rest in the database; transcripts often contain phone numbers and addresses.
  • Erasure — keep a per-user list of thread IDs so you can delete everything for one person.
  • Size — checkpoints store state per step, so tables grow fast on long agent runs; monitor growth and prune old checkpoints.

A real-life example

A law firm's contract-search assistant is used for research that lasts days: an associate asks about indemnity clauses on Monday and continues the same thread on Wednesday. The first version kept history in memory on 3 servers. Associates found that threads "forgot" everything after the nightly deploy, and that a second browser tab sometimes reached a server with no history at all.

The team moved to PostgresSaver on the firm's existing Postgres cluster, with one connection pool per server, and listed each associate's thread IDs in their own matters table so threads could be reopened by matter. Lost threads went to zero. Because checkpoints are stored per step, the tables grew by about 3 GB a month across 400 users, so they added a job that prunes old checkpoints of closed matters, keeping only the final state, as the firm's records policy requires.

Follow-up questions to expect

  • "Checkpointer or SQLChatMessageHistory?" — Checkpointer for new code: it saves tool steps and supports resume and human approval. SQLChatMessageHistory belongs to the deprecated RunnableWithMessageHistory pattern.
  • "Why a Redis checkpointer?" — Fast reads and built-in expiry (TTL); good for short-lived sessions, less so for long retention or analytics.
  • "How do you scale this horizontally?" — Keep no history in process memory; every request loads by ID from the shared store.