mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-17 21:25:46 +02:00
better hitl multi interrupt resume docs
This commit is contained in:
@@ -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.
|
||||
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
@@ -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:
|
||||
<figure markdown="1">
|
||||
{: style="max-height:400px"}
|
||||
</figure>
|
||||
|
||||
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`.
|
||||
|
||||
Reference in New Issue
Block a user