LangGraphLangGraph 1.2 · Python 3.10+
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
37 small wins to finish your pathNext lesson

Stopping to ask a human

Some things should not happen without a person saying yes. A graph can stop in the middle of a node, wait as long as it takes, and then carry on from the same line.

You are going to build a refund step that stops and asks. Five short pieces, and the program grows by a few lines at a time.

First, what the run is about

A refund has an amount, and it will end with an outcome. Two keys, nothing else.

python
from typing_extensions import TypedDict

class State(TypedDict):
    amount: int
    outcome: str

The amount is what a person needs to see before they can answer. The outcome stays empty until somebody tells us.

Then the node that stops

This is the only new idea in the lesson, and it is one line.

python
from langgraph.types import interrupt

def ask_a_human(state):
    answer = interrupt(f"Refund {state['amount']} rupees?")
    return {"outcome": f"you said: {answer}"}

interrupt stops the run on that line and hands whatever you pass it back to you, so pass whatever a person needs in order to decide. The line does not finish on the first pass. It gets its value later, when somebody answers, and everything below it waits until then.

Then the graph, with somewhere to save the pause

A paused run has to be written down, or there would be nothing to come back to. That is the checkpointer from lesson 20, doing a second job.

python
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START

builder = StateGraph(State)
builder.add_node("ask_a_human", ask_a_human)
builder.add_edge(START, "ask_a_human")
graph = builder.compile(checkpointer=InMemorySaver())

Without a checkpointer there is nowhere to keep a half finished run, and LangGraph will tell you so. Pausing and remembering are the same feature.

Now run it, and watch it stop

Example
config = {"configurable": {"thread_id": "refund-1"}}
paused = graph.invoke({"amount": 500, "outcome": ""}, config)

print("it asks:", paused["__interrupt__"][0].value)

The thread id works exactly as it did in lesson 20, and it is how you find the paused run again. When a run stops instead of finishing you get an __interrupt__ key back, holding whatever was passed to interrupt.

Read that carefully, because it is not what people expect. The invoke returned. It did not raise, and it did not sit there blocking. You got a dictionary back with the question in it.

That is what lets a web server hand the question to a browser and go serve somebody else while it waits.

Finally, answer it

The run is sitting in the checkpointer, halfway through a node. To finish it you invoke again, with an answer instead of a new input.

Example
from langgraph.types import Command

print(graph.invoke(Command(resume="approved"), config)["outcome"])

You met Command in lesson 11 coming out of a node. This one goes in, and what you put in resume becomes the value of answer. The config is the same thread id as before, which is how LangGraph knows which paused run you are answering.

interrupt handed back "approved", the return line finally ran, and the outcome came out. One node, two invokes, and any amount of time in between.

One thing that will catch you

When you resume, LangGraph runs the whole node again from the top. interrupt returns your answer this time rather than stopping, but everything above it happens twice.

So if the line above your interrupt sends an email, it sends two. Put the risky work after the interrupt, which is what you wanted anyway, because the whole point was to ask first.

See it for yourself
Add a print above the interrupt line and run the example again. You will see it twice. It is worth doing once so it never surprises you later.
Try it yourself
  • Resume with "rejected" and read the outcome.
  • Put a print above the interrupt and count how many times it appears.
  • Call graph.get_state(config) while it is paused and look at next.

Slow is fine. Stopping is the only problem.