diff --git a/docs/cassettes/persistence_mongodb_2e65b3b2-8e3e-4d96-8db4-4846a3bc2eac.msgpack.zlib b/docs/cassettes/persistence_mongodb_2e65b3b2-8e3e-4d96-8db4-4846a3bc2eac.msgpack.zlib
new file mode 100644
index 000000000..d331f38dc
--- /dev/null
+++ b/docs/cassettes/persistence_mongodb_2e65b3b2-8e3e-4d96-8db4-4846a3bc2eac.msgpack.zlib
@@ -0,0 +1 @@
+eNrtVwlwFFUaDgYQQUpE0UVdbMaAKOm5ejJHQsTcB0kmySQkAwlDT/ebmSY93Z0+kplgInKq4GJTgO6uuGJCYsZACKQQCKiYBZHgrshVCRhPChWXIxhXTYR9PZloArjlbmGtu+urmul+/f73X9///+/9C+vKAC9QLDOkgWJEwOOECCfCqoV1PCiVgCAurvUC0cOSNdlWW161xFPtD3pEkROiNRqco9QsBxicUhOsV1Om0xAeXNTAd44GQTY1Tpb0d4Qb56u8QBBwNxBU0cjs+SqChbIYEU5U5XDL/QIiegBSDnD44BGKQQSXKhJR8SwNFBpJALyqMhIZuJGRaHoACS4IlCDicAl+FFmWdhA4TYfEiX4uSOSSmKB5Cg1FKl8UIoe9FLea2Xws3pPiTauwzEzN85Wm+P1ZCtn3W6KhdAb3Bvm4gegIKavQ4Lxb8kKtFGmq+UUqghL9RfC9SCW4ilSVqsrK4iuUV6UpNuN0Oe4XEEFiGP/VRitGDDLG8VNUvlJQ3mC/2nAGSeZxhqAEgkWowTpMVF3ToVey/OmIFcMvXpYEdNBpnIgaWNRLMZRCqXhUB5+CyAPcCycunBaAYi/wcjAORYlXOGnV2pAP/gmWPwUkEggET3EhMlW+AKAF0AEii0DKAZa4WN6LK2RqZRuH85AfTAwhyJzjYcDzIgX6pgrSwRfASIoNs1WMn1C2QW8oxvcrC42kGDcMBMVFMK8oHpBB8iCDgZSscx4goM+VmKnzAJyEklfWeFhBlDcNzrdGnCAA9ClgCJaE3OWN7gqKi0RI4KJxEQQgYgwIukUOlADAoThNlYHavl3yZpzjaIoIGqqZJ7BMQwhhVNHk6uWAgigKM5gR5WYrVCIuTZPth4WBQXTqKJNav9mHwnChGBomOkrjUJ9aLrjeMnCBw4kSyAQNFR25tm/zpoE0rCBvyMQJq20QS5wnPPIGnPcaDVsHfuclRqS8QK5LyL5aXGjxB3GYWqdTW5oGMRb8DCFvCIbfK4M2A5H3owQLecjrtZv6/UMDxi165GqzXv8SDwQOljmwqBZuEyVhYQ3EAhzcXxcqdy9aZ/SD2Bl2Z00ixEXeneeRIhGdHkkEBKLX6g3wL1pvjjYYkZTMvIaEkJi8a8LQlAeTV3BBKJL6Ya8jPBJTAshAwjUB360ADq1R1IfZiQIfxwoADWklNxSiuX2FHk1L3NoXXSjLu3GGqgiKlesVMGFhp5jm0DJMAoUlFI56Bbka0xs3hVb6/RyAdmlRnRbV6nb6UJjMgKa8FPRd8D90skCYMS0c26+mENkSwAhyvS5K2zdeHUjDAy/URhH/Aye9BY5d16b6nptBIbIYLDsH0wlggE7Veq+w/er1EI8XtUKDr58YpUi5PQJOHFEYabIAkxPocAzoCYzEAInpCDNBGg2kyejcoRQAAnJR0ONYXkQFQMCzVPTL7ZFe3KckViymi8KM0NYYWIQIWiKBTXImsooRQgzC8YBmcbIxIRlNwAkPQG3BgJPrEu1ZcZlpCQEbVDKBZUsosKpjSLjDQbgcTm8siXMFXJ43iSoo5SRXjt2Vk5mcJaktGXnqBBPNYmwFyDdklJTEpwuozoQZtFgUhBPVqbVqnVqHxjml+CzXvJl+u1CYM5Ow22ZadfmMTZ2Rk5OdKZlKUsy0I97h9cflz7CWFgiEKc5KuvN5nb+EFjJovTOzQJvlmFUelZGfFUVZQWFiOuak46A1sNjGamIQGIywFAqxoZRAYUqgfQkR1Z8QMQgZ9EGsenD5i0FS4TXEytD+GMSmOBPAJyzVNkoEsVksA9pXQx9IZRQZy+XT8Y50fXJSSsUsbUkioeUpLNNvKWdS6Fxzeirn85Wm2vSJaRW+pAFOMBgwVBvyg1FrMAfj8AfV/02tthWiAzMctQZPJIgjwwoM5XLV2gAPs0gOEDQrkbCS86AWYp4bZ5ebzS6d1mQ0YUYLsJgJLYEmFeRu7uf2fT2oUY6BOpyGMVZGyFs9WKwqGtqiikG8eKzZaNBqg7eyx2r7DqW9Q764d/mIsOAIh7/Ll1fYMhv/qB2z6+LUhthlTw4dO5Y5WjK1u2XN84GvosbxB29gVnjbkzVTDjzauGBo6h/ST821x8Rsbfn41Ke33jBt0eKwqfXEmjsutdGvJc7Z0vpmd2uF915HY9d7S6u+69r2zZjdj9nbslve339ySffC+vRRYtFbE1zD61tnShOW5YxRt+4NhD+4Zr2HWlJWVdP75IyzmoVJn9zYNN3xp/YNH92MRnxSlzJi4ptn6Z7V4ycgE/d8Mbb+nVmzkIiEsRPnrvbc9/DGU8dyPptrXJiRtmyLt/hYfEZY3erU8eOckScfffOtSsPykbL6mVvePlx+oVd8fHllUVXP7Zd83d9UXmByG59hxOPZc2YYnzXsWebjbSvGPN38+fZhO78cXz/10Ml3bhoy78md8Z91HPtw+7B9xVuXkKOM779x06Z3rZ27exqfuG34xY7c0m2f3jN99POj8j3VZV/ti9+/45NtGaNzTzyll4605J35/OHKbs8LTwWaZ6zrqKbB6biTL7+xvfA2Q/SiLMuDky/sfaQsRfP2Ozmduz/fnLpiy9itcrT69o3YpskLTL//7hxzlvr7RcHeu7J3hHZpB7523qLx7uO3raSGn7h81kEtaitunXv3mc3pgnqUoeM8c2bicWr7+pzXjVPvWlDedSkIaXhYS/LagvChYWHX814/tOjXe/3/5b3+Z0MsKirBP6uQLp/lM7lLTJ6ZWZg90TrDnf4LRuxHVf61+7k+3U9n2Jhf+5//fP9TSwQvl3J71y/8bvkz3Pqu7P1qdDqd8V9r/u74X23+DJb/ruZPf92bPz0wWwCBARyzYHozoTc6jTod7jRaSJxwkU7Tz9/8XYemAmprNl7HpmL9lU1FbubKw9rbd50umPYIk/2uqzyHXZ632LxxaCqyWHW8Ki1jnfvj0y+tzr8cw38r5y1ovvNeV+cKV3LVX8IWH3KOjpf0gc4TvTnnznK/+ej0K9Nfozs1pj9b53/7LfkQ/ca5dYURS05cenicJnD8QHen0/C7rOaRU+6bfLwLzx/3csvRrqqIirpDk+1niDk7dpnILtDU0PXRRM37zx1dh420N3w4e3jY+WmvPnA/1TvpWHakOnXlwS1JTa9OGPL4B3T8YtJ4z6SkpmdTT01RPXVoujRi/uqm1rClH1RH+O6MeKD1wGP402WaW26eNua3c96OvcEY3nZk0fCSv3594a4DO4/d+Mop7NG9dycsbYl+gUx5ICzl9N79E7AF54hvPqxdeX5P90PHDv3t6VX4jkBWlbPjkX3PP3ProSTK7ZNy3vv66F3VhrXuizdNyl9WsSprdlvc3ab7ttjPvHa4t63nyMnsnrE9T2SPrEws7lnQ8dKJfYftr3856XJ5WbH/5sivX7+1Z6fuiPP8nuceSvz4TE/ymmFrhzFJZQf7O4Epo9vCJ0BQ/gH9HBvK
\ No newline at end of file
diff --git a/docs/cassettes/persistence_mongodb_5fe54e79-9eaf-44e2-b2d9-1e0284b984d0.msgpack.zlib b/docs/cassettes/persistence_mongodb_5fe54e79-9eaf-44e2-b2d9-1e0284b984d0.msgpack.zlib
deleted file mode 100644
index 2856be5a5..000000000
--- a/docs/cassettes/persistence_mongodb_5fe54e79-9eaf-44e2-b2d9-1e0284b984d0.msgpack.zlib
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/docs/cassettes/persistence_mongodb_623b81ea-9415-4c49-9ded-9c7830a3ef6b.msgpack.zlib b/docs/cassettes/persistence_mongodb_623b81ea-9415-4c49-9ded-9c7830a3ef6b.msgpack.zlib
new file mode 100644
index 000000000..c1ea230c2
--- /dev/null
+++ b/docs/cassettes/persistence_mongodb_623b81ea-9415-4c49-9ded-9c7830a3ef6b.msgpack.zlib
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/cassettes/persistence_mongodb_6a39d1ff-ca37-4457-8b52-07d33b59c36e.msgpack.zlib b/docs/cassettes/persistence_mongodb_6a39d1ff-ca37-4457-8b52-07d33b59c36e.msgpack.zlib
deleted file mode 100644
index 4063f4d7b..000000000
--- a/docs/cassettes/persistence_mongodb_6a39d1ff-ca37-4457-8b52-07d33b59c36e.msgpack.zlib
+++ /dev/null
@@ -1 +0,0 @@
-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
\ No newline at end of file
diff --git a/docs/cassettes/persistence_mongodb_e3a9889d-7a60-455f-96d6-95a8a2e7dbf6.msgpack.zlib b/docs/cassettes/persistence_mongodb_e3a9889d-7a60-455f-96d6-95a8a2e7dbf6.msgpack.zlib
new file mode 100644
index 000000000..f5647be04
--- /dev/null
+++ b/docs/cassettes/persistence_mongodb_e3a9889d-7a60-455f-96d6-95a8a2e7dbf6.msgpack.zlib
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/cassettes/persistence_mongodb_f7aaec32-1755-4ae4-a40b-ade58491a5bb.msgpack.zlib b/docs/cassettes/persistence_mongodb_f7aaec32-1755-4ae4-a40b-ade58491a5bb.msgpack.zlib
new file mode 100644
index 000000000..0d507d7f2
--- /dev/null
+++ b/docs/cassettes/persistence_mongodb_f7aaec32-1755-4ae4-a40b-ade58491a5bb.msgpack.zlib
@@ -0,0 +1 @@
+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
\ No newline at end of file
diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md
index 24083d328..1651f07be 100644
--- a/docs/docs/how-tos/index.md
+++ b/docs/docs/how-tos/index.md
@@ -30,7 +30,7 @@ These how-to guides show how to achieve that controllability.
- [How to add thread-level persistence to subgraphs](subgraph-persistence.ipynb)
- [How to add cross-thread persistence to your graph](cross-thread-persistence.ipynb)
- [How to use Postgres checkpointer for persistence](persistence_postgres.ipynb)
-- [How to create a custom checkpointer using MongoDB](persistence_mongodb.ipynb)
+- [How to use MongoDB checkpointer for persistence](persistence_mongodb.ipynb)
- [How to create a custom checkpointer using Redis](persistence_redis.ipynb)
### Memory
diff --git a/docs/docs/how-tos/persistence_mongodb.ipynb b/docs/docs/how-tos/persistence_mongodb.ipynb
index c8b7bef4c..5b8350595 100644
--- a/docs/docs/how-tos/persistence_mongodb.ipynb
+++ b/docs/docs/how-tos/persistence_mongodb.ipynb
@@ -5,7 +5,7 @@
"id": "51466c8d-8ce4-4b3d-be4e-18fdbeda5f53",
"metadata": {},
"source": [
- "# How to create a custom checkpointer using MongoDB\n",
+ "# How to use MongoDB checkpointer for persistence\n",
"\n",
"
\n",
"
Prerequisites
\n",
@@ -28,23 +28,16 @@
"\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",
+ "This reference implementation shows how to use MongoDB as the backend for persisting checkpoint state using the `langgraph-checkpoint-mongodb` library.\n",
"\n",
- "
\n",
- "
Note
\n",
- "
\n",
- " This is a **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.\n",
- "
\n",
- "
\n",
- "\n",
- "For demonstration purposes we add persistence to the [pre-built create react agent](https://langchain-ai.github.io/langgraph/reference/prebuilt/#langgraph.prebuilt.chat_agent_executor.create_react_agent).\n",
+ "For demonstration purposes we add persistence to a [prebuilt ReAct agent](https://langchain-ai.github.io/langgraph/how-tos/create-react-agent/).\n",
"\n",
"In general, you can add a checkpointer to any custom graph that you build like this:\n",
"\n",
"```python\n",
"from langgraph.graph import StateGraph\n",
"\n",
- "builder = StateGraph(....)\n",
+ "builder = StateGraph(...)\n",
"# ... define the graph\n",
"checkpointer = # mongodb checkpointer (see examples below)\n",
"graph = builder.compile(checkpointer=checkpointer)\n",
@@ -59,7 +52,9 @@
"source": [
"## Setup\n",
"\n",
- "First let's install the required packages and set our API keys"
+ "To use the MongoDB checkpointer, you will need a MongoDB cluster. Follow [this guide](https://www.mongodb.com/docs/guides/atlas/cluster/) to create a cluster if you don't already have one.\n",
+ "\n",
+ "Next, let's install the required packages and set our API keys"
]
},
{
@@ -70,15 +65,23 @@
"outputs": [],
"source": [
"%%capture --no-stderr\n",
- "%pip install -U pymongo motor langgraph"
+ "%pip install -U pymongo langgraph langgraph-checkpoint-mongodb"
]
},
{
"cell_type": "code",
- "execution_count": null,
+ "execution_count": 2,
"id": "eca9aafb-a155-407a-8036-682a2f1297d7",
"metadata": {},
- "outputs": [],
+ "outputs": [
+ {
+ "name": "stdin",
+ "output_type": "stream",
+ "text": [
+ "OPENAI_API_KEY: ········\n"
+ ]
+ }
+ ],
"source": [
"import getpass\n",
"import os\n",
@@ -94,7 +97,7 @@
},
{
"cell_type": "markdown",
- "id": "3080e508",
+ "id": "9a657ea8-fd68-4116-a484-d88b0a906888",
"metadata": {},
"source": [
"
\n",
@@ -107,594 +110,21 @@
},
{
"cell_type": "markdown",
- "id": "ecb23436-f238-4f8c-a2b7-67c7956121e2",
+ "id": "e26b3204-cca2-414c-800e-7e09032445ae",
"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."
+ "## Define model and tools for the graph"
]
},
{
"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)"
- ]
- },
- {
- "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",
+ "id": "ce7ccf56-9914-4557-97b8-13c95f3b7edc",
"metadata": {},
"outputs": [],
"source": [
"from typing import Literal\n",
- "from langchain_core.runnables import ConfigurableField\n",
+ "\n",
"from langchain_core.tools import tool\n",
"from langchain_openai import ChatOpenAI\n",
"from langgraph.prebuilt import create_react_agent\n",
@@ -717,60 +147,105 @@
},
{
"cell_type": "markdown",
- "id": "e9342c62-dbb4-40f6-9271-7393f1ca48c4",
+ "id": "d92ba022",
"metadata": {},
"source": [
- "## Use sync connection"
+ "## MongoDB checkpointer usage"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "a3b78544",
+ "metadata": {},
+ "source": [
+ "### With a connection string\n",
+ "\n",
+ "This creates a connection to MongoDB directly using the connection string of your cluster. This is ideal for use in scripts, one-off operations and short-lived applications."
]
},
{
"cell_type": "code",
- "execution_count": 6,
- "id": "5fe54e79-9eaf-44e2-b2d9-1e0284b984d0",
+ "execution_count": 4,
+ "id": "2e65b3b2-8e3e-4d96-8db4-4846a3bc2eac",
"metadata": {},
"outputs": [],
"source": [
- "with MongoDBSaver.from_conn_info(\n",
- " host=\"localhost\", port=27017, db_name=\"checkpoints\"\n",
- ") as checkpointer:\n",
+ "from langgraph.checkpoint.mongodb import MongoDBSaver\n",
+ "\n",
+ "MONGODB_URI = \"localhost:27017\" # replace this with your connection string\n",
+ "\n",
+ "with MongoDBSaver.from_conn_string(MONGODB_URI) 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))"
+ " response = graph.invoke(\n",
+ " {\"messages\": [(\"human\", \"what's the weather in sf\")]}, config\n",
+ " )"
]
},
{
"cell_type": "code",
- "execution_count": 7,
- "id": "c298e627-115a-4b4c-ae17-520ca9a640cd",
+ "execution_count": 5,
+ "id": "dfcb7da8",
"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': {}}"
+ "{'messages': [HumanMessage(content=\"what's the weather in sf\", additional_kwargs={}, response_metadata={}, id='729afd6a-fdc0-4192-a255-1dac065c79b2'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_YqaO8oU3BhGmIz9VHTxqGyyN', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_39a40c96a0', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-b45c0c12-c68e-4392-92dd-5d325d0a9f60-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_YqaO8oU3BhGmIz9VHTxqGyyN', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
+ " ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='0c72eb29-490b-44df-898f-8454c314eac1', tool_call_id='call_YqaO8oU3BhGmIz9VHTxqGyyN'),\n",
+ " AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_818c284075', 'finish_reason': 'stop', 'logprobs': None}, id='run-33f54c91-0ba9-48b7-9b25-5a972bbdeea9-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}"
+ ]
+ },
+ "execution_count": 5,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "response"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "fe8fcb47",
+ "metadata": {},
+ "source": [
+ "### Using the MongoDB client\n",
+ "\n",
+ "This creates a connection to MongoDB using the MongoDB client. This is ideal for long-running applications since it allows you to reuse the client instance for multiple database operations without needing to reinitialize the connection each time."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "e3a9889d-7a60-455f-96d6-95a8a2e7dbf6",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from pymongo import MongoClient\n",
+ "\n",
+ "mongodb_client = MongoClient(MONGODB_URI)\n",
+ "\n",
+ "checkpointer = MongoDBSaver(mongodb_client)\n",
+ "graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
+ "config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
+ "response = graph.invoke({\"messages\": [(\"user\", \"What's the weather in sf?\")]}, config)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "fd2a16ad",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "{'messages': [HumanMessage(content=\"What's the weather in sf?\", additional_kwargs={}, response_metadata={}, id='4ce68bee-a843-4b08-9c02-7a0e3b010110'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_MvGxq9IU9wvW9mfYKSALHtGu', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-9712c5a4-376c-4812-a0c4-1b522334a59d-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_MvGxq9IU9wvW9mfYKSALHtGu', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
+ " ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='b4eed38d-bcaf-4497-ad08-f21ccd6a8c30', tool_call_id='call_MvGxq9IU9wvW9mfYKSALHtGu'),\n",
+ " AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-c6c4ad75-89ef-4b4f-9ca4-bd52ccb0729b-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}"
]
},
"execution_count": 7,
@@ -779,19 +254,19 @@
}
],
"source": [
- "latest_checkpoint"
+ "response"
]
},
{
"cell_type": "code",
"execution_count": 8,
- "id": "922f9406-0f68-418a-9cb4-e0e29de4b5f9",
+ "id": "a0f28d9b",
"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=[])"
+ "CheckpointTuple(config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1efb8c75-9262-68b4-8003-1ac1ef198757'}}, checkpoint={'v': 1, 'ts': '2024-12-12T20:26:20.545003+00:00', 'id': '1efb8c75-9262-68b4-8003-1ac1ef198757', 'channel_values': {'messages': [HumanMessage(content=\"What's the weather in sf?\", additional_kwargs={}, response_metadata={}, id='4ce68bee-a843-4b08-9c02-7a0e3b010110'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_MvGxq9IU9wvW9mfYKSALHtGu', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-9712c5a4-376c-4812-a0c4-1b522334a59d-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_MvGxq9IU9wvW9mfYKSALHtGu', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='b4eed38d-bcaf-4497-ad08-f21ccd6a8c30', tool_call_id='call_MvGxq9IU9wvW9mfYKSALHtGu'), AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-c6c4ad75-89ef-4b4f-9ca4-bd52ccb0729b-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})], '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': []}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-c6c4ad75-89ef-4b4f-9ca4-bd52ccb0729b-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}, 'thread_id': '2', 'step': 3, 'parents': {}}, parent_config={'configurable': {'thread_id': '2', 'checkpoint_ns': '', 'checkpoint_id': '1efb8c75-8d89-6ffe-8002-84a4312c4fed'}}, pending_writes=[])"
]
},
"execution_count": 8,
@@ -800,92 +275,64 @@
}
],
"source": [
- "latest_checkpoint_tuple"
+ "# Retrieve the latest checkpoint for the given thread ID\n",
+ "# To retrieve a specific checkpoint, pass the checkpoint_id in the config\n",
+ "checkpointer.get_tuple(config)"
]
},
{
"cell_type": "code",
"execution_count": 9,
- "id": "b2ce743b-5896-443b-9ec0-a655b065895c",
+ "id": "935a00bf-fc2f-48d4-a8d7-56900a7071e2",
"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"
- }
- ],
+ "outputs": [],
"source": [
- "checkpoint_tuples"
+ "# Remember to close the connection after you're done\n",
+ "mongodb_client.close()"
]
},
{
"cell_type": "markdown",
- "id": "c0a47d3e-e588-48fc-a5d4-2145dff17e77",
+ "id": "47c33104",
"metadata": {},
"source": [
- "## Use async connection"
+ "### Using an async connection\n",
+ "\n",
+ "This creates a short-lived asynchronous connection to MongoDB. \n",
+ "\n",
+ "Async connections allow non-blocking database operations. This means other parts of your application can continue running while waiting for database operations to complete. It's particularly useful in high-concurrency scenarios or when dealing with I/O-bound operations."
]
},
{
"cell_type": "code",
"execution_count": 10,
- "id": "6a39d1ff-ca37-4457-8b52-07d33b59c36e",
+ "id": "f7aaec32-1755-4ae4-a40b-ade58491a5bb",
"metadata": {},
"outputs": [],
"source": [
- "async with AsyncMongoDBSaver.from_conn_info(\n",
- " host=\"localhost\", port=27017, db_name=\"checkpoints\"\n",
- ") as checkpointer:\n",
- " graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
- " config = {\"configurable\": {\"thread_id\": \"2\"}}\n",
- " res = await graph.ainvoke(\n",
- " {\"messages\": [(\"human\", \"what's the weather in nyc\")]}, config\n",
- " )\n",
+ "from langgraph.checkpoint.mongodb.aio import AsyncMongoDBSaver\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)]"
+ "async with AsyncMongoDBSaver.from_conn_string(MONGODB_URI) as checkpointer:\n",
+ " graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
+ " config = {\"configurable\": {\"thread_id\": \"3\"}}\n",
+ " response = await graph.ainvoke(\n",
+ " {\"messages\": [(\"user\", \"What's the weather in sf?\")]}, config\n",
+ " )"
]
},
{
"cell_type": "code",
"execution_count": 11,
- "id": "51125ef1-bdb6-454e-82cc-4ae19a113606",
+ "id": "40810610",
"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': {}}"
+ "{'messages': [HumanMessage(content=\"What's the weather in sf?\", additional_kwargs={}, response_metadata={}, id='fed70fe6-1b2e-4481-9bfc-063df3b587dc'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_miRiF3vPQv98wlDHl6CeRxBy', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-7f2d5153-973e-4a9e-8b71-a77625c342cf-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_miRiF3vPQv98wlDHl6CeRxBy', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
+ " ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='49035e8e-8aee-4d9d-88ab-9a1bc10ecbd3', tool_call_id='call_miRiF3vPQv98wlDHl6CeRxBy'),\n",
+ " AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-9403d502-391e-4407-99fd-eec8ed184e50-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}"
]
},
"execution_count": 11,
@@ -894,44 +341,51 @@
}
],
"source": [
- "latest_checkpoint"
+ "response"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "c9c39f64",
+ "metadata": {},
+ "source": [
+ "### Using the async MongoDB client\n",
+ "\n",
+ "This routes connections to MongoDB through an asynchronous MongoDB client."
]
},
{
"cell_type": "code",
"execution_count": 12,
- "id": "97f8a87b-8423-41c6-a76b-9a6b30904e73",
+ "id": "623b81ea-9415-4c49-9ded-9c7830a3ef6b",
"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"
- }
- ],
+ "outputs": [],
"source": [
- "latest_checkpoint_tuple"
+ "from pymongo import AsyncMongoClient\n",
+ "\n",
+ "async_mongodb_client = AsyncMongoClient(MONGODB_URI)\n",
+ "\n",
+ "checkpointer = AsyncMongoDBSaver(async_mongodb_client)\n",
+ "graph = create_react_agent(model, tools=tools, checkpointer=checkpointer)\n",
+ "config = {\"configurable\": {\"thread_id\": \"4\"}}\n",
+ "response = await graph.ainvoke(\n",
+ " {\"messages\": [(\"user\", \"What's the weather in sf?\")]}, config\n",
+ ")"
]
},
{
"cell_type": "code",
"execution_count": 13,
- "id": "2b6d73ca-519e-45f7-90c2-1b8596624505",
+ "id": "80ec0420",
"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)]"
+ "{'messages': [HumanMessage(content=\"What's the weather in sf?\", additional_kwargs={}, response_metadata={}, id='58282e2b-4cc1-40a1-8e65-420a2177bbd6'),\n",
+ " AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_SJFViVHl1tYTZDoZkNN3ePhJ', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_bba3c8e70b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-131af8c1-d388-4d7f-9137-da59ebd5fefd-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_SJFViVHl1tYTZDoZkNN3ePhJ', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}),\n",
+ " ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='6090a56f-177b-4d3f-b16a-9c05f23800e3', tool_call_id='call_SJFViVHl1tYTZDoZkNN3ePhJ'),\n",
+ " AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-6ff5ddf5-6e13-4126-8df9-81c8638355fc-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}"
]
},
"execution_count": 13,
@@ -940,7 +394,39 @@
}
],
"source": [
- "checkpoint_tuples"
+ "response"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 14,
+ "id": "a948dcd4",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "CheckpointTuple(config={'configurable': {'thread_id': '4', 'checkpoint_ns': '', 'checkpoint_id': '1efb8c76-21f4-6d10-8003-9496e1754e93'}}, checkpoint={'v': 1, 'ts': '2024-12-12T20:26:35.599560+00:00', 'id': '1efb8c76-21f4-6d10-8003-9496e1754e93', 'channel_values': {'messages': [HumanMessage(content=\"What's the weather in sf?\", additional_kwargs={}, response_metadata={}, id='58282e2b-4cc1-40a1-8e65-420a2177bbd6'), AIMessage(content='', additional_kwargs={'tool_calls': [{'id': 'call_SJFViVHl1tYTZDoZkNN3ePhJ', 'function': {'arguments': '{\"city\":\"sf\"}', 'name': 'get_weather'}, 'type': 'function'}], 'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 14, 'prompt_tokens': 57, 'total_tokens': 71, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_bba3c8e70b', 'finish_reason': 'tool_calls', 'logprobs': None}, id='run-131af8c1-d388-4d7f-9137-da59ebd5fefd-0', tool_calls=[{'name': 'get_weather', 'args': {'city': 'sf'}, 'id': 'call_SJFViVHl1tYTZDoZkNN3ePhJ', 'type': 'tool_call'}], usage_metadata={'input_tokens': 57, 'output_tokens': 14, 'total_tokens': 71, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}}), ToolMessage(content=\"It's always sunny in sf\", name='get_weather', id='6090a56f-177b-4d3f-b16a-9c05f23800e3', tool_call_id='call_SJFViVHl1tYTZDoZkNN3ePhJ'), AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-6ff5ddf5-6e13-4126-8df9-81c8638355fc-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})], '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': []}, metadata={'source': 'loop', 'writes': {'agent': {'messages': [AIMessage(content='The weather in San Francisco is always sunny!', additional_kwargs={'refusal': None}, response_metadata={'token_usage': {'completion_tokens': 10, 'prompt_tokens': 84, 'total_tokens': 94, 'completion_tokens_details': {'accepted_prediction_tokens': 0, 'audio_tokens': 0, 'reasoning_tokens': 0, 'rejected_prediction_tokens': 0}, 'prompt_tokens_details': {'audio_tokens': 0, 'cached_tokens': 0}}, 'model_name': 'gpt-4o-mini-2024-07-18', 'system_fingerprint': 'fp_6fc10e10eb', 'finish_reason': 'stop', 'logprobs': None}, id='run-6ff5ddf5-6e13-4126-8df9-81c8638355fc-0', usage_metadata={'input_tokens': 84, 'output_tokens': 10, 'total_tokens': 94, 'input_token_details': {'audio': 0, 'cache_read': 0}, 'output_token_details': {'audio': 0, 'reasoning': 0}})]}}, 'thread_id': '4', 'step': 3, 'parents': {}}, parent_config={'configurable': {'thread_id': '4', 'checkpoint_ns': '', 'checkpoint_id': '1efb8c76-1c6c-6474-8002-9c2595cd481c'}}, pending_writes=[])\n"
+ ]
+ }
+ ],
+ "source": [
+ "# Retrieve the latest checkpoint for the given thread ID\n",
+ "# To retrieve a specific checkpoint, pass the checkpoint_id in the config\n",
+ "latest_checkpoint = await checkpointer.aget_tuple(config)\n",
+ "print(latest_checkpoint)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 15,
+ "id": "68ee7400-645d-4d00-b118-554698d8496a",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Remember to close the connection after you're done\n",
+ "await async_mongodb_client.close()"
]
}
],
@@ -960,7 +446,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.11.4"
+ "version": "3.12.3"
}
},
"nbformat": 4,
diff --git a/poetry.lock b/poetry.lock
index f25351691..9b0fe29f8 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -3035,7 +3035,7 @@ langchain-core = ">=0.3.0,<0.4.0"
[[package]]
name = "langgraph"
-version = "0.2.57"
+version = "0.2.59"
description = "Building stateful, multi-actor applications with LLMs"
optional = false
python-versions = ">=3.9.0,<4.0"
@@ -3068,6 +3068,23 @@ msgpack = "^1.1.0"
type = "directory"
url = "libs/checkpoint"
+[[package]]
+name = "langgraph-checkpoint-mongodb"
+version = "0.1.0"
+description = "Library with a MongoDB implementation of LangGraph checkpoint saver."
+optional = false
+python-versions = "<4.0.0,>=3.9.0"
+files = [
+ {file = "langgraph_checkpoint_mongodb-0.1.0-py3-none-any.whl", hash = "sha256:52f20956b36e0275ff805a1eea1db4c1a7e5e0ffe0a1ade65969004fa1654703"},
+ {file = "langgraph_checkpoint_mongodb-0.1.0.tar.gz", hash = "sha256:3165c134ad5c82a3fe02fef04c81dcd48a3f5d031e07a9d1cb84457241f76793"},
+]
+
+[package.dependencies]
+langgraph = ">=0.2.38,<0.3.0"
+langgraph-checkpoint = ">=2.0.0,<3.0.0"
+motor = ">3.5.0"
+pymongo = ">=4.9.0,<4.10.0"
+
[[package]]
name = "langgraph-checkpoint-postgres"
version = "2.0.8"
@@ -7468,4 +7485,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
-content-hash = "cf18eed5e183fc4f7786d095540b6c9261e130750f2d1fcc427e08b78d522c61"
+content-hash = "367f5fb480a8fa5d8ab1c0964a1e9450dbb28e6998097e7536966e7a5fe30c90"
diff --git a/pyproject.toml b/pyproject.toml
index c198ae5e4..ed09fc121 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -41,6 +41,7 @@ langchain-nomic = "^0.1.3"
langchain-fireworks = "^0.2.0"
langchain-community = "^0.3.0"
langchain-experimental = "^0.3.2"
+langgraph-checkpoint-mongodb = "^0.1.0"
langsmith = "^0.1.129"
chromadb = "^0.5.5"
gpt4all = "^2.8.2"