import { interrupt } from "@langchain/langgraph";async function approvalNode(state: State) { // Pause and ask for approval const approved = interrupt("Do you approve this action?"); // Command({ resume: ... }) provides the value returned into this variable return { approved };}
import { Command } from "@langchain/langgraph";// Initial run - hits the interrupt and pauses// thread_id is the durable pointer back to the saved checkpointconst config = { configurable: { thread_id: "thread-1" } };const result = await graph.invoke({ input: "data" }, config);// Check what was interrupted// __interrupt__ mirrors every payload you passed to interrupt()console.log(result.__interrupt__);// [{ value: 'Do you approve this action?', ... }]// Resume with the human's response// Command({ resume }) returns that value from interrupt() in the nodeawait graph.invoke(new Command({ resume: true }), config);
关于恢复的关键点:
恢复时必须使用与中断发生时相同的 线程 ID
传递给 new Command({ resume: ... }) 的值成为 interrupt 调用的返回值
import { interrupt } from "@langchain/langgraph";const reviewNode: typeof State.Node = (state) => { // Pause and show the current content for review (surfaces in result.__interrupt__) const editedContent = interrupt({ instruction: "Review and edit this content", content: state.generatedText }); // Update the state with the edited version return { generatedText: editedContent };}
恢复时,提供编辑后的内容:
await graph.invoke( new Command({ resume: "The edited and improved text" }), // Value becomes the return from interrupt() config);
完整示例
import { Command, MemorySaver, START, END, StateGraph, StateSchema, interrupt,} from "@langchain/langgraph";import * as z from "zod";const State = new StateSchema({ generatedText: z.string(),});const builder = new StateGraph(State) .addNode("review", async (state) => { // Ask a reviewer to edit the generated content const updated = interrupt({ instruction: "Review and edit this content", content: state.generatedText }); return { generatedText: updated }; }) .addEdge(START, "review") .addEdge("review", END);const checkpointer = new MemorySaver();const graph = builder.compile({ checkpointer });const config = { configurable: { thread_id: "review-42" } };const initial = await graph.invoke({ generatedText: "Initial draft" }, config);console.log(initial.__interrupt__);// [{ value: { instruction: ..., content: ... } }]// Resume with the edited text from the reviewerconst finalState = await graph.invoke( new Command({ resume: "Improved draft after review" }), config,);console.log(finalState.generatedText); // -> "Improved draft after review"
import { interrupt } from "@langchain/langgraph";const getAgeNode: typeof State.Node = (state) => { let prompt = "What is your age?"; while (true) { const answer = interrupt(prompt); // payload surfaces in result.__interrupt__ // Validate the input if (typeof answer === "number" && answer > 0) { // Valid input - continue return { age: answer }; } else { // Invalid input - ask again with a more specific prompt prompt = `'${answer}' is not a valid age. Please enter a positive number.`; } }}
每次您使用无效输入恢复图时,它将再次使用更清晰的消息询问。一旦提供有效输入,节点完成,图继续。
完整示例
import { Command, MemorySaver, START, END, StateGraph, StateSchema, interrupt,} from "@langchain/langgraph";import * as z from "zod";const State = new StateSchema({ age: z.number().nullable(),});const builder = new StateGraph(State) .addNode("collectAge", (state) => { let prompt = "What is your age?"; while (true) { const answer = interrupt(prompt); // payload surfaces in result.__interrupt__ if (typeof answer === "number" && answer > 0) { return { age: answer }; } prompt = `'${answer}' is not a valid age. Please enter a positive number.`; } }) .addEdge(START, "collectAge") .addEdge("collectAge", END);const checkpointer = new MemorySaver();const graph = builder.compile({ checkpointer });const config = { configurable: { thread_id: "form-1" } };const first = await graph.invoke({ age: null }, config);console.log(first.__interrupt__); // -> [{ value: "What is your age?", ... }]// Provide invalid data; the node re-promptsconst retry = await graph.invoke(new Command({ resume: "thirty" }), config);console.log(retry.__interrupt__); // -> [{ value: "'thirty' is not a valid age...", ... }]// Provide valid data; loop exits and state updatesconst final = await graph.invoke(new Command({ resume: 30 }), config);console.log(final.age); // -> 30
async function nodeA(state: State) { // ❌ Bad: wrapping interrupt in bare try/catch will catch the interrupt exception try { const name = interrupt("What's your name?"); } catch (err) { console.error(error); } return state;}
async function nodeA(state: State) { // ✅ Good: interrupt calls happen in the same order every time const name = interrupt("What's your name?"); const age = interrupt("What's your age?"); const city = interrupt("What's your city?"); return { name, age, city };}
const nodeA: GraphNode<typeof State> = async (state) => { // ❌ Bad: conditionally skipping interrupts changes the order const name = interrupt("What's your name?"); // On first run, this might skip the interrupt // On resume, it might not skip it - causing index mismatch if (state.needsAge) { const age = interrupt("What's your age?"); } const city = interrupt("What's your city?"); return { name, city };}
const nodeA: GraphNode<typeof State> = async (state) => { // ✅ Good: using upsert operation which is idempotent # Running this multiple times will have the same result await db.upsertUser({ userId: state.userId, status: "pending_approval" }); const approved = interrupt("Approve this change?"); return { approved };}
const nodeA: GraphNode<typeof State> = async (state) => { # ❌ Bad: creating a new record before interrupt # This will create duplicate records on each resume const auditId = await db.createAuditLog({ userId: state.userId, action: "pending_approval", timestamp: new Date() }); const approved = interrupt("Approve this change?"); return { approved, auditId };}
async function nodeInParentGraph(state: State) { someCode(); // <-- This will re-execute when resumed // Invoke a subgraph as a function. // The subgraph contains an `interrupt` call. const subgraphResult = await subgraph.invoke(someInput); // ...}async function nodeInSubgraph(state: State) { someOtherCode(); // <-- This will also re-execute when resumed const result = interrupt("What's your name?"); // ...}