diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 7fefcdda..a6b6a21f 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -248,20 +248,20 @@ import { StateGraph, Annotation } from "@langchain/langgraph"; const GraphAnnotation = Annotation.Root({ input: Annotation, results: Annotation, -}) +}); // The state type can be extracted using `typeof .State` const myNode = (state: typeof GraphAnnotation.State, config?: RunnableConfig) => { console.log("In node: ", config.configurable?.user_id); return { results: `Hello, ${state.input}!` - } -} + }; +}; // The second argument is optional const myOtherNode = (state: typeof GraphAnnotation.State) => { - return state -} + return state; +}; const builder = new StateGraph(GraphAnnotation) .addNode("myNode", myNode) @@ -331,6 +331,9 @@ graph.addConditionalEdges("nodeA", routingFunction, { }); ``` +!!! tip + Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function. + ### Entry Point The entry point is the first node(s) that are run when the graph starts. You can use the [`addEdge`](/langgraphjs/reference/classes/langgraph.StateGraph.html#addEdge) method from the virtual [`START`](/langgraphjs/reference/variables/langgraph.START.html) node to the first node to execute to specify where to enter the graph. @@ -376,6 +379,108 @@ const continueToJokes = (state: { subjects: string[] }) => { graph.addConditionalEdges("nodeA", continueToJokes); ``` +## `Command` + +!!! tip Compatibility + This functionality requires `@langchain/langgraph>=0.2.29`. + +It can be convenient 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 rather than use a conditional edge. LangGraph provides a way to do so by returning a [`Command`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Command.html) object from node functions: + +```ts +import { StateGraph, Annotation, Command } from "@langchain/langgraph"; + +const StateAnnotation = Annotation.Root({ + foo: Annotation, +}); + + +const myNode = (state: typeof StateAnnotation.State) => { + return new Command({ + // state update + update: { + foo: "bar", + }, + // control flow + goto: "myOtherNode", + }); +}; +``` + +A `Command` has the following properties: + +| Property | Description | +| --- | --- | +| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `Command.PARENT`: closest parent graph | +| `update` | Update to apply to the graph's state. | +| `resume` | Value to resume execution with. To be used together with [`interrupt()`](https://langchain-ai.github.io/langgraphjs/reference/functions/langgraph.interrupt-1.html). | +| `goto` | Can be one of the following:
- name of the node to navigate to next (any node that belongs to the specified `graph`)
- sequence of node names to navigate to next
- [`Send`](https://langchain-ai.github.io/langgraphjs/reference/classes/langgraph.Send.html) object (to execute a node with the input provided)
- sequence of `Send` objects
If `goto` is not specified and there are no other tasks left in the graph, the graph will halt after executing the current superstep. | + +Here's a complete example: + +```ts +import { StateGraph, Annotation, Command } from "@langchain/langgraph"; + +const StateAnnotation = Annotation.Root({ + foo: Annotation, +}); + +const myNode = async (state: typeof StateAnnotation.State) => { + return new Command({ + // state update + update: { + foo: "bar", + }, + // control flow + goto: "myOtherNode", + }); +}; + +const myOtherNode = async (state: typeof StateAnnotation.State) => { + return { + foo: state.foo + "baz" + }; +}; + +const graph = new StateGraph(StateAnnotation) + .addNode("myNode", myNode, { + // For compiling and validating the graph + ends: ["myOtherNode"], + }) + .addNode("myOtherNode", myOtherNode) + .addEdge("__start__", "myNode") + .compile(); + +await graph.invoke({ + foo: "", +}); +``` + +```ts +{ foo: "barbaz" } +``` + +With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)): + +```ts +const myNode = async (state: typeof StateAnnotation.State) => { + if (state.foo === "bar") { + return new Command({ + update: { + foo: "baz", + }, + goto: "myOtherNode", + }); + } + // ... +}; +``` + +!!! important + + When returning `Command` in your node functions, you must also add an `ends` parameter with the list of node names the node is routing to, e.g. `.addNode("myNode", myNode, { ends: ["nodeA", "nodeB"] })`. This is necessary for graph compilation and validation, and indicates that `myNode` can navigate to `nodeA` and `nodeB`. + +Check out this [how-to guide](../how-tos/command.ipynb) for an end-to-end example of how to use `Command`. + ## Persistence LangGraph provides built-in persistence for your agent's state using [checkpointers](/langgraphjs/reference/classes/checkpoint.BaseCheckpointSaver.html). Checkpointers save snapshots of the graph state at every superstep, allowing resumption at any time. This enables features like human-in-the-loop interactions, memory management, and fault-tolerance. You can even directly manipulate a graph's state after its execution using the appropriate `get` and `update` methods. For more details, see the [conceptual guide](/langgraphjs/concepts/persistence) for more information. diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index b2a27181..176cc9b6 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -23,6 +23,7 @@ These how-to guides show how to achieve that controllability. - [How to create branches for parallel execution](branching.ipynb) - [How to create map-reduce branches for parallel execution](map-reduce.ipynb) +- [How to combine control flow and state updates with Command](command.ipynb) ### Persistence diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fd0002f6..9d5d8b27 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -109,6 +109,7 @@ nav: - Controllability: how-tos#controllability - how-tos/map-reduce.ipynb - how-tos/branching.ipynb + - how-tos/command.ipynb - Persistence: - Persistence: how-tos#persistence - how-tos/persistence.ipynb diff --git a/examples/how-tos/command.ipynb b/examples/how-tos/command.ipynb new file mode 100644 index 00000000..64c2719c --- /dev/null +++ b/examples/how-tos/command.ipynb @@ -0,0 +1,254 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d33ecddc-6818-41a3-9d0d-b1b1cbcd286d", + "metadata": {}, + "source": [ + "# How to combine control flow and state updates with Command" + ] + }, + { + "cell_type": "markdown", + "id": "7c0a8d03-80b4-47fd-9b17-e26aa9b081f3", + "metadata": {}, + "source": [ + "
\n", + "

Prerequisites

\n", + "

\n", + " This guide assumes familiarity with the following:\n", + "

\n", + "

\n", + " This functionality also requires @langchain/langgraph>=0.2.29.\n", + "

\n", + "

\n", + "
\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", + "```ts\n", + "const myNode = (state: typeof StateAnnotation.State) => {\n", + " return new Command({\n", + " // state update\n", + " update: {\n", + " foo: \"bar\",\n", + " },\n", + " // control flow\n", + " goto: \"myOtherNode\",\n", + " });\n", + "};\n", + "```\n", + "\n", + "This guide shows how you can use `Command` to add dynamic control flow in your LangGraph app." + ] + }, + { + "cell_type": "markdown", + "id": "d1c3f866-8c20-40c7-a201-35f6c9f4b680", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's install the required packages:\n", + "\n", + "```bash\n", + "yarn add @langchain/langgraph @langchain/core\n", + "```" + ] + }, + { + "cell_type": "markdown", + "id": "0f131c92-4744-431c-a89c-7c382a15b79f", + "metadata": {}, + "source": [ + "
\n", + "

Set up LangSmith for LangGraph development

\n", + "

\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 here. \n", + "

\n", + "
" + ] + }, + { + "cell_type": "markdown", + "id": "f22c228f-6882-4757-8e7e-1ca51328af4a", + "metadata": {}, + "source": [ + "Let's create a simple graph with 3 nodes: A, B and C. We will first execute node A, and then decide whether to go to Node B or Node C next based on the output of node A." + ] + }, + { + "cell_type": "markdown", + "id": "6a08d957-b3d2-4538-bf4a-68ef90a51b98", + "metadata": {}, + "source": [ + "## Define graph" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "4539b81b-09e9-4660-ac55-1b1775e13892", + "metadata": {}, + "outputs": [], + "source": [ + "import { Annotation, Command } from \"@langchain/langgraph\";\n", + "\n", + "// Define graph state\n", + "const StateAnnotation = Annotation.Root({\n", + " foo: Annotation,\n", + "});\n", + "\n", + "// Define the nodes\n", + "const nodeA = async (_state: typeof StateAnnotation.State) => {\n", + " console.log(\"Called A\");\n", + " // this is a replacement for a real conditional edge function\n", + " const goto = Math.random() > .5 ? \"nodeB\" : \"nodeC\";\n", + " // note how Command allows you to BOTH update the graph state AND route to the next node\n", + " return new Command({\n", + " // this is the state update\n", + " update: {\n", + " foo: \"a\",\n", + " },\n", + " // this is a replacement for an edge\n", + " goto,\n", + " });\n", + "};\n", + "\n", + "// Nodes B and C are unchanged\n", + "\n", + "const nodeB = async (state: typeof StateAnnotation.State) => {\n", + " console.log(\"Called B\");\n", + " return {\n", + " foo: state.foo + \"|b\",\n", + " };\n", + "}\n", + "\n", + "const nodeC = async (state: typeof StateAnnotation.State) => {\n", + " console.log(\"Called C\");\n", + " return {\n", + " foo: state.foo + \"|c\",\n", + " };\n", + "}" + ] + }, + { + "cell_type": "markdown", + "id": "badc25eb-4876-482e-bb10-d763023cdaad", + "metadata": {}, + "source": [ + "We can now create the `StateGraph` with the above nodes. Notice that the graph doesn't have [conditional edges](/langgraphjs/concepts/low_level#conditional-edges) for routing! This is because control flow is defined with `Command` inside `nodeA`." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "d6711650-4380-4551-a007-2805f49ab2d8", + "metadata": {}, + "outputs": [], + "source": [ + "import { StateGraph } from \"@langchain/langgraph\";\n", + "\n", + "// NOTE: there are no edges between nodes A, B and C!\n", + "const graph = new StateGraph(StateAnnotation)\n", + " .addNode(\"nodeA\", nodeA, {\n", + " ends: [\"nodeB\", \"nodeC\"],\n", + " })\n", + " .addNode(\"nodeB\", nodeB)\n", + " .addNode(\"nodeC\", nodeC)\n", + " .addEdge(\"__start__\", \"nodeA\")\n", + " .compile();" + ] + }, + { + "cell_type": "markdown", + "id": "0ab344c5-d634-4d7d-b3b4-edf4fa875311", + "metadata": {}, + "source": [ + "
\n", + "

Important

\n", + "

\n", + " You might have noticed that we add an ends field as an extra param to the node where we use Command. This is necessary for graph compilation and validation, and tells LangGraph that nodeA can navigate to nodeB and nodeC.\n", + "

\n", + "
" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "eeb810e5-8822-4c09-8d53-c55cd0f5d42e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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" + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "import * as tslab from \"tslab\";\n", + "\n", + "const drawableGraph = await graph.getGraphAsync();\n", + "const image = await drawableGraph.drawMermaidPng();\n", + "const arrayBuffer = await image.arrayBuffer();\n", + "\n", + "await tslab.display.png(new Uint8Array(arrayBuffer));" + ] + }, + { + "cell_type": "markdown", + "id": "58fb6c32-e6fb-4c94-8182-e351ed52a45d", + "metadata": {}, + "source": [ + "If we run the graph multiple times, we'd see it take different paths (A -> B or A -> C) based on the random choice in node A." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "d88a5d9b-ee08-4ed4-9c65-6e868210bfac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Called A\n", + "Called C\n", + "{ foo: 'a|c' }\n" + ] + } + ], + "source": [ + "await graph.invoke({ foo: \"\" });" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "TypeScript", + "language": "typescript", + "name": "tslab" + }, + "language_info": { + "codemirror_mode": { + "mode": "typescript", + "name": "javascript", + "typescript": true + }, + "file_extension": ".ts", + "mimetype": "text/typescript", + "name": "typescript", + "version": "3.7.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +}