LangChain Tool Calling 原理:模型是怎么决定调用哪个工具的?
一、模型不会真的调用工具
很多人第一次看 Tool Calling,会以为模型自己会查数据库、调接口、执行函数。错。模型没有直接执行你代码的权限。它能做的,是看懂你给它的工具说明,然后输出一段结构化数据。
这段结构化数据就是 tool_calls。它通常包含工具名、参数、调用 id。程序拿到它,再决定是否执行、怎么执行、是否拦截、是否需要人工确认。
二、模型为什么知道该调用哪个工具?
因为你把工具说明给了模型。这个说明不是一句随便写的注释,而是一份 Schema。它会告诉模型三个关键点:这个工具叫什么、什么时候该用、需要哪些参数。
LangChain 官方也明确说明:工具本质上是带有清晰输入输出的可调用函数,传给 Chat Model 后,模型会根据对话上下文决定是否调用工具,以及提供什么参数。
所以,工具调用的准确率,很大一部分不在模型,而在你的工具设计。工具名模糊,描述空泛,参数没有说明,模型就会乱选。
三、bind_tools 到底做了什么?
上一章我们讲了工具定义。这一章看更关键的一步:bind_tools。
bind_tools 的作用不是执行工具,而是把工具“挂”到模型调用上。也就是告诉模型:这轮对话里,你可以使用这些工具。
不同模型供应商要求的工具格式不一样。OpenAI、Anthropic、Gemini 的底层协议各有差异。LangChain 的价值,就是把 Python 函数、Pydantic 类、LangChain Tool、字典 Schema 统一转换成模型供应商能接受的工具描述。
# 你写的是 Python 函数
@tool
def get_weather(location: str) -> str:
"""Get the weather at a location."""
return f"It's sunny in {location}."
# LangChain 把工具绑定给模型
model_with_tools = model.bind_tools([get_weather])
# 注意:这一步只是“让模型知道工具存在”
# 工具不会在 bind_tools 这里执行
四、bind_tools:标准接口 + Provider 实现
源码上要分两层看。
第一层:BaseChatModel.bind_tools 定义统一接口。不是所有模型都天然支持工具调用。基础类里通常只是声明能力。
第二层:具体 Provider 实现,比如 ChatOpenAI.bind_tools。它会把工具转换成 OpenAI 能识别的 schema,再通过 bind(...) 挂到模型参数里。
第三层:模型调用时,messages 和 tools 一起发给模型,模型再决定是否返回 tool_calls。
# 源码思想压缩版,不是逐字源码
class BaseChatModel:
def bind_tools(self, tools, *, tool_choice=None, **kwargs):
raise NotImplementedError
class ChatOpenAI(BaseChatModel):
def bind_tools(self, tools, *, tool_choice=None, strict=None,
parallel_tool_calls=None, **kwargs):
formatted_tools = [
convert_to_openai_tool(tool, strict=strict)
for tool in tools
]
return self.bind(
tools=formatted_tools,
tool_choice=tool_choice,
parallel_tool_calls=parallel_tool_calls,
**kwargs
)
这段逻辑很重要:LangChain 不会神奇地让任意模型都具备工具调用能力。模型本身要支持工具调用,Provider 包也要实现 bind_tools。否则可能出现 NotImplementedError,或者模型明明收到了描述却不返回 tool_calls。
五、模型返回的 tool_calls 长什么样?
模型如果判断需要调用工具,它通常不会直接给最终答案,而是返回一个 AIMessage。这个 AIMessage 里面会带 tool_calls。
LangChain 把不同供应商的工具调用结果统一成标准结构:name、args、id、type。这样你不用为每个模型写一套解析逻辑。
# 模型返回的标准化调用意图
ai_msg.tool_calls
[
{
"name": "get_weather",
"args": {"location": "Boston"},
"id": "call_123",
"type": "tool_call"
}
]
看到这里要记住一句话:tool_calls 不是执行结果,它只是执行请求。真正执行还在下一步。
六、没有 Agent 时,工具调用需要你手动跑循环
如果你只是 model.bind_tools(...),没有使用 Agent,那么模型只会把调用意图返回给你。你需要自己根据 tool_calls 找到工具、执行工具、创建 ToolMessage、再次调用模型。
messages = [{"role": "user", "content": "Boston 天气怎么样?"}]
# 1. 模型生成工具调用意图
ai_msg = model_with_tools.invoke(messages)
messages.append(ai_msg)
# 2. 程序执行工具
for tool_call in ai_msg.tool_calls:
tool_result = get_weather.invoke(tool_call)
messages.append(tool_result)
# 3. 把工具结果交回模型,生成最终答案
final_response = model_with_tools.invoke(messages)
这里最容易出错的是 ToolMessage。ToolMessage 必须带上 tool_call_id,而且要和 AIMessage.tool_calls 里的 id 对上。否则模型不知道这条结果对应哪次调用。
七、Agent 做了什么?它替你跑这个循环
Agent 的本质,就是“模型 + 工具 + 循环控制器”。
普通 bind_tools 只负责让模型会返回 tool_calls。Agent 会进一步处理:调用模型、发现 tool_calls、执行工具、追加 ToolMessage、再次调用模型,直到模型输出最终答案,或者达到迭代上限。
Agent 的底层逻辑 |
官方文档也把 Agent 描述为模型调用工具的循环,直到任务完成;create_agent 则是一个可配置的 harness,负责把模型、工具、Prompt 和中间件组织在一起。
八、tool_choice、strict、parallel_tool_calls:控制模型怎么用工具
模型默认会自己判断是否调用工具。但在生产环境里,有时不能完全放开。比如订单查询必须查接口;高危操作不能让模型随便执行;多个工具有顺序依赖,不能并行乱跑。
这些控制参数不是所有模型都完全一致支持。OpenAI、Anthropic、Gemini、国产模型、开源模型的工具调用能力和参数语义可能不同。上生产前必须做兼容性测试。
九、源码深挖:ToolCall、ToolMessage、invalid_tool_calls
LangChain 里,ToolCall 是模型发出的调用请求。它至少要有 name、args、id。id 的意义很大:它用来把工具请求和工具结果绑定起来。
ToolMessage 是工具执行后的结果。模型第二次调用时,会读取 ToolMessage,并结合原始问题生成最终回答。
invalid_tool_calls 用来承接解析失败或格式不合法的工具调用。比如模型输出的参数不是合法 JSON,或者字段结构不符合预期,就可能被放到这里。
# ToolCall 思想压缩版
class ToolCall(TypedDict):
name: str # 要调用哪个工具
args: dict[str, Any] # 参数
id: str | None # 调用 id,用于匹配结果
# ToolMessage 的关键字段
ToolMessage(
content="工具执行结果",
tool_call_id="call_123"
)
从工程角度看,invalid_tool_calls 不是无关紧要的字段。它应该进入日志和告警。因为它往往意味着模型能力、Schema 描述、参数约束或者模型供应商解析逻辑出了问题。
十、为什么模型会选错工具?
工具选错,通常不是一个原因。更常见的是多个小问题叠加。
最常见的问题是工具太多、工具名太像、描述不清、参数字段太抽象。模型不是读你的源码,它只看你暴露出去的 schema。所以 schema 写得烂,模型选择就会烂。
另一个问题是:模型返回 tool_calls 后,程序没有做强校验,直接执行。这在 Demo 里没事,在真实业务里很危险。
十一、企业级落地:工具不能裸奔
生产环境里,Tool Calling 不能只是 LangChain 代码。它必须纳入业务治理。
工具白名单:只有允许暴露的工具,才能进入模型上下文。
参数校验:模型填的参数必须通过 Pydantic / JSON Schema / 业务规则校验。
权限控制:用户没有权限,就算模型选了工具也不能执行。
风险分级:查询类工具可以自动执行,修改类工具要人工确认。
审计日志:记录 tool name、args、result、耗时、requestId、userId。
异常兜底:工具失败后,模型不能胡编结果,要明确说明失败。
十二、Java + Python 架构里应该怎么落地?
如果你的主系统是 Java,AI 服务是 Python,我建议这样拆:
Java 主服务负责用户、权限、订单、资金、审计、幂等、风控。
Python AI 服务负责 Prompt、模型调用、工具 Schema、Agent 编排。
工具执行不要全部放在 Python 里。高价值业务工具应该回调 Java 接口,由 Java 做权限和业务校验。
Python 只拿到“可调用工具列表”和“安全调用入口”,不要绕过业务系统直接改数据库。
所有 tool_calls 都要落日志,方便复盘模型为什么选这个工具。
推荐边界 |
十四、总结
Tool Calling 让大模型从聊天机器人变成了业务系统的调度入口。它可以查数据库、调 API、跑计算、走搜索、生成工单。
但能力越大,风险越大。
真正靠谱的 Tool Calling,不是把一堆函数塞给模型。真正靠谱的是:工具少而准,Schema 清楚,权限明确,执行可控,日志可追。
大模型负责判断“要不要调用”。LangChain 负责把调用标准化。业务系统负责执行和兜底。三者边界清楚,Agent 才能上线。
内容来源:LangChain Tool Calling 原理:模型是怎么决定调用哪个工具的?:功能变化与行业影响解析_热闻岛
