LangGraph结合LangChain使用interrupt与new Command({resume:...})时出现undefined message错误的问题咨询
LangGraph结合LangChain使用interrupt与new Command({resume:...})时出现undefined message错误的问题咨询
当我调用包含interrupt的节点的工作流图时,它会进入无效且无法恢复的状态,抛出以下错误:
return chatGeneration.message; ^ TypeError: Cannot read properties of undefined (reading 'message') at ChatOpenAI.invoke (file:///Users/user/code/lgdemo//node_modules/@langchain/core/dist/language_models/chat_models.js:64:31)
第一次触发interrupt时一切正常,但第二次遇到后就会出现这个问题。
我的核心逻辑在approveNode里,这个节点包含了interrupt逻辑:
- 如果用户回复
y,流程会跳转到agentNode请求的toolsNode - 如果用户回复其他内容(比如
n),流程会直接进入END
问题在于,一旦流程走到END,后续再调用graph.invoke就会触发上面的错误。
我还尝试过修改approveNode的逻辑:
- 用户回复
y时,还是跳转到toolsNode(和之前一致) - 用户回复其他内容时,流程回到
agentNode(这里做了修改)
同时也更新了主图的流程配置,但结果还是一样的错误,只是这次会在第一次interrupt后就触发。
我的疑问
- 我定义的
workflow结构是否合法?有没有更合理的组织方式? - 这个错误的根本原因是什么?虽然能复现,但我完全摸不着头绪
- 除此之外,我该怎么实现一个简单的工具调用批准/拒绝功能?
主图代码
const workflow = new StateGraph(MessagesAnnotation) .addNode('agent', agentNode) .addNode('tools', toolsNode) .addNode('approve', approveNode, { ends: ['tools', END], }) .addEdge(START, 'agent') .addEdge('tools', 'agent') .addConditionalEdges('agent', agentRouter, ['approve', END]); const checkpointer = new MemorySaver(); const graph = workflow.compile({ checkpointer, }); const graphConfig = { configurable: { thread_id: '0x0004' }, };
工具、节点和路由代码
const cmdFooTool = tool(async function(inputs) { console.log('===TOOL CMD_FOO==='); return inputs.name; }, { name: 'CMD_FOO', description: 'Invoke when you want to do a Foo.', schema: z.object({ name: z.string('Any string'), }), }); const cmdBarTool = tool(async function(inputs) { console.log('===TOOL QRY_BAR==='); return inputs.name; }, { name: 'QRY_BAR', description: 'Invoke when you want to query a Bar.', schema: z.object({ name: z.string('Any string'), }), }); const tools = [cmdFooTool, cmdBarTool]; const llmWithTools = llm.bindTools(tools); const toolsNode = new ToolNode(tools); async function agentNode(state) { console.log('===AGENT NODE==='); const response = await llmWithTools.invoke(state.messages); console.log('=RESPONSE=', '\ncontent:', response.content, '\ntool_calls:', response.tool_calls.map((toolCall) => (toolCall.name))); return { messages: [response] }; } async function approveNode (state) { console.log('===APPROVE NODE==='); const lastMsg = state.messages.at(-1); const toolCall = lastMsg.tool_calls.at(-1); const interruptMessage = `Please review the following tool invocation: ${toolCall.name} with inputs ${JSON.stringify(toolCall.args, undefined, 2)} Do you approve (y/N)`; console.log('=INTERRUPT PRE='); const interruptResponse = interrupt(interruptMessage); console.log('=INTERRUPT POST='); const isApproved = (interruptResponse.trim().charAt(0).toLowerCase() === 'y'); const goto = (isApproved) ? 'tools' : END; console.log('=RESULT=\n', { isApproved, goto }); return new Command({ goto }); } function hasToolCalls(message) { return message?.tool_calls?.length > 0; } async function agentRouter (state) { const lastMsg = state.messages.at(-1); if (hasToolCalls(lastMsg)) { return 'approve'; } return END; }
模拟运行代码
let state; let agentResult; let inputText; let invokeWith; // step 1: prompt inputText = 'Pls perform a Foo with name "ASDF".'; console.log('===HUMAN PROMPT===\n', inputText); invokeWith = { messages: [new HumanMessage(inputText)] }; agentResult = await graph.invoke(invokeWith, graphConfig); state = await graph.getState(graphConfig); console.log('===STATE NEXT===\n', state.next); console.log('=LAST MSG=\n', agentResult.messages.at(-1).content); console.log('=LAST TOOL CALLS=\n', agentResult.messages.at(-1).tool_calls); // step 2: interrupted in the 'approve' node, human in the loop authorises inputText = 'yes' console.log('===HUMAN INTERRUPT RESPONSE===\n', inputText); invokeWith = new Command({ resume: inputText }); agentResult = await graph.invoke(invokeWith, graphConfig); state = await graph.getState(graphConfig); console.log('===STATE NEXT===\n', state.next); console.log('=LAST MSG=\n', agentResult.messages.at(-1).content); console.log('=LAST TOOL CALLS=\n', agentResult.messages.at(-1).tool_calls); // step 3: prompt inputText = 'Pls perform a Foo with name "ZXCV".'; console.log('===HUMAN PROMPT===\n', inputText); invokeWith = { messages: [new HumanMessage(inputText)] }; agentResult = await graph.invoke(invokeWith, graphConfig); state = await graph.getState(graphConfig); console.log('===STATE NEXT===\n', state.next); console.log('=LAST MSG=\n', agentResult.messages.at(-1).content); console.log('=LAST TOOL CALLS=\n', agentResult.messages.at(-1).tool_calls); // step 4: interrupted in the 'approve' node, human in the loop does not authorise inputText = 'no'; console.log('===HUMAN INTERRUPT RESPONSE===\n', inputText); invokeWith = new Command({ resume: inputText }); agentResult = await graph.invoke(invokeWith, graphConfig); state = await graph.getState(graphConfig); console.log('===STATE NEXT===\n', state.next); console.log('=LAST MSG=\n', agentResult.messages.at(-1).content); console.log('=LAST TOOL CALLS=\n', agentResult.messages.at(-1).tool_calls); // step 5: prompt inputText = 'Pls perform a Foo with name "GHJK".'; console.log('===HUMAN PROMPT===\n', inputText); invokeWith = { messages: [new HumanMessage(inputText)] }; agentResult = await graph.invoke(invokeWith, graphConfig); state = await graph.getState(graphConfig); console.log('===STATE NEXT===\n', state.next); console.log('=LAST MSG=\n', agentResult.messages.at(-1).content); console.log('=LAST TOOL CALLS=\n', agentResult.messages.at(-1).tool_calls);
完整运行输出
===HUMAN PROMPT=== Pls perform a Foo with name "ASDF". ===AGENT NODE=== (node:58990) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. (Use `node --trace-deprecation ...` to show where the warning was created) =RESPONSE= content: tool_calls: [ 'CMD_FOO' ] ===APPROVE NODE=== =INTERRUPT PRE= ===STATE NEXT=== [ 'approve' ] =LAST MSG= =LAST TOOL CALLS= [ { name: 'CMD_FOO', args: { name: 'ASDF' }, type: 'tool_call', id: 'call_u7CIyWdTesFATZ5bGG2uaVUZ' } ] ===HUMAN INTERRUPT RESPONSE=== yes ===APPROVE NODE=== =INTERRUPT PRE= =INTERRUPT POST= =RESULT= { isApproved: true, goto: 'tools' } ===TOOL CMD_FOO=== ===AGENT NODE=== =RESPONSE= content: The Foo operation has been performed with the name "ASDF". tool_calls: [] ===STATE NEXT=== [] =LAST MSG= The Foo operation has been performed with the name "ASDF". =LAST TOOL CALLS= [] ===HUMAN PROMPT=== Pls perform a Foo with name "ZXCV". ===AGENT NODE=== =RESPONSE= content: tool_calls: [ 'CMD_FOO' ] ===APPROVE NODE=== =INTERRUPT PRE= ===STATE NEXT=== [ 'approve' ] =LAST MSG= =LAST TOOL CALLS= [ { name: 'CMD_FOO', args: { name: 'ZXCV' }, type: 'tool_call', id: 'call_kKF91c8G6enWwlrLFON8TYLJ' } ] ===HUMAN INTERRUPT RESPONSE=== no ===APPROVE NODE=== =INTERRUPT PRE= =INTERRUPT POST= =RESULT= { isApproved: false, goto: '__end__' } ===STATE NEXT=== [] =LAST MSG= =LAST TOOL CALLS= [ { name: 'CMD_FOO', args: { name: 'ZXCV' }, type: 'tool_call', id: 'call_kKF91c8G6enWwlrLFON8TYLJ' } ] ===HUMAN PROMPT=== Pls perform a Foo with name "GHJK". ===AGENT NODE=== file:///Users/user/code/lgdemo/node_modules/@langchain/core/dist/language_models/chat_models.js:64 return chatGeneration.message; ^ TypeError: Cannot read properties of undefined (reading 'message') at ChatOpenAI.invoke (file:///Users/user/code/lgdemo//node_modules/@langchain/core/dist/language_models/chat_models.js:64:31) at process.processTicksAndRejections (node:internal/process/task_queues:105:5) at async RunnableCallable.agentNode [as func] (fi
备注:内容来源于stack exchange,提问作者bguiz
相关产品推荐
相关产品推荐

