mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
docs: add a how-to on subgraph transform state (#1595)
* docs: add a how-to on subgraph transform state --------- Co-authored-by: isaac hershenson <ihershenson@hmc.edu> Co-authored-by: Isaac Francisco <78627776+isahers1@users.noreply.github.com>
This commit is contained in:
co-authored by
isaac hershenson
Isaac Francisco
parent
b34bfe165a
commit
e14c17f6b8
@@ -0,0 +1,277 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# How to transform inputs and outputs of a subgraph\n",
|
||||
"\n",
|
||||
"It's possible that your subgraph state is completely independent from the parent graph state, i.e. there are no overlapping channels (keys) between the two. For example, you might have a supervisor agent that needs to produce a report with a help of multiple ReAct agents. ReAct agent subgraphs might keep track of a list of messages whereas the supervisor only needs user input and final report in its state, and doesn't need to keep track of messages.\n",
|
||||
"\n",
|
||||
"In such cases you need to transform the inputs to the subgraph before calling it and then transform its outputs before returning. This guide shows how to do that."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%%capture --no-stderr\n",
|
||||
"%pip install -U langgraph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Define graph and subgraphs\n",
|
||||
"\n",
|
||||
"Let's define 3 graphs:\n",
|
||||
"- a parent graph\n",
|
||||
"- a child subgraph that will be called by the parent graph\n",
|
||||
"- a grandchild subgraph that will be called by the child graph"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define grandchild"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import TypedDict\n",
|
||||
"from langgraph.graph.state import StateGraph, START, END\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GrandChildState(TypedDict):\n",
|
||||
" my_grandchild_key: str\n",
|
||||
"\n",
|
||||
"def grandchild_1(state: GrandChildState) -> GrandChildState:\n",
|
||||
" # NOTE: child or parent keys will not be accessible here\n",
|
||||
" return {\"my_grandchild_key\": state[\"my_grandchild_key\"] + \", how are you\"}\n",
|
||||
"\n",
|
||||
"grandchild = StateGraph(GrandChildState)\n",
|
||||
"grandchild.add_node(\"grandchild_1\", grandchild_1)\n",
|
||||
"\n",
|
||||
"grandchild.add_edge(START, \"grandchild_1\")\n",
|
||||
"grandchild.add_edge(\"grandchild_1\", END)\n",
|
||||
"\n",
|
||||
"grandchild_graph = grandchild.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'my_grandchild_key': 'hi Bob, how are you'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"grandchild_graph.invoke({\"my_grandchild_key\": \"hi Bob\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define child"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class ChildState(TypedDict):\n",
|
||||
" my_child_key: str\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_grandchild_graph(state: ChildState) -> ChildState:\n",
|
||||
" # NOTE: parent or grandchild keys won't be accessible here\n",
|
||||
" # we're transforming the state from the child state channels (`my_child_key`)\n",
|
||||
" # to the child state channels (`my_grandchild_key`)\n",
|
||||
" grandchild_graph_input = {\"my_grandchild_key\": state[\"my_child_key\"]}\n",
|
||||
" # we're transforming the state from the grandchild state channels (`my_grandchild_key`)\n",
|
||||
" # back to the child state channels (`my_child_key`)\n",
|
||||
" grandchild_graph_output = grandchild_graph.invoke(grandchild_graph_input)\n",
|
||||
" return {\"my_child_key\": grandchild_graph_output[\"my_grandchild_key\"] + \" today?\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"child = StateGraph(ChildState)\n",
|
||||
"# NOTE: we're passing a function here instead of just compiled graph (`child_graph`)\n",
|
||||
"child.add_node(\"child_1\", call_grandchild_graph)\n",
|
||||
"child.add_edge(START, \"child_1\")\n",
|
||||
"child.add_edge(\"child_1\", END)\n",
|
||||
"child_graph = child.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'my_child_key': 'hi Bob, how are you today?'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"child_graph.invoke({\"my_child_key\": \"hi Bob\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition info\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" We're wrapping the <code>grandchild_graph</code> invocation in a separate function (<code>call_grandchild_graph</code>) that transforms the input state before calling the grandchild graph and then transforms the output of grandchild graph back to child graph state. If you just pass <code>grandchild_graph</code> directly to <code>.add_node</code> without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between child and grandchild states.\n",
|
||||
" </p>\n",
|
||||
"</div> "
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Note that child and grandchild subgraphs have their own, **independent** state that is not shared with the parent graph."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### Define parent"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class ParentState(TypedDict):\n",
|
||||
" my_key: str\n",
|
||||
" \n",
|
||||
"def parent_1(state: ParentState) -> ParentState:\n",
|
||||
" # NOTE: child or grandchild keys won't be accessible here\n",
|
||||
" return {\"my_key\": \"hi \" + state[\"my_key\"]}\n",
|
||||
"\n",
|
||||
"def parent_2(state: ParentState) -> ParentState:\n",
|
||||
" return {\"my_key\": state[\"my_key\"] + \" bye!\"}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def call_child_graph(state: ParentState) -> ParentState:\n",
|
||||
" # we're transforming the state from the parent state channels (`my_key`)\n",
|
||||
" # to the child state channels (`my_child_key`)\n",
|
||||
" child_graph_input = {\"my_child_key\": state[\"my_key\"]}\n",
|
||||
" # we're transforming the state from the child state channels (`my_child_key`)\n",
|
||||
" # back to the parent state channels (`my_key`)\n",
|
||||
" child_graph_output = child_graph.invoke(child_graph_input)\n",
|
||||
" return {\"my_key\": child_graph_output[\"my_child_key\"]}\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"parent = StateGraph(ParentState)\n",
|
||||
"parent.add_node(\"parent_1\", parent_1)\n",
|
||||
"# NOTE: we're passing a function here instead of just a compiled graph (`<code>child_graph</code>`)\n",
|
||||
"parent.add_node(\"child\", call_child_graph)\n",
|
||||
"parent.add_node(\"parent_2\", parent_2)\n",
|
||||
"\n",
|
||||
"parent.add_edge(START, \"parent_1\")\n",
|
||||
"parent.add_edge(\"parent_1\", \"child\")\n",
|
||||
"parent.add_edge(\"child\", \"parent_2\")\n",
|
||||
"parent.add_edge(\"parent_2\", END)\n",
|
||||
"\n",
|
||||
"parent_graph = parent.compile()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"<div class=\"admonition info\">\n",
|
||||
" <p class=\"admonition-title\">Note</p>\n",
|
||||
" <p>\n",
|
||||
" We're wrapping the <code>child_graph</code> invocation in a separate function (<code>call_child_graph</code>) that transforms the input state before calling the child graph and then transforms the output of the child graph back to parent graph state. If you just pass <code>child_graph</code> directly to <code>.add_node</code> without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between parent and child states.\n",
|
||||
" </p>\n",
|
||||
"</div> \n",
|
||||
"\n",
|
||||
"Let's run the parent graph and make sure it correctly calls both the child and grandchild subgraphs:"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'my_key': 'hi Bob, how are you today? bye!'}"
|
||||
]
|
||||
},
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parent_graph.invoke({\"my_key\": \"Bob\"})"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Perfect! The parent graph correctly calls both the child and grandchild subgraphs (which we know since the \", how are you\" and \"today?\" are added to our original \"my_key\" state value)."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "langgraph",
|
||||
"language": "python",
|
||||
"name": "langgraph"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.11.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 4
|
||||
}
|
||||
Reference in New Issue
Block a user