import { createAgent, humanInTheLoopMiddleware } from "langchain";import { MemorySaver } from "@langchain/langgraph";const agent = createAgent({ model: "gpt-4.1", tools: [writeFileTool, executeSQLTool, readDataTool], middleware: [ humanInTheLoopMiddleware({ interruptOn: { write_file: true, // All decisions (approve, edit, reject) allowed execute_sql: { allowedDecisions: ["approve", "reject"], // No editing allowed description: "🚨 SQL execution requires DBA approval", }, // Safe operation, no approval needed read_data: false, }, // Prefix for interrupt messages - combined with tool name and args to form the full message // e.g., "Tool execution pending approval: execute_sql with query='DELETE FROM...'" // Individual tools can override this by specifying a "description" in their interrupt config descriptionPrefix: "Tool execution pending approval", }), ], // Human-in-the-loop requires checkpointing to handle interrupts. // In production, use a persistent checkpointer like AsyncPostgresSaver. checkpointer: new MemorySaver(),});
import { HumanMessage } from "@langchain/core/messages";import { Command } from "@langchain/langgraph";// You must provide a thread ID to associate the execution with a conversation thread,// so the conversation can be paused and resumed (as is needed for human review).const config = { configurable: { thread_id: "some_id" } };// Run the graph until the interrupt is hit.const result = await agent.invoke( { messages: [new HumanMessage("Delete old records from the database")], }, config );// The interrupt contains the full HITL request with action_requests and review_configsconsole.log(result.__interrupt__);// > [// > Interrupt(// > value: {// > action_requests: [// > {// > name: 'execute_sql',// > arguments: { query: 'DELETE FROM records WHERE created_at < NOW() - INTERVAL '30 days';' },// > description: 'Tool execution pending approval\n\nTool: execute_sql\nArgs: {...}'// > }// > ],// > review_configs: [// > {// > action_name: 'execute_sql',// > allowed_decisions: ['approve', 'reject']// > }// > ]// > }// > )// > ]// Resume with approval decisionawait agent.invoke( new Command({ resume: { decisions: [{ type: "approve" }] }, // or "reject" }), config // Same thread ID to resume the paused conversation);
await agent.invoke( new Command({ // Decisions are provided as a list, one per action under review. // The order of decisions must match the order of actions // listed in the `__interrupt__` request. resume: { decisions: [ { type: "approve", } ] } }), config // Same thread ID to resume the paused conversation);
使用 edit 在执行前修改工具调用。
提供带有新工具名称和参数的已编辑操作。
Copy
await agent.invoke( new Command({ // Decisions are provided as a list, one per action under review. // The order of decisions must match the order of actions // listed in the `__interrupt__` request. resume: { decisions: [ { type: "edit", // Edited action with tool name and args editedAction: { // Tool name to call. // Will usually be the same as the original action. name: "new_tool_name", // Arguments to pass to the tool. args: { key1: "new_value", key2: "original_value" }, } } ] } }), config // Same thread ID to resume the paused conversation);
await agent.invoke( new Command({ // Decisions are provided as a list, one per action under review. // The order of decisions must match the order of actions // listed in the `__interrupt__` request. resume: { decisions: [ { type: "reject", // An explanation about why the action was rejected message: "No, this is wrong because ..., instead do this ...", } ] } }), config // Same thread ID to resume the paused conversation);