{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
   "metadata": {},
   "source": [
    "# How to create a custom checkpointer using MongoDB\n",
    "\n",
    "When creating LangGraph agents, you can also set them up so that they persist their state. This allows you to do things like interact with an agent multiple times and have it remember previous interactions. \n",
    "\n",
    "This reference implementation shows how to use MongoDB as the backend for persisting checkpoint state. Make sure that you have MongoDB running on port `27017` for going through this guide.\n",
    "\n",
    "NOTE: this is just an reference implementation. You can implement your own checkpointer using a different database or modify this one as long as it conforms to the `BaseCheckpointSaver` interface."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "456fa19c-93a5-4750-a410-f2d810b964ad",
   "metadata": {},
   "source": [
    "## Setup environment"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "faadfb1b-cebe-4dcf-82fd-34044c380bc4",
   "metadata": {},
   "outputs": [],
   "source": [
    "%%capture --no-stderr\n",
    "%pip install -U pymongo motor langgraph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "eca9aafb-a155-407a-8036-682a2f1297d7",
   "metadata": {},
   "outputs": [
    {
     "name": "stdin",
     "output_type": "stream",
     "text": [
      "OPENAI_API_KEY:  ········\n"
     ]
    }
   ],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "\n",
    "def _set_env(var: str):\n",
    "    if not os.environ.get(var):\n",
    "        os.environ[var] = getpass.getpass(f\"{var}: \")\n",
    "\n",
    "\n",
    "_set_env(\"OPENAI_API_KEY\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ecb23436-f238-4f8c-a2b7-67c7956121e2",
   "metadata": {},
   "source": [
    "## Checkpointer implementation"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "922822a8-f7d2-41ce-bada-206fc125c20c",
   "metadata": {},
   "source": [
    "### MongoDBSaver"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c216852b-8318-4927-9000-1361d3ca81e8",
   "metadata": {},
   "source": [
    "Below is an implementation of MongoDBSaver (for synchronous use of graph, i.e. `.invoke()`, `.stream()`). MongoDBSaver implements four methods that are required for any checkpointer:\n",
    "\n",
    "- `.put` - Store a checkpoint with its configuration and metadata.\n",
    "- `.put_writes` - Store intermediate writes linked to a checkpoint (i.e. pending writes).\n",
    "- `.get_tuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `checkpoint_id`).\n",
    "- `.list` - List checkpoints that match a given configuration and filter criteria."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "98c8d65e-eb95-4cbd-8975-d33a52351d03",
   "metadata": {},
   "outputs": [],
   "source": [
    "from contextlib import asynccontextmanager, contextmanager\n",
    "from typing import Any, AsyncIterator, Dict, Iterator, Optional, Sequence, Tuple\n",
    "\n",
    "from langchain_core.runnables import RunnableConfig\n",
    "from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase\n",
    "from pymongo import MongoClient, UpdateOne\n",
    "from pymongo.database import Database as MongoDatabase\n",
    "\n",
    "from langgraph.checkpoint.base import (\n",
    "    BaseCheckpointSaver,\n",
    "    ChannelVersions,\n",
    "    Checkpoint,\n",
    "    CheckpointMetadata,\n",
    "    CheckpointTuple,\n",
    "    get_checkpoint_id,\n",
    ")\n",
    "\n",
    "\n",
    "class MongoDBSaver(BaseCheckpointSaver):\n",
    "    \"\"\"A checkpoint saver that stores checkpoints in a MongoDB database.\"\"\"\n",
    "\n",
    "    client: MongoClient\n",
    "    db: MongoDatabase\n",
    "\n",
    "    def __init__(\n",
    "        self,\n",
    "        client: MongoClient,\n",
    "        db_name: str,\n",
    "    ) -> None:\n",
    "        super().__init__()\n",
    "        self.client = client\n",
    "        self.db = self.client[db_name]\n",
    "\n",
    "    @classmethod\n",
    "    @contextmanager\n",
    "    def from_conn_info(\n",
    "        cls, *, host: str, port: int, db_name: str\n",
    "    ) -> Iterator[\"MongoDBSaver\"]:\n",
    "        client = None\n",
    "        try:\n",
    "            client = MongoClient(host=host, port=port)\n",
    "            yield MongoDBSaver(client, db_name)\n",
    "        finally:\n",
    "            if client:\n",
    "                client.close()\n",
    "\n",
    "    def get_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:\n",
    "        \"\"\"Get a checkpoint tuple from the database.\n",
    "\n",
    "        This method retrieves a checkpoint tuple from the MongoDB database based on the\n",
    "        provided config. If the config contains a \"checkpoint_id\" key, the checkpoint with\n",
    "        the matching thread ID and checkpoint ID is retrieved. Otherwise, the latest checkpoint\n",
    "        for the given thread ID is retrieved.\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): The config to use for retrieving the checkpoint.\n",
    "\n",
    "        Returns:\n",
    "            Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.\n",
    "        \"\"\"\n",
    "        thread_id = config[\"configurable\"][\"thread_id\"]\n",
    "        checkpoint_ns = config[\"configurable\"].get(\"checkpoint_ns\", \"\")\n",
    "        if checkpoint_id := get_checkpoint_id(config):\n",
    "            query = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": checkpoint_id,\n",
    "            }\n",
    "        else:\n",
    "            query = {\"thread_id\": thread_id, \"checkpoint_ns\": checkpoint_ns}\n",
    "\n",
    "        result = self.db[\"checkpoints\"].find(query).sort(\"checkpoint_id\", -1).limit(1)\n",
    "        for doc in result:\n",
    "            config_values = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": doc[\"checkpoint_id\"],\n",
    "            }\n",
    "            checkpoint = self.serde.loads_typed((doc[\"type\"], doc[\"checkpoint\"]))\n",
    "            serialized_writes = self.db[\"checkpoint_writes\"].find(config_values)\n",
    "            pending_writes = [\n",
    "                (\n",
    "                    doc[\"task_id\"],\n",
    "                    doc[\"channel\"],\n",
    "                    self.serde.loads_typed((doc[\"type\"], doc[\"value\"])),\n",
    "                )\n",
    "                for doc in serialized_writes\n",
    "            ]\n",
    "            return CheckpointTuple(\n",
    "                {\"configurable\": config_values},\n",
    "                checkpoint,\n",
    "                self.serde.loads(doc[\"metadata\"]),\n",
    "                (\n",
    "                    {\n",
    "                        \"configurable\": {\n",
    "                            \"thread_id\": thread_id,\n",
    "                            \"checkpoint_ns\": checkpoint_ns,\n",
    "                            \"checkpoint_id\": doc[\"parent_checkpoint_id\"],\n",
    "                        }\n",
    "                    }\n",
    "                    if doc.get(\"parent_checkpoint_id\")\n",
    "                    else None\n",
    "                ),\n",
    "                pending_writes,\n",
    "            )\n",
    "\n",
    "    def list(\n",
    "        self,\n",
    "        config: Optional[RunnableConfig],\n",
    "        *,\n",
    "        filter: Optional[Dict[str, Any]] = None,\n",
    "        before: Optional[RunnableConfig] = None,\n",
    "        limit: Optional[int] = None,\n",
    "    ) -> Iterator[CheckpointTuple]:\n",
    "        \"\"\"List checkpoints from the database.\n",
    "\n",
    "        This method retrieves a list of checkpoint tuples from the MongoDB database based\n",
    "        on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): The config to use for listing the checkpoints.\n",
    "            filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata. Defaults to None.\n",
    "            before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.\n",
    "            limit (Optional[int]): The maximum number of checkpoints to return. Defaults to None.\n",
    "\n",
    "        Yields:\n",
    "            Iterator[CheckpointTuple]: An iterator of checkpoint tuples.\n",
    "        \"\"\"\n",
    "        query = {}\n",
    "        if config is not None:\n",
    "            query = {\n",
    "                \"thread_id\": config[\"configurable\"][\"thread_id\"],\n",
    "                \"checkpoint_ns\": config[\"configurable\"].get(\"checkpoint_ns\", \"\"),\n",
    "            }\n",
    "\n",
    "        if filter:\n",
    "            for key, value in filter.items():\n",
    "                query[f\"metadata.{key}\"] = value\n",
    "\n",
    "        if before is not None:\n",
    "            query[\"checkpoint_id\"] = {\"$lt\": before[\"configurable\"][\"checkpoint_id\"]}\n",
    "\n",
    "        result = self.db[\"checkpoints\"].find(query).sort(\"checkpoint_id\", -1)\n",
    "\n",
    "        if limit is not None:\n",
    "            result = result.limit(limit)\n",
    "        for doc in result:\n",
    "            checkpoint = self.serde.loads_typed((doc[\"type\"], doc[\"checkpoint\"]))\n",
    "            yield CheckpointTuple(\n",
    "                {\n",
    "                    \"configurable\": {\n",
    "                        \"thread_id\": doc[\"thread_id\"],\n",
    "                        \"checkpoint_ns\": doc[\"checkpoint_ns\"],\n",
    "                        \"checkpoint_id\": doc[\"checkpoint_id\"],\n",
    "                    }\n",
    "                },\n",
    "                checkpoint,\n",
    "                self.serde.loads(doc[\"metadata\"]),\n",
    "                (\n",
    "                    {\n",
    "                        \"configurable\": {\n",
    "                            \"thread_id\": doc[\"thread_id\"],\n",
    "                            \"checkpoint_ns\": doc[\"checkpoint_ns\"],\n",
    "                            \"checkpoint_id\": doc[\"parent_checkpoint_id\"],\n",
    "                        }\n",
    "                    }\n",
    "                    if doc.get(\"parent_checkpoint_id\")\n",
    "                    else None\n",
    "                ),\n",
    "            )\n",
    "\n",
    "    def put(\n",
    "        self,\n",
    "        config: RunnableConfig,\n",
    "        checkpoint: Checkpoint,\n",
    "        metadata: CheckpointMetadata,\n",
    "        new_versions: ChannelVersions,\n",
    "    ) -> RunnableConfig:\n",
    "        \"\"\"Save a checkpoint to the database.\n",
    "\n",
    "        This method saves a checkpoint to the MongoDB database. The checkpoint is associated\n",
    "        with the provided config and its parent config (if any).\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): The config to associate with the checkpoint.\n",
    "            checkpoint (Checkpoint): The checkpoint to save.\n",
    "            metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.\n",
    "            new_versions (ChannelVersions): New channel versions as of this write.\n",
    "\n",
    "        Returns:\n",
    "            RunnableConfig: Updated configuration after storing the checkpoint.\n",
    "        \"\"\"\n",
    "        thread_id = config[\"configurable\"][\"thread_id\"]\n",
    "        checkpoint_ns = config[\"configurable\"][\"checkpoint_ns\"]\n",
    "        checkpoint_id = checkpoint[\"id\"]\n",
    "        type_, serialized_checkpoint = self.serde.dumps_typed(checkpoint)\n",
    "        doc = {\n",
    "            \"parent_checkpoint_id\": config[\"configurable\"].get(\"checkpoint_id\"),\n",
    "            \"type\": type_,\n",
    "            \"checkpoint\": serialized_checkpoint,\n",
    "            \"metadata\": self.serde.dumps(metadata),\n",
    "        }\n",
    "        upsert_query = {\n",
    "            \"thread_id\": thread_id,\n",
    "            \"checkpoint_ns\": checkpoint_ns,\n",
    "            \"checkpoint_id\": checkpoint_id,\n",
    "        }\n",
    "        # Perform your operations here\n",
    "        self.db[\"checkpoints\"].update_one(upsert_query, {\"$set\": doc}, upsert=True)\n",
    "        return {\n",
    "            \"configurable\": {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": checkpoint_id,\n",
    "            }\n",
    "        }\n",
    "\n",
    "    def put_writes(\n",
    "        self,\n",
    "        config: RunnableConfig,\n",
    "        writes: Sequence[Tuple[str, Any]],\n",
    "        task_id: str,\n",
    "    ) -> None:\n",
    "        \"\"\"Store intermediate writes linked to a checkpoint.\n",
    "\n",
    "        This method saves intermediate writes associated with a checkpoint to the MongoDB database.\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): Configuration of the related checkpoint.\n",
    "            writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.\n",
    "            task_id (str): Identifier for the task creating the writes.\n",
    "        \"\"\"\n",
    "        thread_id = config[\"configurable\"][\"thread_id\"]\n",
    "        checkpoint_ns = config[\"configurable\"][\"checkpoint_ns\"]\n",
    "        checkpoint_id = config[\"configurable\"][\"checkpoint_id\"]\n",
    "        operations = []\n",
    "        for idx, (channel, value) in enumerate(writes):\n",
    "            upsert_query = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": checkpoint_id,\n",
    "                \"task_id\": task_id,\n",
    "                \"idx\": idx,\n",
    "            }\n",
    "            type_, serialized_value = self.serde.dumps_typed(value)\n",
    "            operations.append(\n",
    "                UpdateOne(\n",
    "                    upsert_query,\n",
    "                    {\n",
    "                        \"$set\": {\n",
    "                            \"channel\": channel,\n",
    "                            \"type\": type_,\n",
    "                            \"value\": serialized_value,\n",
    "                        }\n",
    "                    },\n",
    "                    upsert=True,\n",
    "                )\n",
    "            )\n",
    "        self.db[\"checkpoint_writes\"].bulk_write(operations)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ec21ff00-75a7-4789-b863-93fffcc0b32d",
   "metadata": {},
   "source": [
    "### AsyncMongoDBSaver"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9e5ad763-12ab-4918-af40-0be85678e35b",
   "metadata": {},
   "source": [
    "Below is a reference implementation of AsyncMongoDBSaver (for asynchronous use of graph, i.e. `.ainvoke()`, `.astream()`). AsyncMongoDBSaver implements four methods that are required for any async checkpointer:\n",
    "\n",
    "- `.aput` - Store a checkpoint with its configuration and metadata.\n",
    "- `.aput_writes` - Store intermediate writes linked to a checkpoint (i.e. pending writes).\n",
    "- `.aget_tuple` - Fetch a checkpoint tuple using for a given configuration (`thread_id` and `checkpoint_id`).\n",
    "- `.alist` - List checkpoints that match a given configuration and filter criteria."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "888302ee-c201-498f-b6e3-69ec5f1a039c",
   "metadata": {},
   "outputs": [],
   "source": [
    "class AsyncMongoDBSaver(BaseCheckpointSaver):\n",
    "    \"\"\"A checkpoint saver that stores checkpoints in a MongoDB database asynchronously.\"\"\"\n",
    "\n",
    "    client: AsyncIOMotorClient\n",
    "    db: AsyncIOMotorDatabase\n",
    "\n",
    "    def __init__(\n",
    "        self,\n",
    "        client: AsyncIOMotorClient,\n",
    "        db_name: str,\n",
    "    ) -> None:\n",
    "        super().__init__()\n",
    "        self.client = client\n",
    "        self.db = self.client[db_name]\n",
    "\n",
    "    @classmethod\n",
    "    @asynccontextmanager\n",
    "    async def from_conn_info(\n",
    "        cls, *, host: str, port: int, db_name: str\n",
    "    ) -> AsyncIterator[\"AsyncMongoDBSaver\"]:\n",
    "        client = None\n",
    "        try:\n",
    "            client = AsyncIOMotorClient(host=host, port=port)\n",
    "            yield AsyncMongoDBSaver(client, db_name)\n",
    "        finally:\n",
    "            if client:\n",
    "                client.close()\n",
    "\n",
    "    async def aget_tuple(self, config: RunnableConfig) -> Optional[CheckpointTuple]:\n",
    "        \"\"\"Get a checkpoint tuple from the database asynchronously.\n",
    "\n",
    "        This method retrieves a checkpoint tuple from the MongoDB database based on the\n",
    "        provided config. If the config contains a \"checkpoint_id\" key, the checkpoint with\n",
    "        the matching thread ID and checkpoint ID is retrieved. Otherwise, the latest checkpoint\n",
    "        for the given thread ID is retrieved.\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): The config to use for retrieving the checkpoint.\n",
    "\n",
    "        Returns:\n",
    "            Optional[CheckpointTuple]: The retrieved checkpoint tuple, or None if no matching checkpoint was found.\n",
    "        \"\"\"\n",
    "        thread_id = config[\"configurable\"][\"thread_id\"]\n",
    "        checkpoint_ns = config[\"configurable\"].get(\"checkpoint_ns\", \"\")\n",
    "        if checkpoint_id := get_checkpoint_id(config):\n",
    "            query = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": checkpoint_id,\n",
    "            }\n",
    "        else:\n",
    "            query = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "            }\n",
    "\n",
    "        result = self.db[\"checkpoints\"].find(query).sort(\"checkpoint_id\", -1).limit(1)\n",
    "        async for doc in result:\n",
    "            config_values = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": doc[\"checkpoint_id\"],\n",
    "            }\n",
    "            checkpoint = self.serde.loads_typed((doc[\"type\"], doc[\"checkpoint\"]))\n",
    "            serialized_writes = self.db[\"checkpoint_writes\"].find(config_values)\n",
    "            pending_writes = [\n",
    "                (\n",
    "                    doc[\"task_id\"],\n",
    "                    doc[\"channel\"],\n",
    "                    self.serde.loads_typed((doc[\"type\"], doc[\"value\"])),\n",
    "                )\n",
    "                async for doc in serialized_writes\n",
    "            ]\n",
    "            return CheckpointTuple(\n",
    "                {\"configurable\": config_values},\n",
    "                checkpoint,\n",
    "                self.serde.loads(doc[\"metadata\"]),\n",
    "                (\n",
    "                    {\n",
    "                        \"configurable\": {\n",
    "                            \"thread_id\": thread_id,\n",
    "                            \"checkpoint_ns\": checkpoint_ns,\n",
    "                            \"checkpoint_id\": doc[\"parent_checkpoint_id\"],\n",
    "                        }\n",
    "                    }\n",
    "                    if doc.get(\"parent_checkpoint_id\")\n",
    "                    else None\n",
    "                ),\n",
    "                pending_writes,\n",
    "            )\n",
    "\n",
    "    async def alist(\n",
    "        self,\n",
    "        config: Optional[RunnableConfig],\n",
    "        *,\n",
    "        filter: Optional[Dict[str, Any]] = None,\n",
    "        before: Optional[RunnableConfig] = None,\n",
    "        limit: Optional[int] = None,\n",
    "    ) -> AsyncIterator[CheckpointTuple]:\n",
    "        \"\"\"List checkpoints from the database asynchronously.\n",
    "\n",
    "        This method retrieves a list of checkpoint tuples from the MongoDB database based\n",
    "        on the provided config. The checkpoints are ordered by checkpoint ID in descending order (newest first).\n",
    "\n",
    "        Args:\n",
    "            config (Optional[RunnableConfig]): Base configuration for filtering checkpoints.\n",
    "            filter (Optional[Dict[str, Any]]): Additional filtering criteria for metadata.\n",
    "            before (Optional[RunnableConfig]): If provided, only checkpoints before the specified checkpoint ID are returned. Defaults to None.\n",
    "            limit (Optional[int]): Maximum number of checkpoints to return.\n",
    "\n",
    "        Yields:\n",
    "            AsyncIterator[CheckpointTuple]: An asynchronous iterator of matching checkpoint tuples.\n",
    "        \"\"\"\n",
    "        query = {}\n",
    "        if config is not None:\n",
    "            query = {\n",
    "                \"thread_id\": config[\"configurable\"][\"thread_id\"],\n",
    "                \"checkpoint_ns\": config[\"configurable\"].get(\"checkpoint_ns\", \"\"),\n",
    "            }\n",
    "\n",
    "        if filter:\n",
    "            for key, value in filter.items():\n",
    "                query[f\"metadata.{key}\"] = value\n",
    "\n",
    "        if before is not None:\n",
    "            query[\"checkpoint_id\"] = {\"$lt\": before[\"configurable\"][\"checkpoint_id\"]}\n",
    "\n",
    "        result = self.db[\"checkpoints\"].find(query).sort(\"checkpoint_id\", -1)\n",
    "\n",
    "        if limit is not None:\n",
    "            result = result.limit(limit)\n",
    "        async for doc in result:\n",
    "            checkpoint = self.serde.loads_typed((doc[\"type\"], doc[\"checkpoint\"]))\n",
    "            yield CheckpointTuple(\n",
    "                {\n",
    "                    \"configurable\": {\n",
    "                        \"thread_id\": doc[\"thread_id\"],\n",
    "                        \"checkpoint_ns\": doc[\"checkpoint_ns\"],\n",
    "                        \"checkpoint_id\": doc[\"checkpoint_id\"],\n",
    "                    }\n",
    "                },\n",
    "                checkpoint,\n",
    "                self.serde.loads(doc[\"metadata\"]),\n",
    "                (\n",
    "                    {\n",
    "                        \"configurable\": {\n",
    "                            \"thread_id\": doc[\"thread_id\"],\n",
    "                            \"checkpoint_ns\": doc[\"checkpoint_ns\"],\n",
    "                            \"checkpoint_id\": doc[\"parent_checkpoint_id\"],\n",
    "                        }\n",
    "                    }\n",
    "                    if doc.get(\"parent_checkpoint_id\")\n",
    "                    else None\n",
    "                ),\n",
    "            )\n",
    "\n",
    "    async def aput(\n",
    "        self,\n",
    "        config: RunnableConfig,\n",
    "        checkpoint: Checkpoint,\n",
    "        metadata: CheckpointMetadata,\n",
    "        new_versions: ChannelVersions,\n",
    "    ) -> RunnableConfig:\n",
    "        \"\"\"Save a checkpoint to the database asynchronously.\n",
    "\n",
    "        This method saves a checkpoint to the MongoDB database. The checkpoint is associated\n",
    "        with the provided config and its parent config (if any).\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): The config to associate with the checkpoint.\n",
    "            checkpoint (Checkpoint): The checkpoint to save.\n",
    "            metadata (CheckpointMetadata): Additional metadata to save with the checkpoint.\n",
    "            new_versions (ChannelVersions): New channel versions as of this write.\n",
    "\n",
    "        Returns:\n",
    "            RunnableConfig: Updated configuration after storing the checkpoint.\n",
    "        \"\"\"\n",
    "        thread_id = config[\"configurable\"][\"thread_id\"]\n",
    "        checkpoint_ns = config[\"configurable\"][\"checkpoint_ns\"]\n",
    "        checkpoint_id = checkpoint[\"id\"]\n",
    "        type_, serialized_checkpoint = self.serde.dumps_typed(checkpoint)\n",
    "        doc = {\n",
    "            \"parent_checkpoint_id\": config[\"configurable\"].get(\"checkpoint_id\"),\n",
    "            \"type\": type_,\n",
    "            \"checkpoint\": serialized_checkpoint,\n",
    "            \"metadata\": self.serde.dumps(metadata),\n",
    "        }\n",
    "        upsert_query = {\n",
    "            \"thread_id\": thread_id,\n",
    "            \"checkpoint_ns\": checkpoint_ns,\n",
    "            \"checkpoint_id\": checkpoint_id,\n",
    "        }\n",
    "        # Perform your operations here\n",
    "        await self.db[\"checkpoints\"].update_one(\n",
    "            upsert_query, {\"$set\": doc}, upsert=True\n",
    "        )\n",
    "        return {\n",
    "            \"configurable\": {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": checkpoint_id,\n",
    "            }\n",
    "        }\n",
    "\n",
    "    async def aput_writes(\n",
    "        self,\n",
    "        config: RunnableConfig,\n",
    "        writes: Sequence[Tuple[str, Any]],\n",
    "        task_id: str,\n",
    "    ) -> None:\n",
    "        \"\"\"Store intermediate writes linked to a checkpoint asynchronously.\n",
    "\n",
    "        This method saves intermediate writes associated with a checkpoint to the database.\n",
    "\n",
    "        Args:\n",
    "            config (RunnableConfig): Configuration of the related checkpoint.\n",
    "            writes (Sequence[Tuple[str, Any]]): List of writes to store, each as (channel, value) pair.\n",
    "            task_id (str): Identifier for the task creating the writes.\n",
    "        \"\"\"\n",
    "        thread_id = config[\"configurable\"][\"thread_id\"]\n",
    "        checkpoint_ns = config[\"configurable\"][\"checkpoint_ns\"]\n",
    "        checkpoint_id = config[\"configurable\"][\"checkpoint_id\"]\n",
    "        operations = []\n",
    "        for idx, (channel, value) in enumerate(writes):\n",
    "            upsert_query = {\n",
    "                \"thread_id\": thread_id,\n",
    "                \"checkpoint_ns\": checkpoint_ns,\n",
    "                \"checkpoint_id\": checkpoint_id,\n",
    "                \"task_id\": task_id,\n",
    "                \"idx\": idx,\n",
    "            }\n",
    "            type_, serialized_value = self.serde.dumps_typed(value)\n",
    "            operations.append(\n",
    "                UpdateOne(\n",
    "                    upsert_query,\n",
    "                    {\n",
    "                        \"$set\": {\n",
    "                            \"channel\": channel,\n",
    "                            \"type\": type_,\n",
    "                            \"value\": serialized_value,\n",
    "                        }\n",
    "                    },\n",
    "                    upsert=True,\n",
    "                )\n",
    "            )\n",
    "        await self.db[\"checkpoint_writes\"].bulk_write(operations)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e26b3204-cca2-414c-800e-7e09032445ae",
   "metadata": {},
   "source": [
    "## Setup model and tools for the graph"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "e5213193-5a7d-43e7-aeba-fe732bb1cd7a",
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import Literal\n",
    "from langchain_core.runnables import ConfigurableField\n",
    "from langchain_core.tools import tool\n",
    "from langchain_openai import ChatOpenAI\n",
    "from langgraph.prebuilt import create_react_agent\n",
    "\n",
    "\n",
    "@tool\n",
    "def get_weather(city: Literal[\"nyc\", \"sf\"]):\n",
    "    \"\"\"Use this to get weather information.\"\"\"\n",
    "    if city == \"nyc\":\n",
    "        return \"It might be cloudy in nyc\"\n",
    "    elif city == \"sf\":\n",
    "        return \"It's always sunny in sf\"\n",
    "    else:\n",
    "        raise AssertionError(\"Unknown city\")\n",
    "\n",
    "\n",
    "tools = [get_weather]\n",
    "model = ChatOpenAI(model_name=\"gpt-4o-mini\", temperature=0)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9342c62-dbb4-40f6-9271-7393f1ca48c4",
   "metadata": {},
   "source": [
    "## Use sync connection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "5fe54e79-9eaf-44e2-b2d9-1e0284b984d0",
   "metadata": {},
   "outputs": [],
   "source": [
    "with MongoDBSaver.from_conn_info(host=\"localhost\", port=27017, db_name=\"checkpoints\") as checkpointer:\n",
    "    graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
    "    config = {\"configurable\": {\"thread_id\": \"1\"}}\n",
    "    res = graph.invoke({\"messages\": [(\"human\", \"what's the weather in sf\")]}, config)\n",
    "\n",
    "    latest_checkpoint = checkpointer.get(config)\n",
    "    latest_checkpoint_tuple = checkpointer.get_tuple(config)\n",
    "    checkpoint_tuples = list(checkpointer.list(config))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "c298e627-115a-4b4c-ae17-520ca9a640cd",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'v': 1,\n",
       " 'ts': '2024-08-09T16:19:39.102711+00:00',\n",
       " 'id': '1ef566b2-d2a8-6cdc-8003-cc4d1980d188',\n",
       " 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='f4227353-e0e5-43a9-984a-e4b9e2d8e7b8'),\n",
       "   AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd1d3187-470f-4ebd-938f-527a61824045-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}),\n",
       "   ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='2d124101-696d-450f-bc9f-d8fdcc564101', tool_call_id='call_Y7PzHb7LrIdiTnO5UiSfelt3'),\n",
       "   AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-87c76dd2-33f4-433e-986a-9405cfe88c88-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})],\n",
       "  'agent': 'agent'},\n",
       " 'channel_versions': {'__start__': 2,\n",
       "  'messages': 5,\n",
       "  'start:agent': 3,\n",
       "  'agent': 5,\n",
       "  'branch:agent:should_continue:tools': 4,\n",
       "  'tools': 5},\n",
       " 'versions_seen': {'__input__': {},\n",
       "  '__start__': {'__start__': 1},\n",
       "  'agent': {'start:agent': 2, 'tools': 4},\n",
       "  'tools': {'branch:agent:should_continue:tools': 3}},\n",
       " 'pending_sends': [],\n",
       " 'current_tasks': {}}"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "latest_checkpoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "922f9406-0f68-418a-9cb4-e0e29de4b5f9",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "CheckpointTuple(config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-d2a8-6cdc-8003-cc4d1980d188'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:39.102711+00:00', 'id': '1ef566b2-d2a8-6cdc-8003-cc4d1980d188', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='f4227353-e0e5-43a9-984a-e4b9e2d8e7b8'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd1d3187-470f-4ebd-938f-527a61824045-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='2d124101-696d-450f-bc9f-d8fdcc564101', tool_call_id='call_Y7PzHb7LrIdiTnO5UiSfelt3'), AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-87c76dd2-33f4-433e-986a-9405cfe88c88-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-87c76dd2-33f4-433e-986a-9405cfe88c88-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}, 'step': 3}, parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-cdf7-6b98-8002-997748cc5052'}}, pending_writes=[])"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "latest_checkpoint_tuple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "b2ce743b-5896-443b-9ec0-a655b065895c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[CheckpointTuple(config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-d2a8-6cdc-8003-cc4d1980d188'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:39.102711+00:00', 'id': '1ef566b2-d2a8-6cdc-8003-cc4d1980d188', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='f4227353-e0e5-43a9-984a-e4b9e2d8e7b8'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd1d3187-470f-4ebd-938f-527a61824045-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='2d124101-696d-450f-bc9f-d8fdcc564101', tool_call_id='call_Y7PzHb7LrIdiTnO5UiSfelt3'), AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-87c76dd2-33f4-433e-986a-9405cfe88c88-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is always sunny!', response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-87c76dd2-33f4-433e-986a-9405cfe88c88-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94})]}}, 'step': 3}, parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-cdf7-6b98-8002-997748cc5052'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-cdf7-6b98-8002-997748cc5052'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:38.610752+00:00', 'id': '1ef566b2-cdf7-6b98-8002-997748cc5052', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='f4227353-e0e5-43a9-984a-e4b9e2d8e7b8'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd1d3187-470f-4ebd-938f-527a61824045-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='2d124101-696d-450f-bc9f-d8fdcc564101', tool_call_id='call_Y7PzHb7LrIdiTnO5UiSfelt3')], 'tools': 'tools'}, 'channel_versions': {'__start__': 2, 'messages': 4, 'start:agent': 3, 'agent': 4, 'branch:agent:should_continue:tools': 4, 'tools': 4}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'tools': {'messages': [ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='2d124101-696d-450f-bc9f-d8fdcc564101', tool_call_id='call_Y7PzHb7LrIdiTnO5UiSfelt3')]}}, 'step': 2}, parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-cde3-6c60-8001-28d4cc36978d'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-cde3-6c60-8001-28d4cc36978d'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:38.602590+00:00', 'id': '1ef566b2-cde3-6c60-8001-28d4cc36978d', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='f4227353-e0e5-43a9-984a-e4b9e2d8e7b8'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd1d3187-470f-4ebd-938f-527a61824045-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})], 'agent': 'agent', 'branch:agent:should_continue:tools': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 3, 'start:agent': 3, 'agent': 3, 'branch:agent:should_continue:tools': 3}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-cd1d3187-470f-4ebd-938f-527a61824045-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_Y7PzHb7LrIdiTnO5UiSfelt3', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71})]}}, 'step': 1}, parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-c72c-6fca-8000-aac6e4f4b809'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-c72c-6fca-8000-aac6e4f4b809'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:37.898584+00:00', 'id': '1ef566b2-c72c-6fca-8000-aac6e4f4b809', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in sf\", id='f4227353-e0e5-43a9-984a-e4b9e2d8e7b8')], 'start:agent': '__start__'}, 'channel_versions': {'__start__': 2, 'messages': 2, 'start:agent': 2}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': None, 'step': 0}, parent_config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-c72a-6af4-bfff-919b9dc6abfe'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '1', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b2-c72a-6af4-bfff-919b9dc6abfe'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:37.897642+00:00', 'id': '1ef566b2-c72a-6af4-bfff-919b9dc6abfe', 'channel_values': {'messages': [], '__start__': {'messages': [['human', \"what's the weather in sf\"]]}}, 'channel_versions': {'__start__': 1}, 'versions_seen': {'__input__': {}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'input', 'writes': {'messages': [['human', \"what's the weather in sf\"]]}, 'step': -1}, parent_config=None, pending_writes=None)]"
      ]
     },
     "execution_count": 9,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "checkpoint_tuples"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c0a47d3e-e588-48fc-a5d4-2145dff17e77",
   "metadata": {},
   "source": [
    "## Use async connection"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "6a39d1ff-ca37-4457-8b52-07d33b59c36e",
   "metadata": {},
   "outputs": [],
   "source": [
    "async with AsyncMongoDBSaver.from_conn_info(host=\"localhost\", port=27017, db_name=\"checkpoints\") as checkpointer:\n",
    "    graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
    "    config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
    "    res = await graph.ainvoke({\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config)\n",
    "\n",
    "    latest_checkpoint = await checkpointer.aget(config)\n",
    "    latest_checkpoint_tuple = await checkpointer.aget_tuple(config)\n",
    "    checkpoint_tuples = [c async for c in checkpointer.alist(config)]"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "51125ef1-bdb6-454e-82cc-4ae19a113606",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "{'v': 1,\n",
       " 'ts': '2024-08-09T16:19:48.212051+00:00',\n",
       " 'id': '1ef566b3-2988-664c-8003-5974c59c6bda',\n",
       " 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='1ae4b12f-b1cb-4d55-a754-42cf1c2fbcd5'),\n",
       "   AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b5da58b5-8f75-485d-af29-bfdeb09b0d94-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}),\n",
       "   ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='56d4e46b-6cb3-4efe-b369-27b666e62348', tool_call_id='call_IJvXEELx7Ir3kASCqr9dbvhU'),\n",
       "   AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-dcacbc70-b213-4ddc-ac08-c0d17b2766d8-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})],\n",
       "  'agent': 'agent'},\n",
       " 'channel_versions': {'__start__': 2,\n",
       "  'messages': 5,\n",
       "  'start:agent': 3,\n",
       "  'agent': 5,\n",
       "  'branch:agent:should_continue:tools': 4,\n",
       "  'tools': 5},\n",
       " 'versions_seen': {'__input__': {},\n",
       "  '__start__': {'__start__': 1},\n",
       "  'agent': {'start:agent': 2, 'tools': 4},\n",
       "  'tools': {'branch:agent:should_continue:tools': 3}},\n",
       " 'pending_sends': [],\n",
       " 'current_tasks': {}}"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "latest_checkpoint"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "97f8a87b-8423-41c6-a76b-9a6b30904e73",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-2988-664c-8003-5974c59c6bda'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:48.212051+00:00', 'id': '1ef566b3-2988-664c-8003-5974c59c6bda', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='1ae4b12f-b1cb-4d55-a754-42cf1c2fbcd5'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b5da58b5-8f75-485d-af29-bfdeb09b0d94-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='56d4e46b-6cb3-4efe-b369-27b666e62348', tool_call_id='call_IJvXEELx7Ir3kASCqr9dbvhU'), AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-dcacbc70-b213-4ddc-ac08-c0d17b2766d8-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-dcacbc70-b213-4ddc-ac08-c0d17b2766d8-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}, 'step': 3}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-23c9-64ea-8002-036c32979035'}}, pending_writes=[])"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "latest_checkpoint_tuple"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "2b6d73ca-519e-45f7-90c2-1b8596624505",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "[CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-2988-664c-8003-5974c59c6bda'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:48.212051+00:00', 'id': '1ef566b3-2988-664c-8003-5974c59c6bda', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='1ae4b12f-b1cb-4d55-a754-42cf1c2fbcd5'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b5da58b5-8f75-485d-af29-bfdeb09b0d94-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='56d4e46b-6cb3-4efe-b369-27b666e62348', tool_call_id='call_IJvXEELx7Ir3kASCqr9dbvhU'), AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-dcacbc70-b213-4ddc-ac08-c0d17b2766d8-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})], 'agent': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 5, 'start:agent': 3, 'agent': 5, 'branch:agent:should_continue:tools': 4, 'tools': 5}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2, 'tools': 4}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in NYC might be cloudy.', response_metadata={'token_usage': {'completion_tokens': 9, 'prompt_tokens': 88, 'total_tokens': 97}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'stop', 'logprobs': None}, id='run-dcacbc70-b213-4ddc-ac08-c0d17b2766d8-0', usage_metadata={'input_tokens': 88, 'output_tokens': 9, 'total_tokens': 97})]}}, 'step': 3}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-23c9-64ea-8002-036c32979035'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-23c9-64ea-8002-036c32979035'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:47.609498+00:00', 'id': '1ef566b3-23c9-64ea-8002-036c32979035', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='1ae4b12f-b1cb-4d55-a754-42cf1c2fbcd5'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b5da58b5-8f75-485d-af29-bfdeb09b0d94-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73}), ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='56d4e46b-6cb3-4efe-b369-27b666e62348', tool_call_id='call_IJvXEELx7Ir3kASCqr9dbvhU')], 'tools': 'tools'}, 'channel_versions': {'__start__': 2, 'messages': 4, 'start:agent': 3, 'agent': 4, 'branch:agent:should_continue:tools': 4, 'tools': 4}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2}, 'tools': {'branch:agent:should_continue:tools': 3}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'tools': {'messages': [ToolMessage(content='It might be cloudy in nyc', name='get_weather', id='56d4e46b-6cb3-4efe-b369-27b666e62348', tool_call_id='call_IJvXEELx7Ir3kASCqr9dbvhU')]}}, 'step': 2}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-23b5-6de6-8001-a39c8ce6fd93'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-23b5-6de6-8001-a39c8ce6fd93'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:47.601527+00:00', 'id': '1ef566b3-23b5-6de6-8001-a39c8ce6fd93', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='1ae4b12f-b1cb-4d55-a754-42cf1c2fbcd5'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b5da58b5-8f75-485d-af29-bfdeb09b0d94-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73})], 'agent': 'agent', 'branch:agent:should_continue:tools': 'agent'}, 'channel_versions': {'__start__': 2, 'messages': 3, 'start:agent': 3, 'agent': 3, 'branch:agent:should_continue:tools': 3}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}, 'agent': {'start:agent': 2}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'function': {'arguments': '{\"city\":\"nyc\"}', 'name': 'get_weather'}, 'type': 'function'}]}, response_metadata={'token_usage': {'completion_tokens': 15, 'prompt_tokens': 58, 'total_tokens': 73}, 'model_name': 'gpt-4o-mini', 'system_fingerprint': 'fp_48196bc67a', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b5da58b5-8f75-485d-af29-bfdeb09b0d94-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'nyc'}, 'id': 'call_IJvXEELx7Ir3kASCqr9dbvhU', 'type': 'tool_call'}], usage_metadata={'input_tokens': 58, 'output_tokens': 15, 'total_tokens': 73})]}}, 'step': 1}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-1d6c-6a02-8000-158f156ffce3'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-1d6c-6a02-8000-158f156ffce3'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:46.942389+00:00', 'id': '1ef566b3-1d6c-6a02-8000-158f156ffce3', 'channel_values': {'messages': [HumanMessage(content=\"what's the weather in nyc\", id='1ae4b12f-b1cb-4d55-a754-42cf1c2fbcd5')], 'start:agent': '__start__'}, 'channel_versions': {'__start__': 2, 'messages': 2, 'start:agent': 2}, 'versions_seen': {'__input__': {}, '__start__': {'__start__': 1}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'loop', 'writes': None, 'step': 0}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-1d67-61e2-bfff-d91abbcc3a09'}}, pending_writes=None),\n",
       " CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1ef566b3-1d67-61e2-bfff-d91abbcc3a09'}}, checkpoint={'v': 1, 'ts': '2024-08-09T16:19:46.940133+00:00', 'id': '1ef566b3-1d67-61e2-bfff-d91abbcc3a09', 'channel_values': {'messages': [], '__start__': {'messages': [['human', \"what's the weather in nyc\"]]}}, 'channel_versions': {'__start__': 1}, 'versions_seen': {'__input__': {}}, 'pending_sends': [], 'current_tasks': {}}, metadata={'source': 'input', 'writes': {'messages': [['human', \"what's the weather in nyc\"]]}, 'step': -1}, parent_config=None, pending_writes=None)]"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "checkpoint_tuples"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langgraph",
   "language": "python",
   "name": "langgraph"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
