UNPKG

deepagents

Version:

Deep Agents - a library for building controllable AI agents with LangGraph

104 lines (103 loc) 3.98 kB
import { interrupt } from "@langchain/langgraph"; import { isAIMessage, AIMessage, ToolMessage } from "@langchain/core/messages"; export function createInterruptHook(toolConfigs, messagePrefix = "Tool execution requires approval") { /** * Create a post model hook that handles interrupts using native LangGraph schemas. * * Args: * toolConfigs: Record mapping tool names to HumanInterruptConfig objects * messagePrefix: Optional message prefix for interrupt descriptions */ Object.entries(toolConfigs).forEach(([tool, interruptConfig]) => { if (interruptConfig && typeof interruptConfig === "object" && interruptConfig.allow_ignore) { throw new Error(`For ${tool} we get allow_ignore = true - we currently don't support ignore.`); } }); return async function interruptHook(state) { const messages = state.messages || []; if (!messages.length) { return; } const lastMessage = messages[messages.length - 1]; if (!isAIMessage(lastMessage) || !lastMessage.tool_calls || !lastMessage.tool_calls.length) { return; } const interruptToolCalls = []; const autoApprovedToolCalls = []; for (const toolCall of lastMessage.tool_calls) { const toolName = toolCall.name; if (toolName in toolConfigs) { interruptToolCalls.push(toolCall); } else { autoApprovedToolCalls.push(toolCall); } } if (!interruptToolCalls.length) { return; } const approvedToolCalls = [...autoApprovedToolCalls]; if (interruptToolCalls.length > 1) { throw new Error("Right now, interrupt hook only works when one tool requires interrupts"); } const toolCall = interruptToolCalls[0]; const toolName = toolCall.name; const toolArgs = toolCall.args; const description = `${messagePrefix}\n\nTool: ${toolName}\nArgs: ${JSON.stringify(toolArgs, null, 2)}`; const toolConfig = toolConfigs[toolName]; const defaultToolConfig = { allow_accept: true, allow_edit: true, allow_respond: true, allow_ignore: false, }; const request = { action_request: { action: toolName, args: toolArgs, }, config: typeof toolConfig === "object" ? toolConfig : defaultToolConfig, description: description, }; const res = await interrupt([request]); const responses = Array.isArray(res) ? res : [res]; if (responses.length !== 1) { throw new Error(`Expected a list of one response, got ${responses}`); } const response = responses[0]; if (response.type === "accept") { approvedToolCalls.push(toolCall); } else if (response.type === "edit") { const edited = response.args; const newToolCall = { name: edited.action, args: edited.args, id: toolCall.id, }; approvedToolCalls.push(newToolCall); } else if (response.type === "response") { if (!toolCall.id) { throw new Error("Tool call must have an ID for response type"); } const responseMessage = new ToolMessage({ tool_call_id: toolCall.id, content: response.args, }); return { messages: [responseMessage] }; } else { throw new Error(`Unknown response type: ${response.type}`); } const updatedLastMessage = new AIMessage({ ...lastMessage, tool_calls: approvedToolCalls, }); return { messages: [updatedLastMessage] }; }; }