diff --git a/docs/docs/concepts/human_in_the_loop.md b/docs/docs/concepts/human_in_the_loop.md index 6d5bac05c..f306aaffe 100644 --- a/docs/docs/concepts/human_in_the_loop.md +++ b/docs/docs/concepts/human_in_the_loop.md @@ -9,18 +9,24 @@ A **human-in-the-loop** (or "on-the-loop") workflow integrates human input into automated processes, allowing for decisions, validation, or corrections at key stages. This is especially useful in **LLM-based applications**, where the underlying model may generate occasional inaccuracies. In low-error-tolerance scenarios like compliance, decision-making, or content generation, human involvement ensures reliability by enabling review, correction, or override of model outputs. +## Interaction Patterns + +1. **Approval**/**Rejection**: Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected, you can prevent the graph from executing the step, and potentially take an alternative action. +2. **Editing**: Pause the graph to review and edit the agent's state. This is useful for correcting mistakes or updating the agent's state. +3. **Input**: Explicitly request human input at a particular step in the graph. This is useful for collecting additional information or context to inform the agent's decision-making process. + ## Use cases Key use cases for **human-in-the-loop** workflows in LLM-based applications include: 1. **πŸ› οΈ [Reviewing tool calls](#reviewing-tool-calls)**: Humans can review, edit, or approve tool calls requested by the LLM before tool execution. -2. **βœ… Validating LLM outputs**: Ensure accuracy by reviewing, editing, or approving content generated by the LLM. +2. **βœ… Validating LLM outputs**: Humans can review, edit, or approve content generated by the LLM. 3. **πŸ’‘ Providing context**: Enable the LLM to explicitly request human input for clarification or additional details. 4. **πŸ” Debugging**: Investigate and correct errors in the LLM's decision-making process. ## Interrupt & Resume -**Human-in-the-loop** works in the following manner: +**Human-in-the-loop** workflow consists of four key steps: 1. [**Persistence**](./persistence.md): the graph state is saved after each graph step, enabling **pausing** and **resuming** execution. 2. [**Interrupting execution**](#interrupting-execution): the [`interrupt`](../reference/types.md#langgraph.types.interrupt) function is used to **pause** the graph at specific points for **human input**. @@ -57,10 +63,6 @@ graph = graph_builder.compile(checkpointer=checkpointer) ??? warning "Graph execution resumes from the beginning of the node not the exact point of the `interrupt`" - Execution always resumes from the **beginning** of the **graph node**, not the exact point of the `interrupt`, unlike a traditional breakpoint or Python's `input()` function. Keep the following considerations in mind when using the `interrupt` function: - - 1. **Side effects**: Place side-effecting code, such as API calls, **after** the `interrupt` to avoid duplication, as these are re-triggered every time the node resumes. - 2. **Multiple interrupts**: Using multiple `interrupt` calls in a node is useful (e.g., for run-time validation), but the order and number of calls must remain consistent to prevent mismatched resume values. ### Run @@ -112,37 +114,14 @@ You should see the following output printed by to the `print` function in the `n Value received from interrupt: {'age': '25'} ``` -## Resuming Execution - -Execution is always resumed from the **beginning** of the **graph node** where the `interrupt` was used. - -## Options for resuming execution - -After an `interrupt`, graph execution can be resumed using the [Command](../reference/types.md#langgraph.types.Command) primitive. The `Command` primitive provides several options to control and modify the graph's state during resumption: - -1. **Pass a value to the `interrupt`**: Provide data, such as a user's response, to the graph using `Command(resume=value)`. Execution resumes from the beginning of the node where the `interrupt` was used, however, this time the `interrupt(...)` call will return the value passed in the `Command(resume=value)` instead of pausing the graph. -2. **Update the graph state**: Modify the graph state using `Command(update=update)`. Note that resumption starts from the beginning of the node where the `interrupt` was used. Execution resumes from the beginning of the node where the `interrupt` was used, but with the updated state. -3. **Navigate to another node**: Direct the graph to continue execution at a different node using `Command(goto="node_name")`. - -Here’s how resumption works in practice: - -```python -# Resume graph execution with the user's input. -graph.invoke(Command(resume={"age": "25"}), thread_config) -``` - -By leveraging `Command`, you can resume graph execution, handle user inputs, and dynamically adjust the graph's state or flow. - - -??? note "Using other types of breakpoints" - - The `interrupt` function was introduced to address difficulties with the older methods that necessitated updating the graph state when resuming execution. You can read more about these methods in the [low-level guide](./low_level.md#breakpoints). A [previous version of this guide](v0-human-in-the-loop.md) covers the older method of setting breakpoints using **static breakpoints** and the `NodeInterrupt` exception. - - See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for a full walkthrough of how to add breakpoints. - - ## Interaction Patterns +1. **Approval**/**Rejection**: Pause the graph before a critical step, such as an API call, to review and approve the action. If the action is rejected. + +2. **Editing**: Pause the graph to review and edit the agent's state. This is useful for correcting mistakes or updating the agent's state. + + + ### Approval ![](./img/human_in_the_loop/approval.png) @@ -286,87 +265,40 @@ for event in graph.stream(None, thread, stream_mode="values"): See [the how to review tool calls guide](../how-tos/human_in_the_loop/review-tool-calls.ipynb) for a details. -### Time Travel -When working with agents, we often want closely examine their decision making process: +## Advanced -(1) Even when they arrive a desired final result, the reasoning that led to that result is often important to examine. +### How does an `interrupt` work? -(2) When agents make mistakes, it is often valuable to understand why. +Execution always resumes from the **beginning** of the **graph node**, not the exact point of the `interrupt`. -(3) In either of the above cases, it is useful to manually explore alternative decision making paths. +Please note that this is **unlike** a traditional breakpoint or Python's `input()` function. As a result, you should structure your graph nodes to handle the `interrupt` and `resume` logic effectively. -Collectively, we call these debugging concepts `time-travel` and they are composed of `replaying` and `forking`. +Keep the following considerations in mind when using the `interrupt` function: -#### Replaying +1. **Side effects**: Place side-effecting code, such as API calls, **after** the `interrupt` to avoid duplication, as these are re-triggered every time the node resumes. +2. **Multiple interrupts**: Using multiple `interrupt` calls in a node can be very useful (e.g., for run-time validation), but the order and number of calls must remain consistent to prevent mismatched resume values. -![](./img/human_in_the_loop/replay.png) -Sometimes we want to simply replay past actions of an agent. - -Above, we showed the case of executing an agent from the current state (or checkpoint) of the graph. +### Options for resuming execution -We by simply passing in `None` for the input with a `thread`. +After an `interrupt`, graph execution can be resumed using the [Command](../reference/types.md#langgraph.types.Command) primitive. The `Command` primitive provides several options to control and modify the graph's state during resumption: -``` -thread = {"configurable": {"thread_id": "1"}} -for event in graph.stream(None, thread, stream_mode="values"): - print(event) -``` - -Now, we can modify this to replay past actions from a *specific* checkpoint by passing in the checkpoint ID. - -To get a specific checkpoint ID, we can easily get all of the checkpoints in the thread and filter to the one we want. +1. **Pass a value to the `interrupt`**: Provide data, such as a user's response, to the graph using `Command(resume=value)`. Execution resumes from the beginning of the node where the `interrupt` was used, however, this time the `interrupt(...)` call will return the value passed in the `Command(resume=value)` instead of pausing the graph. +2. **Update the graph state**: Modify the graph state using `Command(update=update)`. Note that resumption starts from the beginning of the node where the `interrupt` was used. Execution resumes from the beginning of the node where the `interrupt` was used, but with the updated state. +3. **Navigate to another node**: Direct the graph to continue execution at a different node using `Command(goto="node_name")`. ```python -all_checkpoints = [] -for state in app.get_state_history(thread): - all_checkpoints.append(state) +# Resume graph execution with the user's input. +graph.invoke(Command(resume={"age": "25"}), thread_config) ``` -Each checkpoint has a unique ID, which we can use to replay from a specific checkpoint. +By leveraging `Command`, you can resume graph execution, handle user inputs, and dynamically adjust the graph's state or flow. -Assume from reviewing the checkpoints that we want to replay from one, `xxx`. +??? note "Using other types of breakpoints" -We just pass in the checkpoint ID when we run the graph. + The `interrupt` function was introduced to address difficulties with the older methods that necessitated updating the graph state when resuming execution. You can read more about these methods in the [low-level guide](./low_level.md#breakpoints). A [previous version of this guide](v0-human-in-the-loop.md) covers the older method of setting breakpoints using **static breakpoints** and the `NodeInterrupt` exception. -```python -config = {'configurable': {'thread_id': '1', 'checkpoint_id': 'xxx'}} -for event in graph.stream(None, config, stream_mode="values"): - print(event) -``` - -Importantly, the graph knows which checkpoints have been previously executed. + See [this guide](../how-tos/human_in_the_loop/breakpoints.ipynb) for a full walkthrough of how to add breakpoints. -So, it will re-play any previously executed nodes rather than re-executing them. -See [this additional conceptual guide](https://langchain-ai.github.io/langgraph/concepts/persistence/#replay) for related context on replaying. - -See see [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for a detailed how-to on doing time-travel! - -#### Forking - -![](./img/human_in_the_loop/forking.png) - -Forking allows us to revisit an agent's past actions and explore alternative paths through the graph. - -The **Editing** pattern, as described earlier, enables modifications to the *current* state of the graph. But what if you want to fork from a *past* state? - -For instance, suppose you want to edit a specific checkpoint, such as `xyz`. You can achieve this by providing the relevant `checkpoint_id` when updating the graph's state. - -```python -config = {"configurable": {"thread_id": "1", "checkpoint_id": "xyz"}} -graph.update_state(config, {"state": "updated state"}, ) -``` - -This creates a new forked checkpoint, `xyz-fork`, which we can then run the graph from. - -```python -config = {'configurable': {'thread_id': '1', 'checkpoint_id': 'xyz-fork'}} -for event in graph.stream(None, config, stream_mode="values"): - print(event) -``` - -See [this additional conceptual guide](https://langchain-ai.github.io/langgraph/concepts/persistence/#update-state) for related context on forking. - -See see [this guide](../how-tos/human_in_the_loop/time-travel.ipynb) for a detailed how-to on doing time-travel!