Skip to content

How to use Postgres checkpointer for persistence

Prerequisites

This guide assumes familiarity with the following:

When creating LangGraph agents, you can set them up so that they persist their state across executions. This allows you to do things like interact with an agent multiple times and have it remember previous interactions.

This how-to guide shows how to use Postgres as the backend for persisting checkpoint state using the @langchain/langgraph-checkpoint-postgres library and the PostgresSaver class.

For demonstration purposes we will add persistence to the pre-built create react agent.

In general, you can add a checkpointer to any custom graph that you build like this:

import { StateGraph } from "@langchain/langgraph";
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";

const builder = new StateGraph(...);

// ... define the graph

const checkpointer = PostgresSaver.fromConnString(...); // postgres checkpointer (see examples below)

const graph = builder.compile({ checkpointer });
...

Setup

You will need access to a Postgres instance. This guide will also use OpenAI, so you will need an OpenAI API key.

First, install the required packages:

npm install @langchain/langgraph @langchain/core @langchain/langgraph-checkpoint-postgres

Then, set your OpenAI API key as process.env.OPENAI_API_KEY.

Set up LangSmith for LangGraph development

Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started here.


Define model and tools for the graph

import { z } from "zod";
import { tool } from "@langchain/core/tools";

const getWeather = tool(async (input: { city: "sf" | "nyc" }) => {
  if (input.city === "nyc") {
    return "It might be cloudy in nyc";
  } else if (input.city === "sf") {
    return "It's always sunny in sf";
  } else {
    throw new Error("Unknown city");
  }
}, {
  name: "get_weather",
  description: "Use this to get weather information.",
  schema: z.object({
    city: z.enum(["sf", "nyc"])
  }),
});

With a connection pool

Under the hood, PostgresSaver uses the node-postgres (pg) package to connect to your Postgres instance. You can pass in a connection pool that you've instantiated like this:

import { ChatOpenAI } from "@langchain/openai";
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
import { createReactAgent } from "@langchain/langgraph/prebuilt";

import pg from "pg";

const { Pool } = pg;

const pool = new Pool({
  connectionString: "postgresql://user:password@localhost:5434/testdb"
});

const checkpointer = new PostgresSaver(pool);

// NOTE: you need to call .setup() the first time you're using your checkpointer

await checkpointer.setup();

const graph = createReactAgent({
  tools: [getWeather],
  llm: new ChatOpenAI({
    model: "gpt-4o-mini",
  }),
  checkpointSaver: checkpointer,
});
const config = { configurable: { thread_id: "1" } };

await graph.invoke({
  messages: [{
    role: "user",
    content: "what's the weather in sf"
  }],
}, config);
{
  messages: [
    HumanMessage {
      "id": "ac832b73-242d-4d0b-80d7-5d06a908787e",
      "content": "what's the weather in sf",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "id": "chatcmpl-AGC3tgRXInGLo0qzrD5u3gNqNOegf",
      "content": "",
      "additional_kwargs": {
        "tool_calls": [
          {
            "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R",
            "type": "function",
            "function": "[Object]"
          }
        ]
      },
      "response_metadata": {
        "tokenUsage": {
          "completionTokens": 14,
          "promptTokens": 57,
          "totalTokens": 71
        },
        "finish_reason": "tool_calls",
        "system_fingerprint": "fp_f85bea6784"
      },
      "tool_calls": [
        {
          "name": "get_weather",
          "args": {
            "city": "sf"
          },
          "type": "tool_call",
          "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R"
        }
      ],
      "invalid_tool_calls": [],
      "usage_metadata": {
        "input_tokens": 57,
        "output_tokens": 14,
        "total_tokens": 71
      }
    },
    ToolMessage {
      "id": "6533d271-6126-40af-b5d0-23a484853a97",
      "content": "It's always sunny in sf",
      "name": "get_weather",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_call_id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R"
    },
    AIMessage {
      "id": "chatcmpl-AGC3ttvB69pQu0atw0lUzTpNePlPn",
      "content": "The weather in San Francisco (SF) is always sunny!",
      "additional_kwargs": {},
      "response_metadata": {
        "tokenUsage": {
          "completionTokens": 13,
          "promptTokens": 84,
          "totalTokens": 97
        },
        "finish_reason": "stop",
        "system_fingerprint": "fp_f85bea6784"
      },
      "tool_calls": [],
      "invalid_tool_calls": [],
      "usage_metadata": {
        "input_tokens": 84,
        "output_tokens": 13,
        "total_tokens": 97
      }
    }
  ]
}

