
很多 AI 产品最容易让人产生一种错觉:
它好像真的会"干活"了。
它能搜索网页,能分析 Excel,能查天气,能读本地文件,甚至能帮我们改代码、跑命令、操作浏览器。刚开始看会觉得很神奇,好像大模型突然从聊天框里走了出来,真的碰到了外部世界。
但从开发者视角看,这件事没有那么玄。
大模型本身并不会真的打开浏览器,也不会真的访问数据库,更不会自己执行某个函数。
真正让 AI 从"会回答"变成"能办事"的,是 Tool Use,也就是工具调用。
LLM 被困在哪里?
先把一个误解拆掉:LLM 本身不是一个会主动操作世界的程序。
它的核心能力仍然是根据上下文预测下一个 token。它擅长理解、推理、生成文本,但它天然有几个限制:
- 不知道实时信息
- 不能直接读取本地文件
- 不能直接查询数据库
- 不能直接调用业务接口
- 不能自己执行代码
比如用户问:
text
青岛啤酒的收盘价是多少?
如果只靠模型自己的参数,它很难给出可靠答案。
因为股价会变,实时数据不会稳定地存在模型参数里。模型可以根据训练语料猜一个答案,但这种猜测没有工程可信度。
所以它需要工具。
工具调用的核心思路是:
模型负责判断"这个问题需要查数据",程序负责真正去查数据。
这就是很多 Agent 产品能工作的基础。
工具不是魔法,而是一份说明书
模型并不天然知道项目里有哪些函数。
开发者必须先把工具描述给模型看,让模型知道:
- 工具叫什么
- 工具能做什么
- 什么时候该用它
- 需要哪些参数
- 参数是什么类型
- 哪些参数是必填
在代码里,这通常会写成一段 tools 配置。
比如一个获取股票收盘价的工具:
js
const tools = [
{
type: 'function',
function: {
name: 'get_closing_price',
description: '获取指定股票的收盘价',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: '股票名称'
}
},
required: ['name']
}
}
}
];
这段代码真正有意思的地方,不是它写了一个 JSON。
而是它把一个传统软件世界里的函数,翻译成了模型能理解的语言。
模型不懂你的后端实现,不懂数据库怎么查,也不懂股票接口怎么封装。但它能读懂"获取指定股票的收盘价"这句话,也能理解参数 name 是"股票名称"。
这一步可以理解成一次"认知植入":把外部工具的能力,写进模型当前这次对话的上下文里。
第一次调用:模型只是在做决策
有了工具说明以后,用户再问:
text
青岛啤酒的收盘价是多少?
程序会把用户消息和工具配置一起发给模型:
js
async function sendMessage(messages) {
const res = await client.chat.completions.create({
model: 'deepseek-v4-pro',
messages,
tools,
tool_choice: 'auto'
});
return res;
}
注意这里的 tool_choice: 'auto'。
它的意思不是"模型自动执行工具",而是"模型可以自己判断要不要调用工具"。
如果模型判断这个问题需要查股票收盘价,它返回的可能不是普通文本,而是类似这样的结构:
js
tool_calls: [
{
type: 'function',
function: {
name: 'get_closing_price',
arguments: '{"name":"青岛啤酒"}'
}
}
]
这一步,模型做了三件事:
- 识别用户意图:用户想查股票收盘价
- 选择工具:应该用
get_closing_price - 生成参数:股票名称是"青岛啤酒"
但它还没有真的查到价格。
因为模型不会执行函数。
它只是生成了一份"我想调用这个工具"的结构化请求。
Runtime 才是真正干活的人
真正执行函数的是 runtime。
也就是 Node、Python、Java 或其他后端代码。
在这个例子里,程序拿到模型返回的 tool_calls 以后,会解析参数,然后调用本地函数:
js
function get_closing_price(name) {
if (name === '青岛啤酒') {
return '67.92';
} else if (name === '贵州茅台') {
return '1488.21';
} else {
return '未找到该股票';
}
}
执行过程大概是:
js
const toolCall = response.choices[0].message.tool_calls[0];
const args = JSON.parse(toolCall.function.arguments);
const price = get_closing_price(args.name);
这一步才是真正的"操作外部世界"。
如果换成真实项目,这个函数里面可以是:
- 查数据库
- 调第三方 API
- 读取本地文件
- 执行搜索
- 调用内部业务服务
模型只负责说"我要用哪个工具,以及参数是什么"。
runtime 负责真的去做。
工具结果还要再交回模型
工具执行完以后,结果不会直接粗暴地丢给用户。
更完整的做法,是把工具结果作为新的上下文,再塞回对话里:
js
messages.push({
role: 'tool',
content: price,
tool_call_id: toolCall.id
});
这里的 tool_call_id 很关键。
它用来告诉模型:这个工具结果,对应刚才哪一次工具调用。
如果一次请求里调用了多个工具,比如同时查天气、查日程、查库存,没有这个 id,模型就很容易分不清哪个结果属于哪个问题。
把工具结果放回 messages 以后,再调用一次模型:
js
const finalRes = await sendMessage(messages);
这时模型终于拥有了真实数据。
它不再需要猜"青岛啤酒的收盘价是多少",而是可以基于工具返回的 67.92,组织一段自然语言回答。
所以一次完整的 Tool Use,其实不是一次模型调用,而是一条链:
text
用户提问
-> 模型判断要调用工具
-> runtime 执行工具
-> 工具结果写回上下文
-> 模型生成最终回答
Tool Use 和普通 API 调用有什么区别?
看到这里,可能会有一个疑问:
这不就是 API 调用吗?
有一点像,但不完全一样。
普通 API 调用通常是程序员提前写死流程:
text
用户点按钮 -> 调某个接口 -> 展示结果
Tool Use 的特别之处在于,中间多了一层模型决策:
text
用户自然语言提问 -> 模型判断意图 -> 模型选择工具 -> 程序执行工具 -> 模型组织回答
也就是说,工具调用不是让模型"拥有了函数能力",而是让模型参与了函数调用前的判断过程。
它可以根据自然语言决定:
- 要不要调用工具
- 调哪个工具
- 参数怎么填
- 工具结果该怎么解释给用户
这就是 LLM 和传统程序结合以后真正有价值的地方。
传统程序负责稳定执行,大模型负责理解意图和组织语言。
工具描述越清楚,调用越稳定
Tool Use 很强,但它不是随便写一个函数名就能稳定工作的。
如果工具描述很模糊,模型就容易选错工具。
如果参数设计很混乱,模型就容易传错参数。
比如一个参数叫 query,但不说明它到底是城市名、股票名称、搜索关键词,模型只能猜。
工具设计也需要工程能力:
- 函数名要清楚,比如
get_weather比handle更好 - description 要具体,告诉模型什么时候该用
- 参数类型要准确,不要什么都写 string
- required 要明确,别让模型漏参数
- 返回结果要干净,方便模型继续理解
- 高风险操作要加确认,不要直接让模型执行
工具越像一份清晰的接口合同,模型调用起来就越稳定。
我真正记住的,是这条分工线
Tool Use 最重要的不是"AI 会调用函数"这句话。
真正重要的是这条分工线:
模型负责理解和决策。
runtime 负责执行和校验。
工具负责连接外部世界。
结果再回到模型,由模型继续生成用户能读懂的回答。
所以以后看到 AI 会查天气、读文件、搜网页、调接口,不用把它想得太神秘。
它不是突然有了自我意识,也不是模型真的突破了物理限制。
它背后是一套很清晰的工程流程:
把工具描述给模型,让模型选择工具,再让程序执行工具,最后把结果交回模型。
LLM 负责"想明白要做什么"。
Tool 负责"真的把事情做掉"。