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.
from typing_extensions import TypedDict
class State(TypedDict):
amount: int
outcome: strThe 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.
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.
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
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.
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.
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.- Resume with
"rejected"and read the outcome. - Put a
printabove the interrupt and count how many times it appears. - Call
graph.get_state(config)while it is paused and look atnext.
Slow is fine. Stopping is the only problem.