await checkpointer.get(config);
{
  v: 1,
  id: '1ef85bc6-bd28-67c1-8003-5cb7dab561b0',
  ts: '2024-10-08T21:29:38.109Z',
  pending_sends: [],
  versions_seen: {
    agent: { tools: 4, '__start__:agent': 2 },
    tools: { 'branch:agent:shouldContinue:tools': 3 },
    __input__: {},
    __start__: { __start__: 1 }
  },
  channel_versions: {
    agent: 5,
    tools: 5,
    messages: 5,
    __start__: 2,
    '__start__:agent': 3,
    'branch:agent:shouldContinue:tools': 4
  },
  channel_values: {
    agent: 'agent',
    messages: [
      HumanMessage {
        "id": "ac832b73-242d-4d0b-80d7-5d06a908787e",
        "content": "what's the weather in sf",
        "additional_kwargs": {},
        "response_metadata": {}
      },
      AIMessage {
        "id": "chatcmpl-AGC3tgRXInGLo0qzrD5u3gNqNOegf",
        "content": "",
        "additional_kwargs": {
          "tool_calls": [
            {
              "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R",
              "type": "function",
              "function": "[Object]"
            }
          ]
        },
        "response_metadata": {
          "tokenUsage": {
            "completionTokens": 14,
            "promptTokens": 57,
            "totalTokens": 71
          },
          "finish_reason": "tool_calls",
          "system_fingerprint": "fp_f85bea6784"
        },
        "tool_calls": [
          {
            "name": "get_weather",
            "args": {
              "city": "sf"
            },
            "type": "tool_call",
            "id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R"
          }
        ],
        "invalid_tool_calls": []
      },
      ToolMessage {
        "id": "6533d271-6126-40af-b5d0-23a484853a97",
        "content": "It's always sunny in sf",
        "name": "get_weather",
        "additional_kwargs": {},
        "response_metadata": {},
        "tool_call_id": "call_I2Ceef2LoxjeaR9m8ZkY7U1R"
      },
      AIMessage {
        "id": "chatcmpl-AGC3ttvB69pQu0atw0lUzTpNePlPn",
        "content": "The weather in San Francisco (SF) is always sunny!",
        "additional_kwargs": {},
        "response_metadata": {
          "tokenUsage": {
            "completionTokens": 13,
            "promptTokens": 84,
            "totalTokens": 97
          },
          "finish_reason": "stop",
          "system_fingerprint": "fp_f85bea6784"
        },
        "tool_calls": [],
        "invalid_tool_calls": []
      }
    ]
  }
}

With a connection string

You can also create a pool internally by passing a connection string to the .fromConnString static method:

const checkpointerFromConnString = PostgresSaver.fromConnString(
  "postgresql://user:password@localhost:5434/testdb"
);

const graph2 = createReactAgent({
  tools: [getWeather],
  llm: new ChatOpenAI({
    model: "gpt-4o-mini",
  }),
  checkpointSaver: checkpointerFromConnString,
});
const config2 = { configurable: { thread_id: "2" } };

