How to use Postgres checkpointer for persistence¶
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
.
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": [] } ] } }