Skip to content

Commit

Permalink
docs: update subgraph how-to (#2079)
Browse files Browse the repository at this point in the history
Co-authored-by: vbarda <[email protected]>
  • Loading branch information
isahers1 and vbarda authored Oct 15, 2024
1 parent 2670bcf commit 18f34c3
Show file tree
Hide file tree
Showing 5 changed files with 572 additions and 544 deletions.
5 changes: 3 additions & 2 deletions docs/docs/how-tos/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ These how-to guides show how to achieve that controllability.
LangGraph makes it easy to persist state across graph runs (thread-level persistence) and across threads (cross-thread persistence). These how-to guides show how to add persistence to your graph.

- [How to add thread-level persistence to your graph](persistence.ipynb)
- [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)
Expand Down Expand Up @@ -73,8 +74,8 @@ These guides show how to use different streaming modes.

## Subgraphs

- [How to create subgraphs](subgraph.ipynb)
- [How to manage state in subgraphs](subgraphs-manage-state.ipynb)
- [How to add and use subgraphs](subgraph.ipynb)
- [How to view and update state in subgraphs](subgraphs-manage-state.ipynb)
- [How to transform inputs and outputs of a subgraph](subgraph-transform-state.ipynb)

## State Management
Expand Down
379 changes: 379 additions & 0 deletions docs/docs/how-tos/subgraph-persistence.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
{
"cells": [
{
"cell_type": "markdown",
"id": "176e8dbb-1a0a-49ce-a10e-2417e8ea17a0",
"metadata": {},
"source": [
"# How to add thread-level persistence to subgraphs"
]
},
{
"cell_type": "markdown",
"id": "8c67581a-49fb-4597-a7fc-6774581c2160",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Prerequisites</p>\n",
" <p>\n",
" This guide assumes familiarity with the following:\n",
" <ul>\n",
" <li> \n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/low_level/#subgraphs\">\n",
" Subgraphs\n",
" </a>\n",
" </li>\n",
" <li>\n",
" <a href=\"https://langchain-ai.github.io/langgraph/concepts/persistence/\">\n",
" Persistence\n",
" </a>\n",
" </li>\n",
" </ul>\n",
" </p>\n",
"</div>\n",
"\n",
"This guide shows how you can add [thread-level](https://langchain-ai.github.io/langgraph/how-tos/persistence/) persistence to graphs that use [subgraphs](https://langchain-ai.github.io/langgraph/how-tos/subgraph/)."
]
},
{
"cell_type": "markdown",
"id": "8f83b855-ab23-4de7-9559-702cad9a29c6",
"metadata": {},
"source": [
"## Setup\n",
"\n",
"First, let's install the required packages"
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "77d1eafa-3252-45f6-9af0-d94e1f9c5c9e",
"metadata": {},
"outputs": [],
"source": [
"%%capture --no-stderr\n",
"%pip install -U langgraph"
]
},
{
"cell_type": "markdown",
"id": "2e60c6cd-bf4e-46af-9761-b872d0fbe3b6",
"metadata": {},
"source": [
"<div class=\"admonition tip\">\n",
" <p class=\"admonition-title\">Set up <a href=\"https://smith.langchain.com\">LangSmith</a> for LangGraph development</p>\n",
" <p style=\"padding-top: 5px;\">\n",
" Sign up for LangSmith to quickly spot issues and improve the performance of your LangGraph projects. LangSmith lets you use trace data to debug, test, and monitor your LLM apps built with LangGraph — read more about how to get started <a href=\"https://docs.smith.langchain.com\">here</a>. \n",
" </p>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "871b9056-fec7-4683-8c22-f56c91f5b13b",
"metadata": {},
"source": [
"## Define the graph with persistence"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "9f1303ef-df37-48e0-8a59-8ff169c52c5b",
"metadata": {},
"source": [
"To add persistence to a graph with subgraphs, all you need to do is pass a [checkpointer](https://langchain-ai.github.io/langgraph/reference/checkpoints/#langgraph.checkpoint.base.BaseCheckpointSaver) when **compiling the parent graph**. LangGraph will automatically propagate the checkpointer to the child subgraphs."
]
},
{
"cell_type": "markdown",
"id": "c74cde2e-c127-4326-8d36-b6acef987f0a",
"metadata": {},
"source": [
"!!! note\n",
" You **shouldn't provide** a checkpointer when compiling a subgraph. Instead, you must define a **single** checkpointer that you pass to `parent_graph.compile()`, and LangGraph will automatically propagate the checkpointer to the child subgraphs. If you pass the checkpointer to the `subgraph.compile()`, it will simply be ignored. This also applies when you [add a node function that invokes the subgraph](../subgraph#add-a-node-function-that-invokes-the-subgraph)."
]
},
{
"cell_type": "markdown",
"id": "c3a1fe22-1ca9-45eb-a35b-71b9c905e8c5",
"metadata": {},
"source": [
"Let's define a simple graph with a single subgraph node to show how to do this."
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "0d76f0c0-bd77-4eca-9527-27bcdf85dd42",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"<langgraph.graph.state.StateGraph at 0x106d2fa10>"
]
},
"execution_count": 2,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from langgraph.graph import START, StateGraph\n",
"from langgraph.checkpoint.memory import MemorySaver\n",
"from typing import TypedDict\n",
"\n",
"\n",
"# subgraph\n",
"\n",
"\n",
"class SubgraphState(TypedDict):\n",
" foo: str # note that this key is shared with the parent graph state\n",
" bar: str\n",
"\n",
"\n",
"def subgraph_node_1(state: SubgraphState):\n",
" return {\"bar\": \"bar\"}\n",
"\n",
"\n",
"def subgraph_node_2(state: SubgraphState):\n",
" # note that this node is using a state key ('bar') that is only available in the subgraph\n",
" # and is sending update on the shared state key ('foo')\n",
" return {\"foo\": state[\"foo\"] + state[\"bar\"]}\n",
"\n",
"\n",
"subgraph_builder = StateGraph(SubgraphState)\n",
"subgraph_builder.add_node(subgraph_node_1)\n",
"subgraph_builder.add_node(subgraph_node_2)\n",
"subgraph_builder.add_edge(START, \"subgraph_node_1\")\n",
"subgraph_builder.add_edge(\"subgraph_node_1\", \"subgraph_node_2\")\n",
"subgraph = subgraph_builder.compile()\n",
"\n",
"\n",
"# parent graph\n",
"\n",
"\n",
"class State(TypedDict):\n",
" foo: str\n",
"\n",
"\n",
"def node_1(state: State):\n",
" return {\"foo\": \"hi! \" + state[\"foo\"]}\n",
"\n",
"\n",
"builder = StateGraph(State)\n",
"builder.add_node(\"node_1\", node_1)\n",
"# note that we're adding the compiled subgraph as a node to the parent graph\n",
"builder.add_node(\"node_2\", subgraph)\n",
"builder.add_edge(START, \"node_1\")\n",
"builder.add_edge(\"node_1\", \"node_2\")"
]
},
{
"cell_type": "markdown",
"id": "47084b1f-9fd5-40a9-9d75-89eb5f853d02",
"metadata": {},
"source": [
"We can now compile the graph with an in-memory checkpointer (`MemorySaver`)."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "7657d285-c896-40c9-a569-b4a3b9c230c7",
"metadata": {},
"outputs": [],
"source": [
"checkpointer = MemorySaver()\n",
"# You must only pass checkpointer when compiling the parent graph.\n",
"# LangGraph will automatically propagate the checkpointer to the child subgraphs.\n",
"graph = builder.compile(checkpointer=checkpointer)"
]
},
{
"cell_type": "markdown",
"id": "0d193e3c-4ec3-4034-beed-8e5550c6542c",
"metadata": {},
"source": [
"## Verify persistence works"
]
},
{
"cell_type": "markdown",
"id": "eb69a5f0-b92e-4d4e-9aa9-c4c4ec7de91a",
"metadata": {},
"source": [
"Let's now run the graph and inspect the persisted state for both the parent graph and the subgraph to verify that persistence works. We should expect to see the final execution results for both the parent and subgraph in `state.values`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "13da686e-6ed6-4b83-93e8-1631fcc8c2a9",
"metadata": {},
"outputs": [],
"source": [
"config = {\"configurable\": {\"thread_id\": \"1\"}}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "8721f045-2e82-4bf0-9d85-5ba6ecf899d6",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{'node_1': {'foo': 'hi! foo'}}\n",
"{'subgraph_node_1': {'bar': 'bar'}}\n",
"{'subgraph_node_2': {'foo': 'hi! foobar'}}\n",
"{'node_2': {'foo': 'hi! foobar'}}\n"
]
}
],
"source": [
"for _, chunk in graph.stream({\"foo\": \"foo\"}, config, subgraphs=True):\n",
" print(chunk)"
]
},
{
"cell_type": "markdown",
"id": "ec6b5ce4-becc-4910-8a6d-d6b60d9d6f60",
"metadata": {},
"source": [
"We can now view the parent graph state by calling `graph.get_state()` with the same config that we used to invoke the graph."
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "3e817283-142d-4fda-8cb1-8de34717f833",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'foo': 'hi! foobar'}"
]
},
"execution_count": 6,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"graph.get_state(config).values"
]
},
{
"cell_type": "markdown",
"id": "fbc4f30b-941e-4140-8bfa-3b8cc670489c",
"metadata": {},
"source": [
"To view the subgraph state, we need to do two things:\n",
"\n",
"1. Find the most recent config value for the subgraph\n",
"2. Use `graph.get_state()` to retrieve that value for the most recent subgraph config.\n",
"\n",
"To find the correct config, we can examine the state history from the parent graph and find the state snapshot before we return results from `node_2` (the node with subgraph):"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "e896628f-36b2-45eb-b7c5-c64c1098f328",
"metadata": {},
"outputs": [],
"source": [
"state_with_subgraph = [\n",
" s for s in graph.get_state_history(config) if s.next == (\"node_2\",)\n",
"][0]"
]
},
{
"cell_type": "markdown",
"id": "7af49977-42b1-40a1-88f1-f07437f8b7f9",
"metadata": {},
"source": [
"The state snapshot will include the list of `tasks` to be executed next. When using subgraphs, the `tasks` will contain the config that we can use to retrieve the subgraph state:"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "21e96df3-946d-40f8-8d6d-055ae4177452",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'configurable': {'thread_id': '1',\n",
" 'checkpoint_ns': 'node_2:6ef111a6-f290-7376-0dfc-a4152307bc5b'}}"
]
},
"execution_count": 8,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"subgraph_config = state_with_subgraph.tasks[0].state\n",
"subgraph_config"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "1d2401b3-d52b-4895-a5d1-dccf015ba216",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'foo': 'hi! foobar', 'bar': 'bar'}"
]
},
"execution_count": 9,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"graph.get_state(subgraph_config).values"
]
},
{
"cell_type": "markdown",
"id": "40aded92-99dd-427b-932d-aa78f474c271",
"metadata": {},
"source": [
"If you want to learn more about how to modify the subgraph state for human-in-the-loop workflows, check out this [how-to guide](https://langchain-ai.github.io/langgraph/how-tos/subgraphs-manage-state/)."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"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.12.3"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
Loading

0 comments on commit 18f34c3

Please sign in to comment.