diff --git a/docs/docs/concepts/human_in_the_loop.md b/docs/docs/concepts/human_in_the_loop.md index 45ce792d4..77a8114dd 100644 --- a/docs/docs/concepts/human_in_the_loop.md +++ b/docs/docs/concepts/human_in_the_loop.md @@ -1,20 +1,17 @@ # Human-in-the-loop -Human-in-the-loop (or "on-the-loop") enhances agent capabilities through several common user interaction patterns. +Human-in-the-loop (or "on-the-loop") workflows enhance agent capabilities through several common user interaction patterns. Common interaction patterns include: -(1) `Approval` - We can interrupt our agent, surface the current state to a user, and allow the user to accept an action. - -(2) `Editing` - We can interrupt our agent, surface the current state to a user, and allow the user to edit the agent state. - -(3) `Input` - We can explicitly create a graph node to collect human input and pass that input directly to the agent state. +1. **Approval**: Pause the agent, present its current state to the user, and allow the user to approve or reject a proposed action. +2. **Editing**: Pause the agent, present its current state to the user, and allow the user to make modifications to the agent's state. +3. **Input**: Introduce a dedicated graph node to explicitly collect user input, which is then integrated into the agent's state. Use-cases for these interaction patterns include: -(1) `Reviewing tool calls` - We can interrupt an agent to review and edit the results of tool calls. - -(2) `Time Travel` - We can manually re-play and / or fork past actions of an agent. +1. `Reviewing tool calls` - We can interrupt an agent to review and edit the results of tool calls. +2. `Time travel` - We can manually re-play and / or fork past actions of an agent. ## Persistence diff --git a/docs/docs/concepts/low_level.md b/docs/docs/concepts/low_level.md index 1d059568b..266ced5be 100644 --- a/docs/docs/concepts/low_level.md +++ b/docs/docs/concepts/low_level.md @@ -451,25 +451,54 @@ Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-li ## Breakpoints -It can often be useful to set breakpoints before or after certain nodes execute. This can be used to wait for human approval before continuing. These can be set when you ["compile" a graph](#compiling-your-graph). You can set breakpoints either _before_ a node executes (using `interrupt_before`) or after a node executes (using `interrupt_after`.) +Breakpoints enable **human-in-the-loop** workflows by **pausing** graph execution to allow for human review before continuing. -You **MUST** use a [checkpointer](./persistence.md) when using breakpoints. This is because your graph needs to be able to resume execution. +You **MUST** use a [checkpointer](./persistence.md) when using breakpoints as breakpoints require the ability to save the state of the graph at the time of pausing. -In order to resume execution, you can just invoke your graph with `None` as the input. +There are two types of breakpoints: + +1. **Static breakpoints**: Pause the graph **before** or **after** a node executes. +2. **Dynamic breakpoints**: Pause the graph from **inside** a node. + +### Static Breakpoints + +To set static breakpoints, specify the `interrupt_before` and/or `interrupt_after` key when [compiling your graph](#compiling-your-graph). ```python -# Initial run of graph -graph.invoke(inputs, config=config) - -# Let's assume it hit a breakpoint somewhere, you can then resume by passing in None -graph.invoke(None, config=config) +graph = graph_builder.compile( + interrupt_before=["node_a"], + interrupt_after=["node_b", "node_c"], + checkpointer=..., # Required +) ``` -See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for a full walkthrough of how to add breakpoints. +When using sub-graphs, specify the `interrupt_before` and `interrupt_after` values when compiling the subgraph. -### Dynamic Breakpoints +### Dynamic Breakpoints -It may be helpful to **dynamically** interrupt the graph from inside a given node based on some condition. In `LangGraph` you can do so by using `NodeInterrupt` -- a special exception that can be raised from inside a node. +There are two ways to interrupt the graph dynamically: + +1. `interrupt` **function (recommended)**: Interrupts the graph within a node and surfaces a value to the client as part of the interrupt information. +2. `NodeInterrupt` exception: An older, less flexible method for interrupting. + +#### `interrupt` + +```python +from langgraph.types import interrupt + +def node(state: State): + ... + client_value = interrupt( + # This value will be sent to the client. + # It can be any JSON serializable value. + {"key": "value"} + ) + ... +``` + +#### `NodeInterrupt` + +Throw a `NodeInterrupt` exception to interrupt the graph. ```python def my_node(state: State) -> State: @@ -479,6 +508,46 @@ def my_node(state: State) -> State: return state ``` +### Resuming + +When a breakpoint is hit, graph execution will pause. + +=== "Command" + + Resume execution using the new `Command` primitive. + + ```python + graph.invoke(inputs, config=config) # This will pause at the breakpoint + ... + # Do something (e.g., get human input) + ... + graph.invoke( + Command( + # Use `resume` to pass a value to the `interrupt`. + resume=resume, + # For other kinds of breakpoints, use `update` to update the state. + update=update, + ), + config=config + ) + ``` + +=== "Without the Command Primitive" + + Resume execution without the `Command` primitive (older versions of LangGraph). + + ```python + graph.invoke(inputs, config=config) # This will pause at the breakpoint + ... + # Do something (e.g., get human input) + ... + + graph.update_state(update, config=config) + graph.invoke(None, config=config) + ``` + +See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for a full walkthrough of how to add breakpoints. + ## Subgraphs A subgraph is a [graph](#graphs) that is used as a [node](#nodes) in another graph. This is nothing more than the age-old concept of encapsulation, applied to LangGraph. Some reasons for using subgraphs are: