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

Approve, reject, or edit

The last lesson paused and wrote down the answer. In real use the answer should change what happens next, and three shapes cover almost everything.

One: approve or reject

The answer picks the route. Return a Command, from lesson 11, and one node both asks and decides.

python
from typing_extensions import TypedDict

class State(TypedDict):
    amount: int
    outcome: str
python
from typing import Literal
from langgraph.types import interrupt, Command

def approve(state) -> Command[Literal["refund", "decline"]]:
    said = interrupt({"question": "Refund?", "amount": state["amount"]})
    return Command(goto="refund" if said == "yes" else "decline")

Passing a dictionary rather than a string means the person deciding sees the amount as well as the question, which is what they actually need. The answer then chooses the destination, so approving and declining are two ordinary nodes.

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

builder = StateGraph(State)
builder.add_node("approve", approve)
builder.add_node("refund", lambda s: {"outcome": f"refunded {s['amount']}"})
builder.add_node("decline", lambda s: {"outcome": "declined"})
builder.add_edge(START, "approve")
graph = builder.compile(checkpointer=InMemorySaver())
Example
for thread, said in [("a", "yes"), ("b", "no")]:
    config = {"configurable": {"thread_id": thread}}
    graph.invoke({"amount": 500, "outcome": ""}, config)
    print(said, "->", graph.invoke(Command(resume=said), config)["outcome"])

Two threads, two answers, two different endings, from one graph. Each thread paused on its own and was answered on its own.

Two: edit what it wrote

Instead of yes or no, hand back a corrected version and the node uses what it is given.

python
from typing_extensions import TypedDict
from langgraph.types import interrupt

class State(TypedDict):
    reply: str

def check_reply(state):
    edited = interrupt({"draft": state["reply"]})
    return {"reply": edited}

The draft goes out so a person can see what the machine wrote, and whatever comes back replaces it. If they changed nothing, nothing changes.

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

builder = StateGraph(State)
builder.add_node("check_reply", check_reply)
builder.add_edge(START, "check_reply")
graph = builder.compile(checkpointer=InMemorySaver())
Example
from langgraph.types import Command

config = {"configurable": {"thread_id": "edit-1"}}
paused = graph.invoke({"reply": "we will refund you eventually"}, config)
print("shown to a human:", paused["__interrupt__"][0].value)

print("after editing:   ", graph.invoke(Command(resume="Your refund is on its way."), config)["reply"])

Three: ask inside the tool

The neatest version. The tool that does the dangerous thing asks for permission itself, so no graph anywhere has to remember to check.

Example
from langchain_core.tools import tool
from langgraph.types import interrupt

@tool
def refund(amount: int) -> str:
    """Refund an amount in rupees to the customer."""
    if interrupt(f"Approve a refund of {amount} rupees?") != "yes":
        return "The refund was not approved."
    return f"Refunded {amount} rupees."

print(refund.name, "->", refund.description)

That is only the tool, so there is nothing to pause yet. Put it in an agent and every refund it ever attempts stops for a human, without one extra line in the graph. Lesson 31 does exactly that.

ShapeWhat comes backGood for
Approve or reject"yes" or "no"Money, deletions, anything one way
EditA corrected valueReplies, drafts, anything with wording
Ask inside the toolEither of the aboveWhen the rule belongs to the action, not the graph
A rule of thumb
Pausing is cheap and being wrong is not. Start by asking on anything that spends money or cannot be undone, and take the pause away later if it turns out to be noise.
Try it yourself
  • Add a third route for "ask my manager".
  • In the editing example, resume with the draft unchanged and confirm nothing breaks.
  • Give the refund tool a limit, so only refunds over 1000 stop to ask.

This is what real progress feels like.