interrupt() din langgraph.types oprește graful în interiorul unui nod, salvează starea prin checkpointer și așteaptă oricât timp. Valoarea dată (orice valoare serializabilă JSON) ajunge la apelant: cu invoke() în result["__interrupt__"], cu stream_events(..., version="v3") în stream.interrupts. Execuția se reia cu graph.invoke(Command(resume=valoare), config) pe același thread_id, iar valoare devine rezultatul apelului interrupt().
Sunt necesare un checkpointer (persistent în producție) și un thread_id. La reluare, nodul se execută din nou de la început, deci codul de dinaintea lui interrupt() rulează a doua oară; efectele secundare de acolo trebuie să fie idempotente. interrupt() funcționează aruncând o excepție specială, de aceea nu se pune într-un try/except generic. Din langgraph 1.2.12, parametrul response_schema validează răspunsul omului.
interrupt_before și interrupt_after (la compile() sau la invoke()) sunt breakpoint-uri statice, reluate cu invoke(None, config). Documentația le recomandă pentru depanare, nu pentru human-in-the-loop. La agenții creați cu create_agent, aprobarea uneltelor se face cu HumanInTheLoopMiddleware, cu deciziile approve, edit, reject și respond.
Concepte cheie și caracteristici
| Concept / Parametru | Descriere practică |
|---|---|
interrupt(valoare) |
Oprește nodul și trimite valoare apelantului. |
Command(resume=...) |
Reia execuția; valoarea devine rezultatul lui interrupt(). Este singura formă de Command destinată ca intrare pentru invoke. |
result["__interrupt__"] |
Unde apar cererile de intervenție la invoke(). |
response_schema |
Model Pydantic, TypedDict sau dataclass care validează răspunsul (din langgraph 1.2.12). |
interrupt_before / interrupt_after |
Breakpoint-uri statice înainte sau după noduri; pentru depanare. |
invoke(None, config) |
Reia după un breakpoint static. |
get_state(config).next |
Nodurile care urmează să ruleze. |
Exemple practice și scenarii reale
Exemplul 1: Aprobarea unei plăți
from typing_extensions import TypedDict
from langgraph.checkpoint.memory import InMemorySaver
from langgraph.graph import StateGraph, START, END
from langgraph.types import interrupt, Command
class State(TypedDict):
suma: int
aprobat: bool
def cere_aprobare(state: State):
raspuns = interrupt({"intrebare": "Aprobi plata?", "suma": state["suma"]})
return {"aprobat": raspuns}
def executa_plata(state: State):
if state["aprobat"]:
... # apelul real către sistemul de plăți
return {}
builder = StateGraph(State)
builder.add_node("cere_aprobare", cere_aprobare)
builder.add_node("executa_plata", executa_plata)
builder.add_edge(START, "cere_aprobare")
builder.add_edge("cere_aprobare", "executa_plata")
builder.add_edge("executa_plata", END)
graph = builder.compile(checkpointer=InMemorySaver())
config = {"configurable": {"thread_id": "plata-1"}}
rez = graph.invoke({"suma": 500, "aprobat": False}, config)
print(rez["__interrupt__"])
graph.invoke(Command(resume=True), config)
Exemplul 2: Breakpoint static pentru depanare
graph = builder.compile(checkpointer=InMemorySaver(), interrupt_before=["executa_plata"])
graph.invoke({"suma": 500, "aprobat": True}, config)
print(graph.get_state(config).next)
graph.invoke(None, config)
Greșeli frecvente și capcane
- Să folosești
interrupt_beforepentru aprobări în producție. Documentația spune că breakpoint-urile statice nu sunt recomandate pentru human-in-the-loop; foloseșteinterrupt(). - Să trimiți
Command(update=...)ca intrare pentru a continua o conversație. DoarCommand(resume=...)este destinat ca intrare; pentru un mesaj nou trimiți un dicționar obișnuit. - Efecte secundare înainte de
interrupt()(scriere în bază, e-mail). La reluare nodul rulează din nou de la început și le repetă.