ToolNode与执行

bind_tools 之后模型会返回 tool_calls,但 谁去执行函数、谁把结果写回 messages 仍需一层执行逻辑。手写 for 循环容易漏并行、漏错误包装。LangGraph 提供 ToolNode 作为标准执行节点;在纯 LangChain/LCEL 场景下,你需要同等语义的执行模式——本篇讲 LangChain 侧 如何可靠跑工具,并说明与 LangGraph ToolNode 的分工。

段末注释ToolNode 在 LangGraph 中为预构建工具执行节点;LangChain 应用层可手写等价循环或委托 create_agent 内置图。


1. 一句话定位

维度 内容
角色 能力层工具执行:AIMessage.tool_calls → ToolMessage
输入 → 输出 AIMessage(含 tool_calls)→ list[ToolMessage]
典型调用入口 手写 execute_tools;或 LangGraph ToolNode(tools).invoke({"messages":[ai]})
与 LangGraph LangGraph ToolNode 是官方实现;create_agent 内部也用它;本篇侧重执行语义与 LangChain 手写模式

2. 实现逻辑

手写 LangChain 执行循环(与 ToolNode 语义对齐):

1
2
3
4
5
6
7
1. ai = model_with_tools.invoke(messages)
2. 若 not ai.tool_calls → 结束,返回 ai
3. 对每个 call:按 name 查 tools_by_name[call["name"]]
4. result = tool.invoke(call["args"]) # 捕获异常 → 错误字符串仍写入 ToolMessage
5. tool_msgs.append(ToolMessage(content=str(result), tool_call_id=call["id"]))
6. messages.extend([ai, *tool_msgs])
7. 回到步骤 1,直到无 tool_calls 或达 max_iterations

字段级变形

1
2
tool_calls=[{id:"1", name:"add", args:{a:1,b:2}}]
→ ToolMessage(content="3", tool_call_id="1")

3. 原理说明

3.1 LangGraph ToolNode 做什么

ToolNode(类,langgraph.prebuilt
功能:从 state 的最后一条 AIMessage 取出 tool_calls,并行执行,把 ToolMessage 追加回 messages

字段 / 参数 类型 默认值 最小维度
tools list 构造必填 len≥1,name 须覆盖模型可能调用的工具
handle_tool_errors bool True True 时异常变 ToolMessage 文本

ToolNode.invoke(state, config=None)(方法)
输入最小结构:{"messages": [AIMessage(tool_calls=[至少 1 条])]}
输出:{"messages": [..., ToolMessage, ...]},ToolMessage 条数 = 有效 tool_calls 条数。

1
2
from langgraph.prebuilt import ToolNode
out = ToolNode([add]).invoke({"messages": [ai]})

3.2 LangChain 侧为何还要理解

线性脚本未上图时手写循环即可。自定义 Agent 前须知 tool_call_id 与错误回填。create_agent 内置 ToolNode,调试仍看 ToolMessage 序列。

3.3 错误回填策略

参数非法:把错误文本放进 ToolMessage.content,让模型自修正。执行期异常:handle_tool_errors=True 时不把图打崩。
最小回填:每条失败的 tool_call 仍要 1 条 ToolMessage(id 对齐),不能缺 id。

1
ToolMessage(content="参数 a 必须是 int", tool_call_id=call["id"])

3.4 与 return_direct

return_direct: bool = False(Tool 字段)
功能:True 时部分 Agent 跳过再次 call model,直接把工具结果给用户。手写循环须显式分支。
最小维度:布尔;与 ToolNode 默认行为(始终产出 ToolMessage)独立。


4. 最小可运行示例

1
pip install -U langchain-openai langgraph
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage
from langgraph.prebuilt import ToolNode

@tool
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b

model = ChatOpenAI(
model="qwen3.5:9b",
api_key="ollama",
base_url="http://localhost:11434/v1",
temperature=0,
).bind_tools([add])

ai = model.invoke([HumanMessage("计算 2+3,必须用 add 工具。")])
print(ai.tool_calls)
# 预期形态:name=add,args 含 a、b

tool_node = ToolNode([add])
out = tool_node.invoke({"messages": [ai]})
tool_msg = out["messages"][-1]
print(type(tool_msg).__name__, tool_msg.content, tool_msg.tool_call_id == ai.tool_calls[0]["id"])
# 预期:ToolMessage 5 True

final = model.invoke([HumanMessage("计算 2+3,必须用 add 工具。"), ai, tool_msg])
print(final.content)
# 预期形态:含 5

纯 LangChain 手写(不依赖 ToolNode):

1
2
3
4
5
6
7
8
9
10
11
from langchain_core.messages import AIMessage, ToolMessage

def run_tool_calls(ai: AIMessage, tools):
by_name = {t.name: t for t in tools}
return [
ToolMessage(
content=str(by_name[c["name"]].invoke(c["args"])),
tool_call_id=c["id"],
)
for c in ai.tool_calls
]

重要配置参数

参数(API 名) 类型 / 默认值 功能说明 作用与影响 参考起点 配置指导
handle_tool_errors bool/str/callable,ToolNode,默认 True 工具抛错时是否改写成 ToolMessage 而不是打崩图 True 模型可看到错误并自愈;False 直接中断 True 生产建议 True
tools list[BaseTool],构造必填 ToolNode 允许执行的白名单 与 bind_tools 名不一致则 KeyError 与 bind 列表同一批 不要漏注册
max_iterations int,Agent 层 工具循环最多跑几轮 过小答不完;过大费钱、易死循环 10~25 防无限 loop
tool_call_id str,回填必填 把执行结果对上哪一次模型调用 缺失或错配则下一轮非法 与 AIMessage 一致 从 tool_calls 原样拷
messages_key str,默认 "messages" state 里消息列表的字段名 自定义 state 不改此键会读空 "messages" 与图 state 定义一致
并行执行 ToolNode 内置,默认开 一条 AIMessage 上多个 tool_call 同时跑 IO 工具加速;注意下游 rate limit 默认开 有副作用的工具慎并行

5. 易踩坑

  1. 只 bind 了工具却用未注册 name 执行:模型幻觉出 footools_by_name 无此键。
  2. 异常直接 raise 不回填:模型看不到失败原因,下一轮上下文断裂。
  3. 混淆 LangChain 与 LangGraph 职责:ToolNode 属 LangGraph;纯 LCEL 无环时手写循环即可,不必强行上图。

小结

  • 工具链核心:tool_calls → invoke → ToolMessage(tool_call_id)
  • LangGraph ToolNode 是标准执行实现;LangChain 脚本可手写同等循环。
  • 错误应 回填为 ToolMessage 文本,便于模型修正。
  • create_agent 内部已封装该循环;自定义时本篇逻辑仍适用。

参考链接

-------------本文结束感谢您的阅读-------------