跳到内容

如何使用 Postgres 检查点程序实现持久化

先决条件

本指南假定您熟悉以下内容

在创建 LangGraph 代理时,您可以设置它们以使其状态在执行之间持久保存。这允许您执行诸如多次与代理交互并使其记住以前的交互之类的操作。

本操作指南展示了如何使用 Postgres 作为后端,使用 @langchain/langgraph-checkpoint-postgres 库和 PostgresSaver 类来持久化检查点状态。

出于演示目的,我们将向 预构建的 create react agent 添加持久化。

一般来说,您可以像这样向您构建的任何自定义图添加检查点程序

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 });
...

设置

您将需要访问 Postgres 实例。本指南还将使用 OpenAI,因此您将需要一个 OpenAI API 密钥。

首先,安装所需的软件包

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

然后,将您的 OpenAI API 密钥设置为 process.env.OPENAI_API_KEY

设置 LangSmith 以进行 LangGraph 开发

注册 LangSmith 以快速发现问题并提高 LangGraph 项目的性能。LangSmith 允许您使用跟踪数据来调试、测试和监控使用 LangGraph 构建的 LLM 应用程序——阅读此处以了解更多关于如何开始的信息。


为图定义模型和工具

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"])
  }),
});

使用连接池

在底层,PostgresSaver 使用 node-postgres (pg) 包连接到您的 Postgres 实例。您可以传入您实例化的 连接池,如下所示

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": []
      }
    ]
  }
}

使用连接字符串

您还可以通过将连接字符串传递给 .fromConnString 静态方法在内部创建一个池

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": []
      }
    ]
  }
}