diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 4656d971a..68e0e0889 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -372,6 +372,10 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]: Setting `graph` to `Command.PARENT` will navigate to the closest parent graph. +!!! important "State updates with `Command.PARENT`" + + When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](#schema), you **must** define a [reducer](#reducers) for the key you're updating in the parent graph state. See this [example](../how-tos/command.ipynb#navigating-to-a-node-in-a-parent-graph). + This is particularly useful when implementing [multi-agent handoffs](./multi_agent.md#handoffs). ### Using inside tools diff --git a/docs/docs/how-tos/command.ipynb b/docs/docs/how-tos/command.ipynb index fe947a80b..a1f24f27b 100644 --- a/docs/docs/how-tos/command.ipynb +++ b/docs/docs/how-tos/command.ipynb @@ -16,10 +16,10 @@ "!!! info \"Prerequisites\"\n", " This guide assumes familiarity with the following:\n", " \n", - " - [State](../../concepts/low_level/#state)\n", - " - [Nodes](../../concepts/low_level/#nodes)\n", - " - [Edges](../../concepts/low_level/#edges)\n", - " - [Command](../../concepts/low_level/#command)\n", + " - [State](../../concepts/low_level#state)\n", + " - [Nodes](../../concepts/low_level#nodes)\n", + " - [Edges](../../concepts/low_level#edges)\n", + " - [Command](../../concepts/low_level#command)\n", "\n", "It can be useful to combine control flow (edges) and state updates (nodes). For example, you might want to BOTH perform state updates AND decide which node to go to next in the SAME node. LangGraph provides a way to do so by returning a `Command` object from node functions:\n", "\n", @@ -44,6 +44,10 @@ " )\n", "```\n", "\n", + "!!! important \"State updates with `Command.PARENT`\"\n", + "\n", + " When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](../../concepts/low_level#schema), you **must** define a [reducer](../../concepts/low_level#reducers) for the key you're updating in the parent graph state. See this [example](#navigating-to-a-node-in-a-parent-graph) below.\n", + "\n", "This guide shows how you can do use `Command` to add dynamic control flow in your LangGraph app." ] }, @@ -224,13 +228,13 @@ "output_type": "stream", "text": [ "Called A\n", - "Called B\n" + "Called C\n" ] }, { "data": { "text/plain": [ - "{'foo': 'ab'}" + "{'foo': 'bc'}" ] }, "execution_count": 5, @@ -258,6 +262,16 @@ "Now let's demonstrate how you can navigate from inside a subgraph to a different node in a parent graph. We'll do so by changing `node_a` in the above example into a single-node graph that we'll add as a subgraph to our parent graph." ] }, + { + "cell_type": "markdown", + "id": "6be0aeb9-e138-4adc-a1df-5d743a8eb348", + "metadata": {}, + "source": [ + "!!! important \"State updates with `Command.PARENT`\"\n", + "\n", + " When you send updates from a subgraph node to a parent graph node for a key that's shared by both parent and subgraph [state schemas](../../concepts/low_level#schema), you **must** define a [reducer](../../concepts/low_level#reducers) for the key you're updating in the parent graph state." + ] + }, { "cell_type": "code", "execution_count": 6, @@ -265,7 +279,14 @@ "metadata": {}, "outputs": [], "source": [ - "# Define the nodes\n", + "import operator\n", + "from typing_extensions import Annotated\n", + "\n", + "\n", + "class State(TypedDict):\n", + " # NOTE: we define a reducer here\n", + " # highlight-next-line\n", + " foo: Annotated[str, operator.add]\n", "\n", "\n", "def node_a(state: State):\n", @@ -283,6 +304,7 @@ " goto=goto,\n", " # this tells LangGraph to navigate to node_b or node_c in the parent graph\n", " # NOTE: this will navigate to the closest parent graph relative to the subgraph\n", + " # highlight-next-line\n", " graph=Command.PARENT,\n", " )\n", "\n", @@ -292,12 +314,17 @@ "\n", "def node_b(state: State):\n", " print(\"Called B\")\n", - " return {\"foo\": state[\"foo\"] + \"b\"}\n", + " # NOTE: since we've defined a reducer, we don't need to manually append\n", + " # new characters to existing 'foo' value. instead, reducer will append these\n", + " # automatically (via operator.add)\n", + " # highlight-next-line\n", + " return {\"foo\": \"b\"}\n", "\n", "\n", "def node_c(state: State):\n", " print(\"Called C\")\n", - " return {\"foo\": state[\"foo\"] + \"c\"}" + " # highlight-next-line\n", + " return {\"foo\": \"c\"}" ] }, {