diff --git a/docs/docs/concepts/human_in_the_loop.md b/docs/docs/concepts/human_in_the_loop.md index 442a98b35..aa26f44a5 100644 --- a/docs/docs/concepts/human_in_the_loop.md +++ b/docs/docs/concepts/human_in_the_loop.md @@ -28,7 +28,7 @@ To review, edit, and approve tool calls in an agent or workflow, [use LangGraph' There are two ways to pause a graph: - [Dynamic interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#pause-using-interrupt): Use `interrupt` to pause a graph from inside a specific node, based on the current state of the graph. - - [Static interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#debug-with-interrupts): Use `interrupt_before` and `interrupt_after` to pause the graph at defined points, either before or after a node executes. + - [Static interrupts](../how-tos/human_in_the_loop/add-human-in-the-loop.md#debug-with-interrupts): Use `interrupt_before` and `interrupt_after` to pause the graph at pre-defined points, either before or after a node executes.
![image](./img/breakpoints.png){: style="max-height:400px"} diff --git a/docs/docs/how-tos/assets/human_in_loop_parallel.png b/docs/docs/how-tos/assets/human_in_loop_parallel.png new file mode 100644 index 000000000..0fdb9da95 Binary files /dev/null and b/docs/docs/how-tos/assets/human_in_loop_parallel.png differ diff --git a/docs/docs/how-tos/human_in_the_loop/add-human-in-the-loop.md b/docs/docs/how-tos/human_in_the_loop/add-human-in-the-loop.md index e9d18ea4a..45c5b3ff7 100644 --- a/docs/docs/how-tos/human_in_the_loop/add-human-in-the-loop.md +++ b/docs/docs/how-tos/human_in_the_loop/add-human-in-the-loop.md @@ -128,7 +128,7 @@ print(graph.invoke(Command(resume="Edited text"), config=config)) # (7)! !!! tip "New in 0.4.0" - `__interrupt__` is a special key that will be returned when running the graph if the graph is interrupted. Support for `__interrupt__` in `invoke` and `ainvoke` has been added in version 0.4.0. If you're on an older version, you will only see `__interrupt__` in the result if you use `stream` or `astream`. You can also use `graph.get_state(thread_id)` to get the interrupt value. + `__interrupt__` is a special key that will be returned when running the graph if the graph is interrupted. Support for `__interrupt__` in `invoke` and `ainvoke` has been added in version 0.4.0. If you're on an older version, you will only see `__interrupt__` in the result if you use `stream` or `astream`. You can also use `graph.get_state(thread_id)` to get the interrupt value(s). !!! warning @@ -145,21 +145,99 @@ To resume execution, use the [`Command`][langgraph.types.Command] primitive, whi graph.invoke(Command(resume={"age": "25"}), thread_config) ``` -### Resume multiple interrupts with one invocation +### Multiple interrupts -If you have multiple interrupts in the task queue, you can use `Command.resume` with a dictionary mapping of interrupt ids to resume with a single `invoke` / `stream` call. +When nodes with interrupt conditions are run in parallel, it's possible to have multiple interrupts in the task queue. +For example, the following graph has two nodes run in parallel that require human input: -For example, once your graph has been interrupted (multiple times, theoretically) and is stalled: +
+![image](../assets/human_in_loop_parallel.png){: style="max-height:400px"} +
+ +Once your graph has been interrupted and is stalled, you can resume all the interrupts at once with `Command.resume`, passing a dictionary mapping of interrupt ids to resume values. ```python -resume_map = { - i.id: f"human input for prompt {i.value}" - for i in parent.get_state(thread_config).interrupts -} +# Run the graph, resulting in two interrupts +result = graph.invoke( + { + 'text_1': 'original text 1', + 'text_2': 'original text 2' + }, + config=thread_config +) -parent_graph.invoke(Command(resume=resume_map), config=thread_config) +print(result["__interrupt__"]) +""" +[ + Interrupt(value={'text_to_revise': 'original text 1'}, id='bba46aa8d060d6e1edbc6cd913a96494'), + Interrupt(value={'text_to_revise': 'original text 2'}, id='22632249d75d67e0e718c1d18596c78a') +] +""" + +# Create a mapping of interrupt ids to corresponding resume values +resume_map = { + i.id: f"edited text for {i.value['text_to_revise']}" + for i in result["__interrupt__"] +} +graph.invoke(Command(resume=resume_map), config=thread_config) ``` +!!! example "Extended example: resume multiple interrupts" + + ```python + from typing import TypedDict + import uuid + from langchain_core.runnables import RunnableConfig + from langgraph.checkpoint.memory import InMemorySaver + from langgraph.constants import START + from langgraph.graph import StateGraph + from langgraph.types import interrupt, Command + + + class State(TypedDict): + text_1: str + text_2: str + + + def human_node_1(state: State): + value = interrupt({"text_to_revise": state["text_1"]}) + return {"text_1": value} + + + def human_node_2(state: State): + value = interrupt({"text_to_revise": state["text_2"]}) + return {"text_2": value} + + + # Build the graph + graph_builder = StateGraph(State) + graph_builder.add_node("human_node_1", human_node_1) + graph_builder.add_node("human_node_2", human_node_2) + + # Add both nodes in parallel from START + graph_builder.add_edge(START, "human_node_1") + graph_builder.add_edge(START, "human_node_2") + + checkpointer = InMemorySaver() + graph = graph_builder.compile(checkpointer=checkpointer) + + # Pass a thread ID to the graph to run it + thread_id = str(uuid.uuid4()) + config: RunnableConfig = {"configurable": {"thread_id": thread_id}} + + # Run the graph until both interrupts are hit + result = graph.invoke( + {"text_1": "original text 1", "text_2": "original text 2"}, config=config + ) + + interrupts = result["__interrupt__"] + resume_map = {i.id: f"edited text for {i.value['text_to_revise']}" for i in interrupts} + + # Resume with mapping of interrupt IDs to values + print(graph.invoke(Command(resume=resume_map), config=config)) + # > {'text_1': 'edited text for original text 1', 'text_2': 'edited text for original text 2'} + ``` + ## Common patterns Below we show different design patterns that can be implemented using `interrupt` and `Command`.