From 90eab07deddf3eb63613a2a152df7ddc4704dca0 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 12:56:39 -0500 Subject: [PATCH 01/12] docs: add Command/GraphCommand docs --- docs/docs/concepts/low_level.md | 46 +++ docs/docs/how-tos/graph-command.ipynb | 363 ++++++++++++++++++++++++ docs/docs/how-tos/index.md | 1 + docs/docs/reference/graphs.md | 1 + docs/docs/reference/types.md | 1 + docs/mkdocs.yml | 1 + libs/langgraph/langgraph/graph/state.py | 13 +- libs/langgraph/langgraph/types.py | 11 +- 8 files changed, 435 insertions(+), 2 deletions(-) create mode 100644 docs/docs/how-tos/graph-command.ipynb diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 05ceacdea..9c562e093 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -322,6 +322,52 @@ def continue_to_jokes(state: OverallState): graph.add_conditional_edges("node_a", continue_to_jokes) ``` +## `GraphCommand` + +Typically, LangGraph separates control flow (edges) from state updates (nodes). However, it is often beneficial to combine the two. For example, you might want to BOTH perform state updates AND decide which node to go next in the SAME node. LangGraph provides a way to combine control flow and node state updates using [`GraphCommand`][langgraph.graph.state.GraphCommand]. To do so, you can return a `GraphCommand` object from a node instead of a state update or `Send` objects. + +`GraphCommand` has the following properties: + + - `goto`: optional, name of the node to navigate to next. + If not specified, the graph will halt after executing the current superstep. + - `graph`: optional, graph to send the command to. Supported values are: + - `None`: the current graph (default) + - `GraphCommand.PARENT`: parent graph. + - `update`: optional, state update to apply to the graph's state at the current superstep. + - `send`: optional, list of [`Send`](#send) objects to send to other nodes. + - `resume`: optional, value to resume execution with. Will be used when `interrupt()` is called. + +```python +from langgraph.graph import GraphCommand, StateGraph, START +from typing_extensions import TypedDict, Literal + +class State(TypedDict): + foo: str + +def my_node(state: State) -> GraphCommand[Literal["my_other_node"]]: + return GraphCommand(update={"foo": "bar"}, goto="my_other_node") + +def my_other_node(state: State): + return {"foo": state["foo"] + "baz"} + +builder = StateGraph(State) +builder.add_edge(START, "my_node") +builder.add_node("my_node", my_node) +builder.add_node("my_other_node", my_other_node) + +graph = builder.compile() +``` + +With `GraphCommand` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)): + +```python +def my_node(state: State) -> GraphCommand[Literal["my_other_node", "__end__"]]: + if state["foo"] == "bar": + return GraphCommand(update={"foo": "baz"}, goto="my_other_node") + else: + return GraphCommand(goto="__end__") +``` + ## Persistence LangGraph provides built-in persistence for your agent's state using [checkpointers][langgraph.checkpoint.base.BaseCheckpointSaver]. 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 diff --git a/docs/docs/how-tos/graph-command.ipynb b/docs/docs/how-tos/graph-command.ipynb new file mode 100644 index 000000000..8df59c9f4 --- /dev/null +++ b/docs/docs/how-tos/graph-command.ipynb @@ -0,0 +1,363 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d33ecddc-6818-41a3-9d0d-b1b1cbcd286d", + "metadata": {}, + "source": [ + "# How to combine control flow and state updates with GraphCommand" + ] + }, + { + "cell_type": "markdown", + "id": "7c0a8d03-80b4-47fd-9b17-e26aa9b081f3", + "metadata": {}, + "source": [ + "Typically, LangGraph separates control flow (edges) and state updates (nodes). However, it is often beneficial to combine the two. For example, you might want to BOTH perform state updates AND decide which node to go next in the SAME node. LangGraph provides a way to combine control flow and node state updates using `GraphCommand`. This guide shows how you can do so." + ] + }, + { + "cell_type": "markdown", + "id": "d1c3f866-8c20-40c7-a201-35f6c9f4b680", + "metadata": {}, + "source": [ + "## Setup\n", + "\n", + "First, let's install the required packages" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "6999c7fe-31bb-4c19-946a-85c2edc57da7", + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "%pip install -U langgraph" + ] + }, + { + "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": "71c8bc81-c1b4-46aa-835f-2c2849156594", + "metadata": {}, + "source": [ + "## Using edges" + ] + }, + { + "cell_type": "markdown", + "id": "9a81df3a-6489-44da-8a7e-615009ef9f59", + "metadata": {}, + "source": [ + "Let's first implement the graph with a traditional LangGraph primitives -- nodes and conditional edges. The conditional edge (`route_from_a`) will inspect the state last updated by node A and decide where to go next based on the value of the state key `foo`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "de32d339-3501-4982-a34f-8d3facc53579", + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "from typing_extensions import TypedDict, Literal\n", + "\n", + "from langgraph.graph import GraphCommand, StateGraph, START\n", + "\n", + "\n", + "# Define graph state\n", + "class State(TypedDict):\n", + " foo: str\n", + "\n", + "\n", + "# Define the nodes\n", + "def node_a(state: State):\n", + " print(\"Called A\")\n", + " return {\"foo\": random.choice([\"a\", \"b\"])}\n", + "\n", + "def node_b(state: State):\n", + " print(\"Called B\")\n", + " return {\"foo\": state[\"foo\"] + \"b\"}\n", + "\n", + "def node_c(state: State):\n", + " print(\"Called C\")\n", + " return {\"foo\": state[\"foo\"] + \"c\"}\n", + "\n", + "# Define the conditional edges\n", + "def route_from_a(state: State) -> Literal[\"node_b\", \"node_c\"]:\n", + " if state[\"foo\"] == \"a\":\n", + " return \"node_b\"\n", + " else:\n", + " return \"node_c\"" + ] + }, + { + "cell_type": "markdown", + "id": "87ef1325-d42f-4a6c-81e6-0058b9628b9e", + "metadata": {}, + "source": [ + "We can now create the StateGraph with the above nodes and conditional edges." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "b6e3044b-d817-4f7e-9e4f-1b3aff109670", + "metadata": {}, + "outputs": [], + "source": [ + "builder = StateGraph(State)\n", + "builder.add_edge(START, \"node_a\")\n", + "builder.add_node(node_a)\n", + "builder.add_node(node_b)\n", + "builder.add_node(node_c)\n", + "builder.add_conditional_edges(\"node_a\", route_from_a)\n", + "\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "e60c8a11-ce6f-484c-ba2f-936c3d69b120", + "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": 4, + "id": "9175add8-0c08-48ee-8d70-249c5d209736", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Called A\n", + "Called C\n" + ] + }, + { + "data": { + "text/plain": [ + "{'foo': 'bc'}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"foo\": \"\"})" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "254eb3a1-bb47-4401-93fb-51a65b6b8e71", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "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", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "from IPython.display import display, Image\n", + "\n", + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "markdown", + "id": "0c52be14-d250-4c64-99e2-ce0a201e4523", + "metadata": {}, + "source": [ + "Now let's reimplement the same graph using `GraphCommand`!" + ] + }, + { + "cell_type": "markdown", + "id": "6a08d957-b3d2-4538-bf4a-68ef90a51b98", + "metadata": {}, + "source": [ + "## Using GraphCommand" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "37107209-34d6-4414-a54e-cd3ee38e3651", + "metadata": {}, + "outputs": [], + "source": [ + "# Define the nodes\n", + "\n", + "def node_a(state: State) -> GraphCommand[Literal[\"node_b\", \"node_c\"]]:\n", + " print(\"Called A\")\n", + " value = random.choice([\"a\", \"b\"])\n", + " # this is a replacement for the logic in route_from_a\n", + " if value == \"a\":\n", + " goto = \"node_b\"\n", + " else:\n", + " goto = \"node_c\"\n", + "\n", + " # note how GraphCommand allows you to BOTH update the graph state AND route to the next node\n", + " return GraphCommand(\n", + " # this is the state update, same as we returned from node A previously\n", + " update={\"foo\": value},\n", + " # this is a replacement for route_from_a conditional edge\n", + " goto=goto\n", + " )\n", + "\n", + "# Nodes B and C are unchanged\n", + "\n", + "def node_b(state: State):\n", + " print(\"Called B\")\n", + " # graph command can also be used \n", + " return {\"foo\": state[\"foo\"] + \"b\"}\n", + "\n", + "def node_c(state: State):\n", + " print(\"Called C\")\n", + " return {\"foo\": state[\"foo\"] + \"c\"}" + ] + }, + { + "cell_type": "markdown", + "id": "badc25eb-4876-482e-bb10-d763023cdaad", + "metadata": {}, + "source": [ + "We can now create the `StateGraph` with the above nodes. But notice that the graph no longer uses conditional edges! This is because control flow is defined inside `node_a`." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "d6711650-4380-4551-a007-2805f49ab2d8", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "builder = StateGraph(State)\n", + "builder.add_edge(START, \"node_a\")\n", + "builder.add_node(node_a)\n", + "builder.add_node(node_b)\n", + "builder.add_node(node_c)\n", + "# NOTE: there are no edges between nodes A, B and C!\n", + "\n", + "graph = builder.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "0ab344c5-d634-4d7d-b3b4-edf4fa875311", + "metadata": {}, + "source": [ + "!!! important\n", + "\n", + " You might have noticed that we used `GraphCommand` as a return type annotation, e.g. `GraphCommand[Literal[\"node_b\", \"node_c\"]]`. This is necessary for the graph compilation and rendering, and tells LangGraph that `node_a` can navigate to `node_b` and `node_c`." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "eeb810e5-8822-4c09-8d53-c55cd0f5d42e", + "metadata": {}, + "outputs": [ + { + "data": { + "image/jpeg": "/9j/4AAQSkZJRgABAQAAAQABAAD/4gHYSUNDX1BST0ZJTEUAAQEAAAHIAAAAAAQwAABtbnRyUkdCIFhZWiAH4AABAAEAAAAAAABhY3NwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAA9tYAAQAAAADTLQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAlkZXNjAAAA8AAAACRyWFlaAAABFAAAABRnWFlaAAABKAAAABRiWFlaAAABPAAAABR3dHB0AAABUAAAABRyVFJDAAABZAAAAChnVFJDAAABZAAAAChiVFJDAAABZAAAAChjcHJ0AAABjAAAADxtbHVjAAAAAAAAAAEAAAAMZW5VUwAAAAgAAAAcAHMAUgBHAEJYWVogAAAAAAAAb6IAADj1AAADkFhZWiAAAAAAAABimQAAt4UAABjaWFlaIAAAAAAAACSgAAAPhAAAts9YWVogAAAAAAAA9tYAAQAAAADTLXBhcmEAAAAAAAQAAAACZmYAAPKnAAANWQAAE9AAAApbAAAAAAAAAABtbHVjAAAAAAAAAAEAAAAMZW5VUwAAACAAAAAcAEcAbwBvAGcAbABlACAASQBuAGMALgAgADIAMAAxADb/2wBDAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRT/2wBDAQMEBAUEBQkFBQkUDQsNFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBT/wAARCAD5AOYDASIAAhEBAxEB/8QAHQABAQEBAQEBAQEBAAAAAAAAAAYFBwQDCAECCf/EAFIQAAEDAwICAwoJCQUDDQAAAAEAAgMEBQYREgchEzFWCBQVFhciQVGU0TI2VWF1lbPS0yM0NTdUdJOytCRCcZGxCRhSJzNDRVNjgYOho8HD8P/EABkBAQEBAQEBAAAAAAAAAAAAAAABAgMEBf/EADIRAQABAgIHBQcFAQAAAAAAAAABAhEDURIUITFSkbEEQWFxoSMyM2KBktETIsHh8PH/2gAMAwEAAhEDEQA/AP8AqmiIgIiICIiAiIgIilw6rzUF8FVPbbDzDZac7Kit5/CY/rjiPoc3RztdQWt0L+lFGltmbRCxDfrLnR2/TvqrgptRqOmkDP8AUryeNVl+WKD2lnvXlo8Dxyh1MVkoTISS6WSBskjiesue4FxPzkr1eKtl+R6D2ZnuXT2Md8+n9mw8arL8sUHtLPenjVZflig9pZ708VbL8j0HszPcnirZfkeg9mZ7k9j4+i7DxqsvyxQe0s96eNVl+WKD2lnvTxVsvyPQezM9yeKtl+R6D2ZnuT2Pj6Gw8arL8sUHtLPegymyk6C70Gv7yz3p4q2X5HoPZme5Bi1lB/RFB7Mz3J7Hx9E2PfT1UNXH0kErJo+rfG4OH+YX1U5U8PbDJJ01LQMtNYBo2rtf9mlHpGpZpuGvodqDqdQdSvpbblW224RWq7v74kl3d6XBsYY2oAGpY8Dk2UAE6DQOALmgaOa2TRTVF8Ob+Elsm+iIuCCIiAiIgIiICIiAiIgIiICIiAiIgms+ldJaKe2RvMbrtVxUDnAkERuJdLoRzB6JsgBHUSCqKKJkETI42NjjYA1rGjQNA6gB6ApvOR0AsNwOvRUN1hfIQNdGyB8Gv+AMwJPoAJ9Cp16K/hU28ef/ACy9wiIvOiHzTjZhnD7IKSx328Glu1VCKhlLDST1DmRF+wSSdExwjYXAgOftBIPPksDDe6DtWW8YcuwEUNfTVdkqY6WCpNBVGOod0HSyl8hhEcQBBa3c7SQAOYXBwUL3Rwu1jzaC/wCB2XLhxIZboqekrbVbTVWi5RdO4951pPmMDdXO3ksLRJqHn4I2cbqr1hXdAcSI6nG7tK3Kxbqq13OmopJ7eHxUQheyaZo0i0fH/e01DgQgtsS4/YFnGUeLtmv3T3hzZHxU81HPTioEfwzC+SNrZdvp2F2g59Snb33VmCw4Vkt+sVXV5E6y0FVWOhpbbWdGXwu2GJ8ohLY3by0HdzDXbyNnnLiOFW7LLtxB4P5Bf7RxCrskt10qBk1VdqeZtuopZ6SeENpoQej6Le8DpYmloYAXv5hdJ4X8P7zL3HF9xU2qe3X65UWQU7KOsiMEjpZ6irERcHAEbg9hBPoIPUg63ws4jUPFPC6C/wBBFVQNmjYJoqqinpiyUxte5rRMxhe0bwA9oLT6CdCq5c74D5PJkHDey09RYr3YKy2UVNRVNNe7fJSP6VkTQ7ZvHntBBG5uoPrXREBYeaWyS6Y1WspyG10LO+aSR2vmTx+fG7l6NwGo9IJHUVuLOyO6MsmP3K4SAltLTSTFrRqXbWk6AeknqA9K6Yc1RXTNO+6xvfWzXOO9WihuEIIhq4I6hgPoa5ocP9V7Fk4lan2LFbNbZdOko6KGndp62Rhp/wBFrKVxTFcxTuJ3iIiwgiIgIiICIiAiIgIiICIiAiIg81yt1Pd7fU0NXGJqWpjdFLGepzXDQj/IrFtV6faJ4LPepmtqz5lJWPOjK1vUBqeXTafCZ6ebm8tQ2jXnr7fS3WjlpK2miq6WUbZIJ2B7Hj1Fp5FdaK4iNGrd0/3qsIu98BuG+SXWqud1wTHblcap/ST1dVbIZJZXetzi3Un/ABXkf3N3CmQ6v4cYu4gAam0wHkBoB8H1BUAwGmp+VBdbxbY+ekUFc57G6+psm8AfMNB8y/niTUdqr9/Gh/CW9DDndX6T/ZaM2pjWL2fDbPDabDa6SzWyEuMdHQwthiYXEudo1oAGpJJ+crUUv4k1Haq/fxofwk8SajtVfv40P4Sfp4fH6SWjNUIuV47b7rdM+y+zz5TeO87UKM05ZLDv/KxOc/cej58wNOQVZ4k1Haq/fxofwk/Tw+P0ktGb/eY8NMS4hmkOUY1ashNJv73Nzo45+h3abtu4HTXa3XTr0HqU5/u2cJ9NPJvi2nq8Ewafyqg8SajtVfv40P4SDCagEHxpvp09Bmh5/wDtJ+nh8fpJaM38xPhrhvDbv2oxzG7PjYnYO+ZbfSR0+9rdSN5aBqBqTz6tSv4ZG51U05hAfj1NK2bp+elbKwhzNnoMTXAO3dTi0aeaCT9I+H1rfIyS4PrL25hBa251L5owQdQeiJ6PUHnrt1HLnyVMmlRh7aJvOe63l/thsjcIiLzoIiICIiAiIgIiICIiAiIgIiICIiAiIgIiICIiDnuFkeV3iPoTrpbdf4DvnXQlz3C9fK7xH6tNLb1aa/8AMO//AHNdCQEREBERAREQEREBERAREQEREBERAREQEREBERAREQEREBERBzzCx/yvcSOYPK2cgOY/IOXQ1zzCtPK9xI9els9H/cOXQ0BERAREQEREBERAREQEREBERAREQEREBERAREQERTF4yqsFxmt9looKyop9BUz1UzooYnEAhgLWuLn7SDpyABGp5gLpRh1Yk2pW11OiiPDuYfsFj9rm/DTw7mH7BY/a5vw16NVrzjnBZbrPyCurLZYLlWW6g8K3CnppZqegEvRd8yNYSyPfodu4gN10OmuuhUx4dzD9gsftc34aeHcw/YLH7XN+Gmq15xzgs/InAfu6qviR3QNTY6HhxPFV5PVUsE2t1BNBHAxzZZXDoBv2t3O01HwdNeeq/eS/NPDrgBNw14x5jxEtlvsxuWRAAUzqiUR0e47ptn5P/pHgO9GnMDkV1/w7mH7BY/a5vw01WvOOcFluiiPDuYfsFj9rm/DTw7mH7BY/a5vw01WvOOcFluiifDuYD/q+xn5u+5h/9S3MdyI3l1RTVVN3jc6baZqcP3t2u12vY/QbmnaeegOoIICxX2euiNKbW8Jgs2kRF5kEREBERAREQEREBERAREQEREBERAUBjh1umUE9fhaTn/5USv1z/G/0nlH0tJ9nEvd2b3a/KOqxuluoiLqgiIgIsfGMutOZUVVV2er78p6asnoJX9G9m2eGQxys0cATo5pGo5HTkSFsKAiIqCy7KdOJNV89pj1+f8s/3n/Naiy7L+smp+iWfbOWo9yvy/DUd63REXymRERAREQEREBERAREQEREBERAREQFz/G/0nlH0tJ9nEugLn+N/pPKPpaT7OJe7s3u1+UdVjdLdX5ryygu2WcQONjDluR2uDHbZRVVrp7Xc5KeKCd1HI8vLWkbhujb5h1YdSS0k6r9KKf8QLD4RySu7w/tWRQx090k6aT+0RxxujYNN2jdGvcNW6Hnz5rcxdHA8Mul7495RbLfe8nvWP0VLhVovLYbBXOoZKuqrGPMs7ns0LmsMYaGfB1J1B1WJwwzTJOO1fg2MXvKbpa6KOwVl1qq2yVJoqm8yw3B9HG4ys0c1myMSuDCNxlHo0Xdb73P+BZJbLJQV1iLoLNQttlE6CtqIJY6VrQ0QOljka+RmjRq15cD1nUkr0ZHwOwfKrbYqCvx+FlPYm7LZ3jNLRyUbNoaWRvhcxzWkAAt10Og1BWNGR+XMdu2T2yxYvgGPVtVIL5mGTR1VZJd3W6pqhSzvc2M1bIZHMe8uLyWMDndGQC3Ur9H8DsdznGKW+UmX1TKihNSyS0xyXZ90qaeMsHSRyVD4YnPG8bm7gSA7Qk6Bex3c/4A7C2YmcdiNijrH3CKn74m3w1DnF5ljl39JG7VzubXDQEgaDkv7Dw6uWD2mntnDmps9hojLJPVi9UdVcpJ5HbQH9J30x2ujTqXF2vm9WnOxEwPhx9v8VlwWKl33oV94uFNbKCKwVYpKqeokfq2MTnlE0hrtz+sNDtOeik+5queTU1/4iYnklTUzmxV1IaWOtujrnNBHPTNkMZqnMY6QA8xubqNxGp01VlNw8u2cWuqtPEaosWQ2tzo5qeO0W+pt8sMzHbmyCU1Ujg4ctCwtI58+a1cJ4T4rw5rK+rx21eDqmvZGyrlFRLI6p2Fxa+Qvcd7/Pdq86uOuhJACtpmbitWXZf1k1P0Sz7Zy1Fl2X9ZNT9Es+2cuse5X5fhqO9boiL5TIiIgIiICIiAiIgIiICIiAiIgIiIC5/jf6Tyj6Wk+ziXQFE3q31+NXGuuFFStuFBXStmmh74ZDLDLtazVpeQxzXBo5FzSHa/C3eb7OzVR+6mZteP5WGminqLIr3X04miwu8tYXOaOllpI3ciQTtdMDpy5HTmNCNQQV9vC1+7GXX2qi/HXr0Pmj7o/K2baLE8LX7sZdfaqL8dPC1+7GXX2qi/HTQ+aPuj8lm2ixPC1+7GXX2qi/HTwtfuxl19qovx00Pmj7o/JZtosTwtfuxl19qovx08LX7sZdfaqL8dND5o+6PyWbay7L+smp+iWfbOWdV5RdqGaOOfELrCJGucJXz0nRNDdNdzxMQ3r5a9fPTXQqkxix1cNdVXe5MjgramJkDKWJ5e2CJpcQC7lq9xcS4gaDRoGu3c7NdsOiq8xti2yYnoblIiIvlMiIiAiIgIiICIiAiIgIiICIiAiLGut/MdXJbLYKeuvbGQzSUb5tvQQSSFnTSaAkDRkpaOW8xOAPJxaHou99prR0cTtaivnZK6loInsE9UWMLnNjDnNGugA1JDQSNSNV4aexS3iSKsvobNq2mnjtD2xy09FUR6uL2v2B0j97h5zuQ6OMtaxwcXe612ZltMsklRPX1MkssvfFW4Oexr3A9GzQANY0NY0NA5hgLtziXHRQEREBERAREQEREHxraKnuVHPSVcEVVSVEbopoJmB7JGOGjmuaeRBBIIPXqsXva447O3vRlReLfPUwRNo90UbrdDsEbnRkhvSMDmteWvcXjdIWudoyMUCIPJa7rR3ugiraCpiq6SXXZNC7c06Egj/EEEEdYIIPML1rDulrqqKSW5WfdJVxU0rW2t8wipap7nB4LvNOx+u4bx/wBo7cH6N2+62Xmju7qtlNOySejl73qoNw6Snl2NfseP7p2vY4etr2kaggkPciIgIiICIiAiIgIiICIiAiIgy7xcqilkpKahpe/amomayQCdkfe8P9+d27UkNA0Aa1xL3MB2tLnt9Fotvgm3w0xqZ62RjR0lVVOBlnf6XvLQBqfU0Bo6mgAADIxOJtfU3S9y+CKipqp3UsVba3GQvpYZHiKOSQ9bmudMS0aNa57wNTqTRoCIiAiIgIiICIiAiIgIiICy71bqiUx1tDLIyupg5zIGyiOKr8122KUlj9GbiDuaNzSOR0Lmu1EQeO03Dwpb4Kh0D6SV7AZaWV7HyU79BujeY3ObuaeR2uI1HIkc17FORRNs+bPZD4IpKa7wvqZow4x11VVxiKPpNvVI0QhjS74TdkY5gjbRoCIiAiIgIiICIsW8Ztj2P1QprnfLdb6kjd0NTVMY/T17SddFumiqubUxeVtdtIpbypYd2ptHtsfvTypYd2ptHtsfvXXV8bgnlK6M5KlZWRZXZMPoWVt+vFBZKN8ghbUXGqZTxueQSGhzyATo1x069AfUsvypYd2ptHtsfvXNu6KocD46cIr9idRk9lFVPF01BM+sj/I1TNTE7XXlz80n/hc5NXxuCeUmjOSy4TZ5jeQWqO12zIcSuV0jNRUSUWLVkckTYzO7zxG07h8Nm92mm9x9YXQF+D/9nTw8sHCHGb3lWVXW3WzKLvIaKKlqqljJaekjdz1BOo6R43cx1MYfSv2P5UsO7U2j22P3pq+NwTyk0ZyVKKW8qWHdqbR7bH708qWHdqbR7bH701fG4J5SaM5KlFNQcS8SqZGxxZNaXvcQA0VsfMnkPT61SrnXh14fvxMeaTExvERFzQREQERfKpqYaOCSeolZBDGC58kjg1rR6yTyATePqilzxRw5p0OU2j1/nsfP/wBV/PKlh3am0e2x+9ejV8bgnlLWjOSpRS3lSw7tTaPbY/enlSw7tTaPbY/emr43BPKTRnJNZHxYwmhzi1R1GY4RTvoDVwVjbhc4G19M/RrdkWrvM85pEgdoeQHoXR6CvprpQ09bRVEVXR1MbZoaiB4fHKxw1a5rhyLSCCCORBX/ADW7pLubcfzzur7Hc7Lera3Ecom77vVXBVR7KKRmhqC46kAyDQt1+E9zgOpfv62cQMEs1tpLfQ5FZaWipImQQQR1kYbHG0BrWga9QAATV8bgnlJozkskUt5UsO7U2j22P3p5UsO7U2j22P3pq+NwTyk0ZyVKKW8qWHdqbR7bH71uWq9W++0xqLbXU9fAHFhkppWyNDh1gkHkR6lirCxKIvVTMfRLTD2oiLkjxXqsdb7PXVTAC+CCSVoPra0kf6KRxKkjprBRSAbp6mJk88zub5pHNBc9xPMkk/8Ah1dQVPlXxYvH7nN/IVPY18XLV+6RfyBfQwNmFPmvc0kRFtBERAREQEREH+ZYmTRujkY2Rjho5rhqCPnC8/DqUsortbw4mnttwfSwNdz2R9HHI1g1PU3pNB6gAOoBepeHh3+cZX9MO/poEq24VX06tRulYoiL5jIiIgKLyxwuGY2a2zjpKNlLPXGFw1a+VkkLWOI9O3e4gEHmQeRaFaKJyD9ZFq+iar7aBevsvxL+E9FhpoiL0IIiICIiAiIgLFqi215jjtVTgRTV9Q+gqCwadNH0E0rQ71lrowQTqRucBpudrtLEvfxlwz6Wf/RVS6Ubbx4T0lYXyIi+QjLyr4sXj9zm/kKnsa+Llq/dIv5AqHKvixeP3Ob+Qqexr4uWr90i/kC+jg/Bnz/he5pLhWE90pdMmoMFvVywg2XGsuqm2+krhdWVE0VS5khaHwiMfk3GJzQ/dr1asbrou6rhFh4EX+18KOEuMS1ltdX4leqS5V0jJZDFJHEZtwiJZqXflG6BwaOR5hSb9yPq7ul6nvV+Stw+Z3DZl18FOyXwgzpde+O9jUCl2amATebu37tATs0Urx/46ZLcOHHE4YVj9X4IsEc9uqcriuwo5YatmnS97xhu54jJAc/czmHBu7RaE3c/Zm/FJOGrbnY28NZLqa01n5bwoKQ1ffRpej29HrvOzpd/wf7mq8+Z8BuIr8V4j4ZjNdjM2LZZVVdfDNdpKiKropal2+WLSNjmuZv3FrtQRu5h2mixOlYbWf8AdUW7CsqrscooLLXVtpp4ZLi685LTWk75IxI2OBsupmftLSfgtG4DdrqB1zA8zt/ETDLLk1q6TwfdaWOrhbM3a9rXDXa4c9COo6E8wuX1/CvN8Tz/ACO/4VJjNfR5IynkraPIxM00lVFEIulhdE129rmtbuY7bzbycNVaXHixY8TqvBN0hvDrhTMYJzbMauNRTFxYHHo3xQPYRz9Djp1HmCtxM32iDzvMsysndHU9vxm0yZNE7EX1LrPNdu8qZrxWAdN5zXNL9NGA7dfO5kBdJ4W8RqTilh8F7pqSotswmlpKy31enTUlTE8xyxP05Etc08x1jQ+lc8ulqyzKuI9NxH4f+CpYPAjrG6iyqnrrfIXd8GV0m10IeANGaat87U8xoCbjg7w8n4a4e6gr66O5Xiurqm63KrhjMcctVUSulkLGkkhoLto156NBPWkXuLheHh3+cZX9MO/poF7l4eHf5xlf0w7+mgW6vhV/TrDUbpWKIi+YyIiICicg/WRavomq+2gVsonIP1kWr6JqvtoF6+y/EnynosNNc+4lcT7hhWT4nj9px0ZBc8jdVR07X1zaWOJ0MYkJe4sd5u0u1IBI05NdqugqEzHA7hkPFDh7klNNTMocefXuqo5XOEr+np+jZ0YDSDo7r1I5dWvUu037kSDO6Qkkx2MNxWaTNJchlxhmOx1rCw1kbeke7vgtA6ERaSF+zXQgbdUm7pLwNar/AE19xapoc0tVfR2xmO0lWyp79nqxrS9DPta0sfo/VzmtLejfqOXPMquAeS01ddL9a7haosjp81qMotIqTI6nkgmpI6aSnnIbuYXND+bA7Qhp58wvHdO56y7JjfMsuN3s9FxCqbvbbtb46Vssttpe8WvbDA9zg2R7Xiabe4Bp1eNB5uhx+4eKm42XvDuJnEG9cQbfNjdssuK2+r8DU11FdAXvqahofGdGND5CWRklrebRqduhVPww7p2iz3OaPFqyks1NX3CmlqqN9jyOmvDD0ehfHN0QBiftdqOTmna7Rx0WJfOAOY8Ua3OKnM62x2l9/sNDbKU2GSafvWemqZKhkjulYze3e5h9GoBGg03G7xetzbFKaruefUmNx2+jpmtDsUpKyrqppS5rd/RiPcG6E+YxryNdd2gKRe46PcK+ntVBU1tXK2ClponTTSu6mMaCXE/4AErnHDTipk/EeS2XRmCPteG3SI1FHdqm6xmpdCWl0Uj6UM80SDbpo9xG4EgL0T8TcTz6mqMadBkQZd4n0Lumxq5U7NsjSw6ySU4YzkTzcQAs3hLi3EzBaWxYzd6rGLjitmpxRR3Gn74bcKmCOPZAHREdGx40ZudvcDodACdRq952DrKxL38ZcM+ln/0VUttYl7+MuGfSz/6KqXfD3z5VdJWF8iIvkIy8q+LF4/c5v5Cp7Gvi5av3SL+QKpvNG642iupGEB88EkQJ9Bc0j/5UhiVZHUWGjhB2VNNCyCogdyfDI1oDmOB5gg/5jQjkQvoYG3CmPFe5sIiLaCIiAiIgIiIC8PDv84yv6Yd/TQL1zTR08TpJXtjjaNXPedAB85Xx4dQudQ3W4BrmwXKvfVQFwI3x9HHG12hAOjuj1HrBB6ilezCqny6tRulWIiL5jIiIgKJyD9ZFq+iar7aBWyi8tDbdl9nudQRFROpZ6EzuOjGSvkhdGHHqG7Y4AkjmAOZcAvX2X4lvCeiw0UQEEAg6govQgiIgIiICIiAsS9/GXDPpZ/8ARVS21i1BZdcxx+lp3Caa31D6+pDDr0MfQTRN3eoudJo0HQna8jUMdp0o2XnwnpKwvURF8hBYt4wrH8hqBUXSx224zgbRLVUkcjwPVq4E6LaRaprqom9M2k3JbyV4Z2Tsn1fF91PJXhnZOyfV8X3VUou2sY3HPOVvOaW8leGdk7J9XxfdTyV4Z2Tsn1fF91VKJrGNxzzkvOaW8leGdk7J9XxfdTyV4Z2Tsn1fF91VKJrGNxzzkvOaW8leGdk7J9XxfdTyV4Z2Tsn1fF91VKJrGNxzzkvOabp+GuI0krZYMXs8UjTqHsoIgR6f+FUiIudeJXie/Mz5l5kREXNBERAXznp4qqCSGaNk0MjS18cjQ5rgesEHrC+iJuEu/hdhr3auxSyk+s0EX3V/PJXhnZOyfV8X3VUovRrGNxzzlbzmlvJXhnZOyfV8X3U8leGdk7J9XxfdVSiaxjcc85LzmlvJXhnZOyfV8X3U8leGdk7J9XxfdVSiaxjcc85LzmlvJXhnZOyfV8X3U8leGdk7J9XxfdVSiaxjcc85LzmlvJXhnZOyfV8X3Vu2uz0Fkpu9rdRU9BT7i/oqaJsbS49Z0AHM+texFirFxK4tVVM/UvMiIi5I/9k=", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "display(Image(graph.get_graph().draw_mermaid_png()))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "d88a5d9b-ee08-4ed4-9c65-6e868210bfac", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Called A\n", + "Called C\n" + ] + }, + { + "data": { + "text/plain": [ + "{'foo': 'bc'}" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "graph.invoke({\"foo\": \"\"})" + ] + } + ], + "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 +} diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index e579902d9..06694822c 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -20,6 +20,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 control graph recursion limit](recursion-limit.ipynb) +- [How to combine control flow and state updates with GraphCommand](graph-command.ipynb) ### Persistence diff --git a/docs/docs/reference/graphs.md b/docs/docs/reference/graphs.md index c67e2136a..fef38e159 100644 --- a/docs/docs/reference/graphs.md +++ b/docs/docs/reference/graphs.md @@ -11,6 +11,7 @@ members: - StateGraph - CompiledStateGraph + - GraphCommand ::: langgraph.graph.message options: diff --git a/docs/docs/reference/types.md b/docs/docs/reference/types.md index 347a87d6e..98b1ef137 100644 --- a/docs/docs/reference/types.md +++ b/docs/docs/reference/types.md @@ -13,3 +13,4 @@ - PregelExecutableTask - StateSnapshot - Send + - Command diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index 58489b12a..882e3cdfd 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -151,6 +151,7 @@ nav: - how-tos/branching.ipynb - how-tos/map-reduce.ipynb - how-tos/recursion-limit.ipynb + - how-tos/graph-command.ipynb - Persistence: - Persistence: how-tos#persistence - how-tos/persistence.ipynb diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index 1a7208a2a..c9b4199ab 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -86,7 +86,18 @@ def _get_node_name(node: RunnableLike) -> str: @dataclasses.dataclass(**_DC_KWARGS) class GraphCommand(Generic[N], Command[N]): - """One or more commands to update a StateGraph's state and go to, or send messages to nodes.""" + """One or more commands to update a StateGraph's state and go to, or send messages to nodes. + + Args: + goto: name of the node to navigate to next. + If not specified, the graph will halt after executing the current superstep. + graph: graph to send the command to. Supported values are: + - None: the current graph (default) + - GraphCommand.PARENT: closest parent graph + update: state update to apply to the graph's state at the current superstep. + send: list of `Send` objects to send to other nodes. + resume: value to resume execution with. Will be used when `interrupt()` is called. + """ goto: Union[str, Sequence[str]] = () diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 7bf9148c5..e8407d0f1 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -239,7 +239,16 @@ N = TypeVar("N", bound=Hashable) @dataclasses.dataclass(**_DC_KWARGS) class Command(Generic[N]): - """One or more commands to update the graph's state and send messages to nodes.""" + """One or more commands to update the graph's state and send messages to nodes. + + Args: + graph: graph to send the command to. Supported values are: + - None: the current graph (default) + - GraphCommand.PARENT: closest parent graph + update: state update to apply to the graph's state at the current superstep. + send: list of `Send` objects to send to other nodes. + resume: value to resume execution with. Will be used when `interrupt()` is called. + """ graph: Optional[str] = None update: Optional[dict[str, Any]] = None From 0fdf3c9daf0bf8b8fd66348ebb3ec212e9023ad3 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 16:26:31 -0500 Subject: [PATCH 02/12] cr --- docs/docs/concepts/low_level.md | 31 ++-- docs/docs/how-tos/graph-command.ipynb | 196 ++++++------------------ libs/langgraph/langgraph/graph/state.py | 5 +- 3 files changed, 67 insertions(+), 165 deletions(-) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 9c562e093..75ff6d4a3 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -324,22 +324,31 @@ graph.add_conditional_edges("node_a", continue_to_jokes) ## `GraphCommand` -Typically, LangGraph separates control flow (edges) from state updates (nodes). However, it is often beneficial to combine the two. For example, you might want to BOTH perform state updates AND decide which node to go next in the SAME node. LangGraph provides a way to combine control flow and node state updates using [`GraphCommand`][langgraph.graph.state.GraphCommand]. To do so, you can return a `GraphCommand` object from a node instead of a state update or `Send` objects. +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 [`GraphCommand`][langgraph.graph.state.GraphCommand] object from node functions: + +```python +def my_node(state: State) -> GraphCommand[Literal["my_other_node"]]: + return GraphCommand( + # state update + update={"foo": "bar"}, + # control flow + goto="my_other_node" + ) +``` `GraphCommand` has the following properties: - - `goto`: optional, name of the node to navigate to next. - If not specified, the graph will halt after executing the current superstep. - - `graph`: optional, graph to send the command to. Supported values are: - - `None`: the current graph (default) - - `GraphCommand.PARENT`: parent graph. - - `update`: optional, state update to apply to the graph's state at the current superstep. - - `send`: optional, list of [`Send`](#send) objects to send to other nodes. - - `resume`: optional, value to resume execution with. Will be used when `interrupt()` is called. +| Property | Description | +| --- | --- | +| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `GraphCommand.PARENT`: parent graph | +| `goto` | Name of the node to navigate to next. Can be any node that belongs to the specified `graph` (current or parent). If `goto` not specified, the graph will halt after executing the current superstep. | +| `update` | State update to apply to the graph's state at the current superstep | +| `send` | List of [`Send`](#send) objects to send to other nodes | +| `resume` | Value to resume execution with. Will be used when `interrupt()` is called | ```python from langgraph.graph import GraphCommand, StateGraph, START -from typing_extensions import TypedDict, Literal +from typing_extensions import Literal, TypedDict class State(TypedDict): foo: str @@ -368,6 +377,8 @@ def my_node(state: State) -> GraphCommand[Literal["my_other_node", "__end__"]]: return GraphCommand(goto="__end__") ``` +Check out this [how-to guide](../how-tos/graph-command.ipynb) for an end-to-end example of how to use `GraphCommand`. + ## Persistence LangGraph provides built-in persistence for your agent's state using [checkpointers][langgraph.checkpoint.base.BaseCheckpointSaver]. 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 diff --git a/docs/docs/how-tos/graph-command.ipynb b/docs/docs/how-tos/graph-command.ipynb index 8df59c9f4..b215c9f4a 100644 --- a/docs/docs/how-tos/graph-command.ipynb +++ b/docs/docs/how-tos/graph-command.ipynb @@ -13,7 +13,27 @@ "id": "7c0a8d03-80b4-47fd-9b17-e26aa9b081f3", "metadata": {}, "source": [ - "Typically, LangGraph separates control flow (edges) and state updates (nodes). However, it is often beneficial to combine the two. For example, you might want to BOTH perform state updates AND decide which node to go next in the SAME node. LangGraph provides a way to combine control flow and node state updates using `GraphCommand`. This guide shows how you can do so." + "!!! 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", + " - [GraphCommand](../../concepts/low_level/#graphcommand)\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 `GraphCommand` object from node functions:\n", + "\n", + "```python\n", + "def my_node(state: State) -> GraphCommand[Literal[\"my_other_node\"]]:\n", + " return GraphCommand(\n", + " # state update\n", + " update={\"foo\": \"bar\"},\n", + " # control flow\n", + " goto=\"my_other_node\"\n", + " )\n", + "```\n", + "\n", + "This guide shows how you can do use `GraphCommand` to add dynamic control flow in your LangGraph app." ] }, { @@ -60,24 +80,16 @@ }, { "cell_type": "markdown", - "id": "71c8bc81-c1b4-46aa-835f-2c2849156594", + "id": "6a08d957-b3d2-4538-bf4a-68ef90a51b98", "metadata": {}, "source": [ - "## Using edges" - ] - }, - { - "cell_type": "markdown", - "id": "9a81df3a-6489-44da-8a7e-615009ef9f59", - "metadata": {}, - "source": [ - "Let's first implement the graph with a traditional LangGraph primitives -- nodes and conditional edges. The conditional edge (`route_from_a`) will inspect the state last updated by node A and decide where to go next based on the value of the state key `foo`." + "## Control flow with GraphCommand" ] }, { "cell_type": "code", "execution_count": 2, - "id": "de32d339-3501-4982-a34f-8d3facc53579", + "id": "4539b81b-09e9-4660-ac55-1b1775e13892", "metadata": {}, "outputs": [], "source": [ @@ -91,142 +103,12 @@ "class State(TypedDict):\n", " foo: str\n", "\n", - "\n", - "# Define the nodes\n", - "def node_a(state: State):\n", - " print(\"Called A\")\n", - " return {\"foo\": random.choice([\"a\", \"b\"])}\n", - "\n", - "def node_b(state: State):\n", - " print(\"Called B\")\n", - " return {\"foo\": state[\"foo\"] + \"b\"}\n", - "\n", - "def node_c(state: State):\n", - " print(\"Called C\")\n", - " return {\"foo\": state[\"foo\"] + \"c\"}\n", - "\n", - "# Define the conditional edges\n", - "def route_from_a(state: State) -> Literal[\"node_b\", \"node_c\"]:\n", - " if state[\"foo\"] == \"a\":\n", - " return \"node_b\"\n", - " else:\n", - " return \"node_c\"" - ] - }, - { - "cell_type": "markdown", - "id": "87ef1325-d42f-4a6c-81e6-0058b9628b9e", - "metadata": {}, - "source": [ - "We can now create the StateGraph with the above nodes and conditional edges." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "b6e3044b-d817-4f7e-9e4f-1b3aff109670", - "metadata": {}, - "outputs": [], - "source": [ - "builder = StateGraph(State)\n", - "builder.add_edge(START, \"node_a\")\n", - "builder.add_node(node_a)\n", - "builder.add_node(node_b)\n", - "builder.add_node(node_c)\n", - "builder.add_conditional_edges(\"node_a\", route_from_a)\n", - "\n", - "graph = builder.compile()" - ] - }, - { - "cell_type": "markdown", - "id": "e60c8a11-ce6f-484c-ba2f-936c3d69b120", - "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": 4, - "id": "9175add8-0c08-48ee-8d70-249c5d209736", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Called A\n", - "Called C\n" - ] - }, - { - "data": { - "text/plain": [ - "{'foo': 'bc'}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "graph.invoke({\"foo\": \"\"})" - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "254eb3a1-bb47-4401-93fb-51a65b6b8e71", - "metadata": {}, - "outputs": [ - { - "data": { - "image/jpeg": "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", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "from IPython.display import display, Image\n", - "\n", - "display(Image(graph.get_graph().draw_mermaid_png()))" - ] - }, - { - "cell_type": "markdown", - "id": "0c52be14-d250-4c64-99e2-ce0a201e4523", - "metadata": {}, - "source": [ - "Now let's reimplement the same graph using `GraphCommand`!" - ] - }, - { - "cell_type": "markdown", - "id": "6a08d957-b3d2-4538-bf4a-68ef90a51b98", - "metadata": {}, - "source": [ - "## Using GraphCommand" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "37107209-34d6-4414-a54e-cd3ee38e3651", - "metadata": {}, - "outputs": [], - "source": [ "# Define the nodes\n", "\n", "def node_a(state: State) -> GraphCommand[Literal[\"node_b\", \"node_c\"]]:\n", " print(\"Called A\")\n", " value = random.choice([\"a\", \"b\"])\n", - " # this is a replacement for the logic in route_from_a\n", + " # this is a replacement for a conditional edge function\n", " if value == \"a\":\n", " goto = \"node_b\"\n", " else:\n", @@ -234,9 +116,9 @@ "\n", " # note how GraphCommand allows you to BOTH update the graph state AND route to the next node\n", " return GraphCommand(\n", - " # this is the state update, same as we returned from node A previously\n", + " # this is the state update\n", " update={\"foo\": value},\n", - " # this is a replacement for route_from_a conditional edge\n", + " # this is a replacement for an edge\n", " goto=goto\n", " )\n", "\n", @@ -257,17 +139,16 @@ "id": "badc25eb-4876-482e-bb10-d763023cdaad", "metadata": {}, "source": [ - "We can now create the `StateGraph` with the above nodes. But notice that the graph no longer uses conditional edges! This is because control flow is defined inside `node_a`." + "We can now create the `StateGraph` with the above nodes. Notice that the graph doesn't have [conditional edges](../../concepts/low_level#conditional-edges) for routing! This is because control flow is defined with `GraphCommand` inside `node_a`." ] }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "id": "d6711650-4380-4551-a007-2805f49ab2d8", "metadata": {}, "outputs": [], "source": [ - "\n", "builder = StateGraph(State)\n", "builder.add_edge(START, \"node_a\")\n", "builder.add_node(node_a)\n", @@ -290,7 +171,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "id": "eeb810e5-8822-4c09-8d53-c55cd0f5d42e", "metadata": {}, "outputs": [ @@ -306,12 +187,21 @@ } ], "source": [ + "from IPython.display import display, Image\n", "display(Image(graph.get_graph().draw_mermaid_png()))" ] }, + { + "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": 9, + "execution_count": 5, "id": "d88a5d9b-ee08-4ed4-9c65-6e868210bfac", "metadata": {}, "outputs": [ @@ -320,16 +210,16 @@ "output_type": "stream", "text": [ "Called A\n", - "Called C\n" + "Called B\n" ] }, { "data": { "text/plain": [ - "{'foo': 'bc'}" + "{'foo': 'ab'}" ] }, - "execution_count": 9, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } diff --git a/libs/langgraph/langgraph/graph/state.py b/libs/langgraph/langgraph/graph/state.py index c9b4199ab..d3f608356 100644 --- a/libs/langgraph/langgraph/graph/state.py +++ b/libs/langgraph/langgraph/graph/state.py @@ -89,14 +89,15 @@ class GraphCommand(Generic[N], Command[N]): """One or more commands to update a StateGraph's state and go to, or send messages to nodes. Args: - goto: name of the node to navigate to next. - If not specified, the graph will halt after executing the current superstep. graph: graph to send the command to. Supported values are: - None: the current graph (default) - GraphCommand.PARENT: closest parent graph update: state update to apply to the graph's state at the current superstep. send: list of `Send` objects to send to other nodes. resume: value to resume execution with. Will be used when `interrupt()` is called. + goto: name of the node to navigate to next. + Can be any node that belongs to the specified `graph` (current or parent). + If `goto` not specified, the graph will halt after executing the current superstep. """ goto: Union[str, Sequence[str]] = () From d52bb911a4d3f78519d786c96ebd8cce84433bc0 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 16:50:56 -0500 Subject: [PATCH 03/12] lint --- docs/docs/how-tos/graph-command.ipynb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/docs/how-tos/graph-command.ipynb b/docs/docs/how-tos/graph-command.ipynb index b215c9f4a..b768e75a8 100644 --- a/docs/docs/how-tos/graph-command.ipynb +++ b/docs/docs/how-tos/graph-command.ipynb @@ -103,8 +103,10 @@ "class State(TypedDict):\n", " foo: str\n", "\n", + "\n", "# Define the nodes\n", "\n", + "\n", "def node_a(state: State) -> GraphCommand[Literal[\"node_b\", \"node_c\"]]:\n", " print(\"Called A\")\n", " value = random.choice([\"a\", \"b\"])\n", @@ -119,16 +121,19 @@ " # this is the state update\n", " update={\"foo\": value},\n", " # this is a replacement for an edge\n", - " goto=goto\n", + " goto=goto,\n", " )\n", "\n", + "\n", "# Nodes B and C are unchanged\n", "\n", + "\n", "def node_b(state: State):\n", " print(\"Called B\")\n", - " # graph command can also be used \n", + " # graph command can also be used\n", " return {\"foo\": state[\"foo\"] + \"b\"}\n", "\n", + "\n", "def node_c(state: State):\n", " print(\"Called C\")\n", " return {\"foo\": state[\"foo\"] + \"c\"}" @@ -188,6 +193,7 @@ ], "source": [ "from IPython.display import display, Image\n", + "\n", "display(Image(graph.get_graph().draw_mermaid_png()))" ] }, From 257e44ccb40b6efe943f007d93221e22aa75cb53 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 18:46:29 -0500 Subject: [PATCH 04/12] update --- docs/docs/concepts/low_level.md | 32 ++++++++++++------------- docs/docs/how-tos/graph-command.ipynb | 34 +++++++++++++-------------- libs/langgraph/langgraph/types.py | 6 ++++- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 75ff6d4a3..c775eae81 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -322,13 +322,13 @@ def continue_to_jokes(state: OverallState): graph.add_conditional_edges("node_a", continue_to_jokes) ``` -## `GraphCommand` +## `Command` -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 [`GraphCommand`][langgraph.graph.state.GraphCommand] object from node functions: +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`][langgraph.graph.state.GraphCommand] object from node functions: ```python -def my_node(state: State) -> GraphCommand[Literal["my_other_node"]]: - return GraphCommand( +def my_node(state: State) -> Command[Literal["my_other_node"]]: + return Command( # state update update={"foo": "bar"}, # control flow @@ -336,25 +336,25 @@ def my_node(state: State) -> GraphCommand[Literal["my_other_node"]]: ) ``` -`GraphCommand` has the following properties: +`Command` has the following properties: | Property | Description | | --- | --- | -| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `GraphCommand.PARENT`: parent graph | -| `goto` | Name of the node to navigate to next. Can be any node that belongs to the specified `graph` (current or parent). If `goto` not specified, the graph will halt after executing the current superstep. | +| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `GraphCommand.PARENT`: closest parent graph | | `update` | State update to apply to the graph's state at the current superstep | -| `send` | List of [`Send`](#send) objects to send to other nodes | | `resume` | Value to resume execution with. Will be used when `interrupt()` is called | +| `goto` | Can be one of the following:
- name of the node to navigate to next (any node that belongs to the specified `graph`)
- list of node names to navigate to next
- `Send` object
- 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. | ```python -from langgraph.graph import GraphCommand, StateGraph, START +from langgraph.graph import StateGraph, START +from langgraph.types import Command from typing_extensions import Literal, TypedDict class State(TypedDict): foo: str -def my_node(state: State) -> GraphCommand[Literal["my_other_node"]]: - return GraphCommand(update={"foo": "bar"}, goto="my_other_node") +def my_node(state: State) -> Command[Literal["my_other_node"]]: + return Command(update={"foo": "bar"}, goto="my_other_node") def my_other_node(state: State): return {"foo": state["foo"] + "baz"} @@ -367,17 +367,17 @@ builder.add_node("my_other_node", my_other_node) graph = builder.compile() ``` -With `GraphCommand` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)): +With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)): ```python -def my_node(state: State) -> GraphCommand[Literal["my_other_node", "__end__"]]: +def my_node(state: State) -> Command[Literal["my_other_node", "__end__"]]: if state["foo"] == "bar": - return GraphCommand(update={"foo": "baz"}, goto="my_other_node") + return Command(update={"foo": "baz"}, goto="my_other_node") else: - return GraphCommand(goto="__end__") + return Command(goto="__end__") ``` -Check out this [how-to guide](../how-tos/graph-command.ipynb) for an end-to-end example of how to use `GraphCommand`. +Check out this [how-to guide](../how-tos/graph-command.ipynb) for an end-to-end example of how to use `Command`. ## Persistence diff --git a/docs/docs/how-tos/graph-command.ipynb b/docs/docs/how-tos/graph-command.ipynb index b768e75a8..ad33e3a62 100644 --- a/docs/docs/how-tos/graph-command.ipynb +++ b/docs/docs/how-tos/graph-command.ipynb @@ -5,7 +5,7 @@ "id": "d33ecddc-6818-41a3-9d0d-b1b1cbcd286d", "metadata": {}, "source": [ - "# How to combine control flow and state updates with GraphCommand" + "# How to combine control flow and state updates with Command" ] }, { @@ -19,12 +19,12 @@ " - [State](../../concepts/low_level/#state)\n", " - [Nodes](../../concepts/low_level/#nodes)\n", " - [Edges](../../concepts/low_level/#edges)\n", - " - [GraphCommand](../../concepts/low_level/#graphcommand)\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 `GraphCommand` object from node functions:\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", "```python\n", - "def my_node(state: State) -> GraphCommand[Literal[\"my_other_node\"]]:\n", + "def my_node(state: State) -> Command[Literal[\"my_other_node\"]]:\n", " return GraphCommand(\n", " # state update\n", " update={\"foo\": \"bar\"},\n", @@ -33,7 +33,7 @@ " )\n", "```\n", "\n", - "This guide shows how you can do use `GraphCommand` to add dynamic control flow in your LangGraph app." + "This guide shows how you can do use `Command` to add dynamic control flow in your LangGraph app." ] }, { @@ -83,7 +83,7 @@ "id": "6a08d957-b3d2-4538-bf4a-68ef90a51b98", "metadata": {}, "source": [ - "## Control flow with GraphCommand" + "## Control flow with Command" ] }, { @@ -96,7 +96,8 @@ "import random\n", "from typing_extensions import TypedDict, Literal\n", "\n", - "from langgraph.graph import GraphCommand, StateGraph, START\n", + "from langgraph.graph import StateGraph, START\n", + "from langgraph.types import Command\n", "\n", "\n", "# Define graph state\n", @@ -107,7 +108,7 @@ "# Define the nodes\n", "\n", "\n", - "def node_a(state: State) -> GraphCommand[Literal[\"node_b\", \"node_c\"]]:\n", + "def node_a(state: State) -> Command[Literal[\"node_b\", \"node_c\"]]:\n", " print(\"Called A\")\n", " value = random.choice([\"a\", \"b\"])\n", " # this is a replacement for a conditional edge function\n", @@ -116,8 +117,8 @@ " else:\n", " goto = \"node_c\"\n", "\n", - " # note how GraphCommand allows you to BOTH update the graph state AND route to the next node\n", - " return GraphCommand(\n", + " # note how Command allows you to BOTH update the graph state AND route to the next node\n", + " return Command(\n", " # this is the state update\n", " update={\"foo\": value},\n", " # this is a replacement for an edge\n", @@ -130,7 +131,6 @@ "\n", "def node_b(state: State):\n", " print(\"Called B\")\n", - " # graph command can also be used\n", " return {\"foo\": state[\"foo\"] + \"b\"}\n", "\n", "\n", @@ -171,7 +171,7 @@ "source": [ "!!! important\n", "\n", - " You might have noticed that we used `GraphCommand` as a return type annotation, e.g. `GraphCommand[Literal[\"node_b\", \"node_c\"]]`. This is necessary for the graph compilation and rendering, and tells LangGraph that `node_a` can navigate to `node_b` and `node_c`." + " You might have noticed that we used `Command` as a return type annotation, e.g. `Command[Literal[\"node_b\", \"node_c\"]]`. This is necessary for the graph compilation and rendering, and tells LangGraph that `node_a` can navigate to `node_b` and `node_c`." ] }, { @@ -216,13 +216,13 @@ "output_type": "stream", "text": [ "Called A\n", - "Called B\n" + "Called C\n" ] }, { "data": { "text/plain": [ - "{'foo': 'ab'}" + "{'foo': 'bc'}" ] }, "execution_count": 5, @@ -237,9 +237,9 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "langgraph", "language": "python", - "name": "python3" + "name": "langgraph" }, "language_info": { "codemirror_mode": { @@ -251,7 +251,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 98f9e350d..28e5a5940 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -252,8 +252,12 @@ class Command(Generic[N]): - None: the current graph (default) - GraphCommand.PARENT: closest parent graph update: state update to apply to the graph's state at the current superstep. - send: list of `Send` objects to send to other nodes. resume: value to resume execution with. Will be used when `interrupt()` is called. + goto: can be one of the following: + - name of the node to navigate to next (any node that belongs to the specified `graph`) + - list of node names to navigate to next + - `Send` object + - sequence of `Send` objects """ graph: Optional[str] = None From 5570121c8388d2d2c32c5f9f8204646c4657cb8c Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 18:56:46 -0500 Subject: [PATCH 05/12] update --- docs/docs/concepts/low_level.md | 4 ++-- docs/docs/reference/graphs.md | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index c775eae81..d06dea750 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -324,7 +324,7 @@ graph.add_conditional_edges("node_a", continue_to_jokes) ## `Command` -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`][langgraph.graph.state.GraphCommand] object from node functions: +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`][langgraph.types.Command] object from node functions: ```python def my_node(state: State) -> Command[Literal["my_other_node"]]: @@ -340,7 +340,7 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]: | Property | Description | | --- | --- | -| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `GraphCommand.PARENT`: closest parent graph | +| `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `Command.PARENT`: closest parent graph | | `update` | State update to apply to the graph's state at the current superstep | | `resume` | Value to resume execution with. Will be used when `interrupt()` is called | | `goto` | Can be one of the following:
- name of the node to navigate to next (any node that belongs to the specified `graph`)
- list of node names to navigate to next
- `Send` object
- 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. | diff --git a/docs/docs/reference/graphs.md b/docs/docs/reference/graphs.md index fef38e159..c67e2136a 100644 --- a/docs/docs/reference/graphs.md +++ b/docs/docs/reference/graphs.md @@ -11,7 +11,6 @@ members: - StateGraph - CompiledStateGraph - - GraphCommand ::: langgraph.graph.message options: From 19a6e894eb8e39b2a2d085c2c2c7890dbe7f4d06 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 19:02:49 -0500 Subject: [PATCH 06/12] more updates --- docs/docs/concepts/low_level.md | 3 +++ docs/docs/how-tos/graph-command.ipynb | 8 ++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index d06dea750..3b3806a97 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -283,6 +283,9 @@ You can optionally provide a dictionary that maps the `routing_function`'s outpu graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: "node_c"}) ``` +!!! tip + Use [`Command`](#command) instead of conditional edges if you need to combine state updates and routing. + ### Entry Point The entry point is the first node(s) that are run when the graph starts. You can use the [`add_edge`][langgraph.graph.StateGraph.add_edge] method from the virtual [`START`][langgraph.constants.START] node to the first node to execute to specify where to enter the graph. diff --git a/docs/docs/how-tos/graph-command.ipynb b/docs/docs/how-tos/graph-command.ipynb index ad33e3a62..3f3a4c0ef 100644 --- a/docs/docs/how-tos/graph-command.ipynb +++ b/docs/docs/how-tos/graph-command.ipynb @@ -83,7 +83,7 @@ "id": "6a08d957-b3d2-4538-bf4a-68ef90a51b98", "metadata": {}, "source": [ - "## Control flow with Command" + "## Define graph" ] }, { @@ -237,9 +237,9 @@ ], "metadata": { "kernelspec": { - "display_name": "langgraph", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "langgraph" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -251,7 +251,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.9" + "version": "3.12.3" } }, "nbformat": 4, From 085395c824b2cdbbe9449a1763e09459f48a771f Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 19:04:42 -0500 Subject: [PATCH 07/12] rename --- docs/docs/concepts/low_level.md | 2 +- docs/docs/how-tos/{graph-command.ipynb => command.ipynb} | 0 docs/docs/how-tos/index.md | 2 +- docs/mkdocs.yml | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename docs/docs/how-tos/{graph-command.ipynb => command.ipynb} (100%) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 3b3806a97..fab9c3e03 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -380,7 +380,7 @@ def my_node(state: State) -> Command[Literal["my_other_node", "__end__"]]: return Command(goto="__end__") ``` -Check out this [how-to guide](../how-tos/graph-command.ipynb) for an end-to-end example of how to use `Command`. +Check out this [how-to guide](../how-tos/command.ipynb) for an end-to-end example of how to use `Command`. ## Persistence diff --git a/docs/docs/how-tos/graph-command.ipynb b/docs/docs/how-tos/command.ipynb similarity index 100% rename from docs/docs/how-tos/graph-command.ipynb rename to docs/docs/how-tos/command.ipynb diff --git a/docs/docs/how-tos/index.md b/docs/docs/how-tos/index.md index 1c60818ae..297bfd3e0 100644 --- a/docs/docs/how-tos/index.md +++ b/docs/docs/how-tos/index.md @@ -20,7 +20,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 control graph recursion limit](recursion-limit.ipynb) -- [How to combine control flow and state updates with GraphCommand](graph-command.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 882e3cdfd..bc1ff59ea 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -151,7 +151,7 @@ nav: - how-tos/branching.ipynb - how-tos/map-reduce.ipynb - how-tos/recursion-limit.ipynb - - how-tos/graph-command.ipynb + - how-tos/command.ipynb - Persistence: - Persistence: how-tos#persistence - how-tos/persistence.ipynb From 6caaa8cea7aed7e9cd3e11ff8b22ad6c795a8724 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Wed, 4 Dec 2024 19:16:26 -0500 Subject: [PATCH 08/12] Update libs/langgraph/langgraph/types.py Co-authored-by: Nuno Campos --- libs/langgraph/langgraph/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 28e5a5940..fcfd99318 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -256,7 +256,7 @@ class Command(Generic[N]): goto: can be one of the following: - name of the node to navigate to next (any node that belongs to the specified `graph`) - list of node names to navigate to next - - `Send` object + - `Send` object (to execute a node with the input provided) - sequence of `Send` objects """ From 1a492f727c12d1c1dfcd5719d82c026e67ca6f2c Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Wed, 4 Dec 2024 19:16:33 -0500 Subject: [PATCH 09/12] Update libs/langgraph/langgraph/types.py Co-authored-by: Nuno Campos --- libs/langgraph/langgraph/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index fcfd99318..6503bde3b 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -251,7 +251,7 @@ class Command(Generic[N]): graph: graph to send the command to. Supported values are: - None: the current graph (default) - GraphCommand.PARENT: closest parent graph - update: state update to apply to the graph's state at the current superstep. + update: update to apply to the graph's state. resume: value to resume execution with. Will be used when `interrupt()` is called. goto: can be one of the following: - name of the node to navigate to next (any node that belongs to the specified `graph`) From 7651f1ab1cfc32ddea62271ccd0922722194b326 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Wed, 4 Dec 2024 19:17:22 -0500 Subject: [PATCH 10/12] Update libs/langgraph/langgraph/types.py Co-authored-by: Nuno Campos --- libs/langgraph/langgraph/types.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 6503bde3b..1780c0a7f 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -252,7 +252,7 @@ class Command(Generic[N]): - None: the current graph (default) - GraphCommand.PARENT: closest parent graph update: update to apply to the graph's state. - resume: value to resume execution with. Will be used when `interrupt()` is called. + resume: value to resume execution with. To be used together with `interrupt()`. goto: can be one of the following: - name of the node to navigate to next (any node that belongs to the specified `graph`) - list of node names to navigate to next From e9cd216887c3fcdfe25ec0090b51bb5c0011a655 Mon Sep 17 00:00:00 2001 From: Vadym Barda Date: Wed, 4 Dec 2024 19:18:37 -0500 Subject: [PATCH 11/12] Update docs/docs/concepts/low_level.md Co-authored-by: Nuno Campos --- docs/docs/concepts/low_level.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index fab9c3e03..b3df3c882 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -284,7 +284,7 @@ graph.add_conditional_edges("node_a", routing_function, {True: "node_b", False: ``` !!! tip - Use [`Command`](#command) instead of conditional edges if you need to combine state updates and routing. + Use [`Command`](#command) instead of conditional edges if you want to combine state updates and routing in a single function. ### Entry Point From 1eeb90ae0d77bbe280df85c620dbbccd16323367 Mon Sep 17 00:00:00 2001 From: vbarda Date: Wed, 4 Dec 2024 19:31:46 -0500 Subject: [PATCH 12/12] cr --- docs/docs/concepts/low_level.md | 14 ++++++++------ docs/docs/reference/types.md | 1 + libs/langgraph/langgraph/types.py | 6 ++++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index b3df3c882..1d059568b 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -344,9 +344,9 @@ def my_node(state: State) -> Command[Literal["my_other_node"]]: | Property | Description | | --- | --- | | `graph` | Graph to send the command to. Supported values:
- `None`: the current graph (default)
- `Command.PARENT`: closest parent graph | -| `update` | State update to apply to the graph's state at the current superstep | -| `resume` | Value to resume execution with. Will be used when `interrupt()` is called | -| `goto` | Can be one of the following:
- name of the node to navigate to next (any node that belongs to the specified `graph`)
- list of node names to navigate to next
- `Send` object
- 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. | +| `update` | Update to apply to the graph's state. | +| `resume` | Value to resume execution with. To be used together with [`interrupt()`][langgraph.types.interrupt]. | +| `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` 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. | ```python from langgraph.graph import StateGraph, START @@ -373,13 +373,15 @@ graph = builder.compile() With `Command` you can also achieve dynamic control flow behavior (identical to [conditional edges](#conditional-edges)): ```python -def my_node(state: State) -> Command[Literal["my_other_node", "__end__"]]: +def my_node(state: State) -> Command[Literal["my_other_node"]]: if state["foo"] == "bar": return Command(update={"foo": "baz"}, goto="my_other_node") - else: - return Command(goto="__end__") ``` +!!! important + + When returning `Command` in your node functions, you must add return type annotations with the list of node names the node is routing to, e.g. `Command[Literal["node_b", "node_c"]]`. This is necessary for the graph compilation and rendering, and tells LangGraph that `node_a` can navigate to `node_b` and `node_c`. + Check out this [how-to guide](../how-tos/command.ipynb) for an end-to-end example of how to use `Command`. ## Persistence diff --git a/docs/docs/reference/types.md b/docs/docs/reference/types.md index 98b1ef137..b42b11f35 100644 --- a/docs/docs/reference/types.md +++ b/docs/docs/reference/types.md @@ -14,3 +14,4 @@ - StateSnapshot - Send - Command + - interrupt diff --git a/libs/langgraph/langgraph/types.py b/libs/langgraph/langgraph/types.py index 1780c0a7f..d4fb5ab55 100644 --- a/libs/langgraph/langgraph/types.py +++ b/libs/langgraph/langgraph/types.py @@ -249,13 +249,15 @@ class Command(Generic[N]): Args: graph: graph to send the command to. Supported values are: + - None: the current graph (default) - GraphCommand.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()`. + resume: value to resume execution with. To be used together with [`interrupt()`][langgraph.types.interrupt]. goto: can be one of the following: + - name of the node to navigate to next (any node that belongs to the specified `graph`) - - list of node names to navigate to next + - sequence of node names to navigate to next - `Send` object (to execute a node with the input provided) - sequence of `Send` objects """