This commit is contained in:
Eugene Yurtsev
2024-12-11 11:32:33 -05:00
parent bec3055561
commit 6907d1b775
4 changed files with 113 additions and 188 deletions
+5 -164
View File
@@ -1,6 +1,6 @@
# Breakpoints
Breakpoints pause graph execution at specific points, enabling [**human-in-the-loop**](./human_in_the_loop.md) workflows and debugging. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step.
Breakpoints pause graph execution at specific points and enable stepping through execution step by step. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step. Breakpoints can also be used to enable [**human-in-the-loop**](./human_in_the_loop.md) workflows, though we recommend using the [`interrupt` function](#the-interrupt-function) for this purpose.
## Requirements
@@ -15,68 +15,9 @@ To use breakpoints, you will need to:
There are two places where you can set breakpoints:
1. **Inside** a node using the [`interrupt` function](#the-interrupt-function) (or the older [`NodeInterrupt` exception](#nodeinterrupt-exception)).
2. **Before** or **after** a node executes by setting breakpoints at **compile time** or **run time**. We call these [**static breakpoints**](#static-breakpoints).
1. **Before** or **after** a node executes by setting breakpoints at **compile time** or **run time**. We call these [**static breakpoints**](#static-breakpoints).
2. **Inside** a node using the [`NodeInterrupt` exception](#nodeinterrupt-exception).
The **recommended** way to set breakpoints is using the [`interrupt` function](#the-interrupt-function). This method is easier to use and more flexible than the older methods.
### The `interrupt` function
Use the [interrupt](../reference/types.md/#langgraph.types.interrupt) function to **pause** the graph at specific points to collect user input. The `interrupt` function surfaces interrupt information to the client, allowing the developer to collect user input, validate the graph state, or make decisions before resuming execution.
```python
from langgraph.types import interrupt
def human_approval(state: State):
...
answer = interrupt(
# Interrupt information to surface to the client.
# Can be any JSON serializable value.
{
"question": "Can we proceed?",
"llm_output": state["llm_output"]
}
)
if answer['approved']:
# Proceed with the action
...
else:
# Do something else
...
# Add the node to the graph
graph_builder.add_node("human_approval", human_approval)
# Compile the graph with a checkpointer
graph = graph_builder.compile(checkpointer=checkpointer)
# Run the graph until the breakpoint
thread_config = {"configurable": {"thread_id": "some_id"}}
for event in graph.stream(inputs, thread_config, stream_mode="values"):
print(event)
```
```pycon
{'__interrupt__': (
Interrupt(
value={'question': 'Can we proceed?', "llm_output": "..."},
resumable=True,
ns=['node:5df255f7-d683-1a99-b7c8-00dd534aed8e'],
when='during'
),
)
}
```
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:
```python
# Resume the graph with the user's input
for event in graph.stream(Command(resume={"approved": True}), config=thread_config):
print(event)
```
### Static breakpoints
Static breakpoints are triggered either **before** or **after** a node executes. You can set static breakpoints by specifying `interrupt_before` and `interrupt_after` at **"compile" time** or **run time**.
@@ -142,7 +83,8 @@ node at a time or if you want to pause the graph execution at specific nodes.
### `NodeInterrupt` exception
We recommend that you [**use the `interrupt` function instead**](#the-interrupt-function) of the `NodeInterrupt` exception. The `interrupt` function is easier to use and more flexible.
We recommend that you [**use the `interrupt` function instead**](#the-interrupt-function) of the `NodeInterrupt` exception if you're trying to implement
[human-in-the-loop](./human_in_the_loop.md) workflows. The `interrupt` function is easier to use and more flexible.
??? node "`NodeInterrupt` exception"
@@ -183,107 +125,6 @@ We recommend that you [**use the `interrupt` function instead**](#the-interrupt-
print(event)
```
## The `Command` primitive
When using the `interrupt` function, the graph will pause at the breakpoint and wait for user input.
Graph execution can be resumed using the [Command](../reference/types.md#langgraph.types.Command) primitive which can be passed through the `invoke`, `ainvoke`, `stream` or `astream` methods.
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. The `resume` value is only used when using `interrupt` as a breakpoint.
```python
# Resume graph execution with the user's input.
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
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.
```python
# Update the graph state and resume.
# You must provide a `resume` value if using an `interrupt`.
graph.invoke(Command(update={"foo": "bar"}, resume="Let's go!!!"), thread_config)
```
By leveraging `Command`, you can resume graph execution, handle user inputs, and dynamically adjust the graph's state.
## Using with `invoke` and `ainvoke`
When you use `stream` or `astream` to run the graph, you will receive an `Interrupt` event that let you know that a breakpoint has been hit.
`invoke` and `ainvoke` do not return the interrupt information. To access this information, you must use the [get_state](../reference/graphs.md#langgraph.graph.graph.CompiledGraph.get_state) method to retrieve the graph state after calling `invoke` or `ainvoke`.
```python
# Run the graph up to the breakpoint
result = graph.invoke(inputs, thread_config)
# Get the graph state to get interrupt information.
state = graph.get_state(thread_config)
# Print the state values
print(state.values)
# Print the pending tasks
print(state.tasks)
# Resume the graph with the user's input.
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
```pycon
{'foo': 'bar'} # State values
(
PregelTask(
id='5d8ffc92-8011-0c9b-8b59-9d3545b7e553',
name='node_foo',
path=('__pregel_pull', 'node_foo'),
error=None,
interrupts=(Interrupt(value='value_in_interrupt', resumable=True, ns=['node_foo:5d8ffc92-8011-0c9b-8b59-9d3545b7e553'], when='during'),), state=None,
result=None
),
) # Pending tasks. interrupts
```
## How does resuming from a breakpoint work?
!!! warning
Resuming from a breakpoint is **different** from traditional breakpoints or Python's `input()` function, where execution resumes from the exact point where the breakpoint was triggered or where the `input()` function was called.
A critical aspect of using breakpoints is understanding how resuming from a breakpoint works. When you resume execution after a breakpoint, the graph execution starts from the **beginning** of the **graph node** where the last breakpoint was triggered.
**All** code from the beginning of the node to the **breakpoint** will be re-executed.
```python
counter = 0
def node(state: State):
# All the code from the beginning of the node to the breakpoint will be re-executed
# when the graph resumes.
global counter
counter += 1
print(f"> Entered the node: {counter} # of times")
# Pause the graph and wait for user input.
answer = interrupt()
print("The value of counter is:", counter)
...
```
Upon **resuming** the graph, the counter will be incremented a second time, resulting in the following output:
```pycon
> Entered the node: 2 # of times
The value of counter is: 2
```
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 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. As a result, we recommend that you structure your code in a way that avoids providing both a `resume` and a state `update` value (e.g., `Command(resume=resume, update=update)`) at the same time.
3. **Subgraphs**: If you're invoking a subgraph [as a function](low_level.md#as-a-function), the **parent** graph will be re-run from the **beginning of the node** where the subgraph was invoked.
## Best practices
* Use the [`interrupt`](#the-interrupt-function) function to set breakpoints and collect user input.
* Use [`Command`](#the-command-primitive) to resume execution and control the graph state.
* Consider putting all side effects (e.g., API calls) after the `interrupt` to prevent duplication. See [How does resuming from a breakpoint work?](#how-does-resuming-from-a-breakpoint-work)
## Additional Resources 📚
- [**Conceptual Guide: Persistence**](persistence.md): Read the persistence guide for more context about persistence.
+101 -10
View File
@@ -34,6 +34,10 @@ def human_node(state: State):
# Update the state with the human's input or route the graph based on the input.
...
graph = graph_builder.compile(
checkpointer=checkpointer # Required for `interrupt` to work
)
# Run the graph and hit the breakpoint
thread_config = {"configurable": {"thread_id": "some_id"}}
graph.invoke(some_input, config=thread_config)
@@ -42,7 +46,14 @@ graph.invoke(some_input, config=thread_config)
graph.invoke(Command(resume=value_from_human), config=thread_config)
```
Please read the [Breakpoints](breakpoints.md) guide for more information on using the `interrupt` function.
## Requirements
To use `interrupt` in your graph, you need to:
1. [**Specify a checkpointer**](persistence.md#checkpoints) to save the graph state after each step.
2. **Call `interrupt()`** in the appropriate place. See the [Design Patterns](#design-patterns) section for examples.
3. **Run the graph** with a [**thread ID**](./persistence.md#threads) to pause execution at the breakpoint.
4. **Resume execution** using `invoke`/`ainvoke`/`stream`/`astream` (see [**The `Command` primitive**](#the-command-primitive)).
## Design Patterns
@@ -293,6 +304,95 @@ def human_node(state: State):
}
```
## The `Command` primitive
When using the `interrupt` function, the graph will pause at the breakpoint and wait for user input.
Graph execution can be resumed using the [Command](../reference/types.md#langgraph.types.Command) primitive which can be passed through the `invoke`, `ainvoke`, `stream` or `astream` methods.
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. The `resume` value is only used when using `interrupt` as a breakpoint.
```python
# Resume graph execution with the user's input.
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
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.
```python
# Update the graph state and resume.
# You must provide a `resume` value if using an `interrupt`.
graph.invoke(Command(update={"foo": "bar"}, resume="Let's go!!!"), thread_config)
```
By leveraging `Command`, you can resume graph execution, handle user inputs, and dynamically adjust the graph's state.
## Using with `invoke` and `ainvoke`
When you use `stream` or `astream` to run the graph, you will receive an `Interrupt` event that let you know that a breakpoint has been hit.
`invoke` and `ainvoke` do not return the interrupt information. To access this information, you must use the [get_state](../reference/graphs.md#langgraph.graph.graph.CompiledGraph.get_state) method to retrieve the graph state after calling `invoke` or `ainvoke`.
```python
# Run the graph up to the breakpoint
result = graph.invoke(inputs, thread_config)
# Get the graph state to get interrupt information.
state = graph.get_state(thread_config)
# Print the state values
print(state.values)
# Print the pending tasks
print(state.tasks)
# Resume the graph with the user's input.
graph.invoke(Command(resume={"age": "25"}), thread_config)
```
```pycon
{'foo': 'bar'} # State values
(
PregelTask(
id='5d8ffc92-8011-0c9b-8b59-9d3545b7e553',
name='node_foo',
path=('__pregel_pull', 'node_foo'),
error=None,
interrupts=(Interrupt(value='value_in_interrupt', resumable=True, ns=['node_foo:5d8ffc92-8011-0c9b-8b59-9d3545b7e553'], when='during'),), state=None,
result=None
),
) # Pending tasks. interrupts
```
## How does resuming from a breakpoint work?
!!! warning
Resuming from an `interrupt` is **different** from Python's `input()` function, where execution resumes from the exact point where the `input()` function was called.
A critical aspect of using breakpoints is understanding how resuming from a breakpoint works. When you resume execution after a breakpoint, the graph execution starts from the **beginning** of the **graph node** where the last breakpoint was triggered.
**All** code from the beginning of the node to the **breakpoint** will be re-executed.
```python
counter = 0
def node(state: State):
# All the code from the beginning of the node to the breakpoint will be re-executed
# when the graph resumes.
global counter
counter += 1
print(f"> Entered the node: {counter} # of times")
# Pause the graph and wait for user input.
answer = interrupt()
print("The value of counter is:", counter)
...
```
Upon **resuming** the graph, the counter will be incremented a second time, resulting in the following output:
```pycon
> Entered the node: 2 # of times
The value of counter is: 2
```
## Gotchyas
!!! warning
@@ -442,15 +542,6 @@ To avoid issues, refrain from dynamically changing the node's structure between
{'human_node': {'age': 'John', 'name': 'N/A'}}
```
## Best practices
* Use the [`interrupt`](breakpoints.md#the-interrupt-function) function to set breakpoints and collect user input.
* Use [`Command`](breakpoints.md#the-command-primitive) to resume execution and control the graph state.
* Consider putting all side effects (e.g., API calls) after the `interrupt` to prevent duplication.
* Understand [how resuming from a breakpoint works](breakpoints.md#how-does-resuming-from-a-breakpoint-work) to avoid common gotchas.
## Additional Resources 📚
- [**Conceptual Guide: Persistence**](persistence.md#replay): Read the persistence guide for more context on replaying.
+1 -1
View File
@@ -24,7 +24,7 @@ The conceptual guide does not cover step-by-step instructions or specific implem
- [LangGraph Glossary](low_level.md): LangGraph workflows are designed as graphs, with nodes representing different components and edges representing the flow of information between them. This guide provides an overview of the key concepts associated with LangGraph graph primitives.
- [Common Agentic Patterns](agentic_concepts.md): An agent uses an LLM to pick its own control flow to solve more complex problems! Agents are a key building block in many LLM applications. This guide explains the different types of agent architectures and how they can be used to control the flow of an application.
- [Multi-Agent Systems](multi_agent.md): Complex LLM applications can often be broken down into multiple agents, each responsible for a different part of the application. This guide explains common patterns for building multi-agent systems.
- [Breakpoints](breakpoints.md): Breakpoints allow pausing the execution of a graph at specific points. Breakpoints are crucial for human-in-the-loop workflows, allowing human review before continuing.
- [Breakpoints](breakpoints.md): Breakpoints allow pausing the execution of a graph at specific points. Breakpoints allow stepping through graph execution for debugging purposes.
- [Human-in-the-Loop](human_in_the_loop.md): Explains different ways of integrating human feedback into a LangGraph application.
- [Time Travel](time-travel.md): Time travel allows you to replay past actions in your LangGraph application to explore alternative paths and debug issues.
- [Persistence](persistence.md): LangGraph has a built-in persistence layer, implemented through checkpointers. This persistence layer helps to support powerful capabilities like human-in-the-loop, memory, time travel, and fault-tolerance.
+6 -13
View File
@@ -446,19 +446,6 @@ graph.invoke(inputs, config={"recursion_limit": 5, "configurable":{"llm": "anthr
Read [this how-to](https://langchain-ai.github.io/langgraph/how-tos/recursion-limit/) to learn more about how the recursion limit works.
## Breakpoints
Breakpoints pause graph execution at specific points, enabling [**human-in-the-loop**](./human_in_the_loop.md) workflows and debugging. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step.
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.
There are two places where you can set breakpoints:
1. **Inside** a node using the [`interrupt` function](#the-interrupt-function) (or the older [`NodeInterrupt` exception](#nodeinterrupt-exception)).
2. **Before** or **after** a node executes by setting breakpoints at **compile time** or **run time**. We call these [**static breakpoints**](#static-breakpoints).
Read more about breakpoints in the [Breakpoints conceptual guide](./breakpoints.md).
## `interrupt`
Use the [interrupt](../reference/types.md/#langgraph.types.interrupt) function to **pause** the graph at specific points to collect user input. The `interrupt` function surfaces interrupt information to the client, allowing the developer to collect user input, validate the graph state, or make decisions before resuming execution.
@@ -480,6 +467,12 @@ Resuming the graph is done by passing a [`Command`](#command) object to the grap
Read more about how the `interrupt` is used for **human-in-the-loop** workflows in the [Human-in-the-loop conceptual guide](./human_in_the_loop.md).
## Breakpoints
Breakpoints pause graph execution at specific points and enable stepping through execution step by step. Breakpoints are powered by LangGraph's [**persistence layer**](./persistence.md), which saves the state after each graph step. Breakpoints can also be used to enable [**human-in-the-loop**](./human_in_the_loop.md) workflows, though we recommend using the [`interrupt` function](#interrupt-function) for this purpose.
Read more about breakpoints in the [Breakpoints conceptual guide](./breakpoints.md).
## 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: