You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.15 03:23:28