{ "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": [ "
Note
\n", "\n",
" We're wrapping the grandchild_graph invocation in a separate function (call_grandchild_graph) 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 grandchild_graph directly to .add_node without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between child and grandchild states.\n",
"
child_graph`)\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": [
"Note
\n", "\n",
" We're wrapping the child_graph invocation in a separate function (call_child_graph) 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 child_graph directly to .add_node without the transformations, LangGraph will raise an error as there are no shared state channels (keys) between parent and child states.\n",
"