await graph2.invoke({
  messages: [{
    role: "user",
    content: "what's the weather in sf"
  }],
}, config2);
{
  messages: [
    HumanMessage {
      "id": "c17b65af-6ac5-411e-ab5c-8003dc53755d",
      "content": "what's the weather in sf",
      "additional_kwargs": {},
      "response_metadata": {}
    },
    AIMessage {
      "id": "chatcmpl-AGC6n8XO05i1Z7f4GnOqpayLPxgoF",
      "content": "",
      "additional_kwargs": {
        "tool_calls": [
          {
            "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO",
            "type": "function",
            "function": "[Object]"
          }
        ]
      },
      "response_metadata": {
        "tokenUsage": {
          "completionTokens": 14,
          "promptTokens": 57,
          "totalTokens": 71
        },
        "finish_reason": "tool_calls",
        "system_fingerprint": "fp_f85bea6784"
      },
      "tool_calls": [
        {
          "name": "get_weather",
          "args": {
            "city": "sf"
          },
          "type": "tool_call",
          "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO"
        }
      ],
      "invalid_tool_calls": [],
      "usage_metadata": {
        "input_tokens": 57,
        "output_tokens": 14,
        "total_tokens": 71
      }
    },
    ToolMessage {
      "id": "779c26b0-6b75-454e-98ef-ecca79e50e8c",
      "content": "It's always sunny in sf",
      "name": "get_weather",
      "additional_kwargs": {},
      "response_metadata": {},
      "tool_call_id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO"
    },
    AIMessage {
      "id": "chatcmpl-AGC6ngqEV0EBZbPwHf2JgTw0n16D8",
      "content": "The weather in San Francisco (SF) is described as always sunny.",
      "additional_kwargs": {},
      "response_metadata": {
        "tokenUsage": {
          "completionTokens": 15,
          "promptTokens": 84,
          "totalTokens": 99
        },
        "finish_reason": "stop",
        "system_fingerprint": "fp_74ba47b4ac"
      },
      "tool_calls": [],
      "invalid_tool_calls": [],
      "usage_metadata": {
        "input_tokens": 84,
        "output_tokens": 15,
        "total_tokens": 99
      }
    }
  ]
}

await checkpointerFromConnString.get(config2);
{
  v: 1,
  id: '1ef85bcd-71b9-6671-8003-6e734c8e9679',
  ts: '2024-10-08T21:32:38.103Z',
  pending_sends: [],
  versions_seen: {
    agent: { tools: 4, '__start__:agent': 2 },
    tools: { 'branch:agent:shouldContinue:tools': 3 },
    __input__: {},
    __start__: { __start__: 1 }
  },
  channel_versions: {
    agent: 5,
    tools: 5,
    messages: 5,
    __start__: 2,
    '__start__:agent': 3,
    'branch:agent:shouldContinue:tools': 4
  },
  channel_values: {
    agent: 'agent',
    messages: [
      HumanMessage {
        "id": "c17b65af-6ac5-411e-ab5c-8003dc53755d",
        "content": "what's the weather in sf",
        "additional_kwargs": {},
        "response_metadata": {}
      },
      AIMessage {
        "id": "chatcmpl-AGC6n8XO05i1Z7f4GnOqpayLPxgoF",
        "content": "",
        "additional_kwargs": {
          "tool_calls": [
            {
              "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO",
              "type": "function",
              "function": "[Object]"
            }
          ]
        },
        "response_metadata": {
          "tokenUsage": {
            "completionTokens": 14,
            "promptTokens": 57,
            "totalTokens": 71
          },
          "finish_reason": "tool_calls",
          "system_fingerprint": "fp_f85bea6784"
        },
        "tool_calls": [
          {
            "name": "get_weather",
            "args": {
              "city": "sf"
            },
            "type": "tool_call",
            "id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO"
          }
        ],
        "invalid_tool_calls": []
      },
      ToolMessage {
        "id": "779c26b0-6b75-454e-98ef-ecca79e50e8c",
        "content": "It's always sunny in sf",
        "name": "get_weather",
        "additional_kwargs": {},
        "response_metadata": {},
        "tool_call_id": "call_n9QCrJ4QbmgFkr5fHEsQHCCO"
      },
      AIMessage {
        "id": "chatcmpl-AGC6ngqEV0EBZbPwHf2JgTw0n16D8",
        "content": "The weather in San Francisco (SF) is described as always sunny.",
        "additional_kwargs": {},
        "response_metadata": {
          "tokenUsage": {
            "completionTokens": 15,
            "promptTokens": 84,
            "totalTokens": 99
          },
          "finish_reason": "stop",
          "system_fingerprint": "fp_74ba47b4ac"
        },
        "tool_calls": [],
        "invalid_tool_calls": []
      }
    ]
  }
}