以前的大模型是没法直接告诉你今天的天气的,但随着时间的发展,LLM也学会了如何调用 工具(Tool),但这个工具调用背后真正的技术逻辑,不只是API
LLM 到底是什么?
技术上没那么玄乎。
LLM 做的事情只有一件:根据上文,猜下一个词。
输入 1+1=,它猜后面是 2。输入 今天天气真,它猜下一个词是 好。
这就是 Next Token Prediction------一次猜一个词,拼上去,再猜下一个,直到对话结束。
问题来了:你问它"青岛啤酒今天的收盘价",它怎么猜?
猜不了。训练数据截止于去年,股价是实时变动的。它没有任何渠道获取当下的信息。本质上,它是一堆跑在 GPU 上的矩阵运算,没有网络连接,没有文件系统,没有数据库驱动。
那为什么现在的 AI 都能查天气、查股价、读文件?
因为有人替它查好了。
给 LLM 一张"工具清单"
LLM 不认识 API endpoint,也听不懂什么叫"调接口"。
但它能读懂结构化的文字描述。
所以第一步很简单:把你的函数封装成一个 JSON 格式的工具声明,描述清楚它叫什么、能干什么、需要什么参数。然后通过 API 的 tools 参数传给模型。
json
js
{
"type": "function",
"function": {
"name": "get_closing_price",
"description": "获取指定股票在当日的收盘价",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "股票名称,如'青岛啤酒'"
}
},
"required": ["name"]
}
}
}
这一步在工程上叫作工具注册 ------把一段函数签名"翻译"成 LLM 能理解的语言,告诉它:"有个叫 get_closing_price 的工具,能查股价,你只需要提供股票名字就行。"
听起来复杂?其实就是把一个软件函数降维成一段文本,让 LLM 能"读懂"。唯一要注意的是 description 要写清楚------你说"获取信息",它可能查天气时也来调这个;你写"获取指定股票的收盘价",意图就锁得精准。因为 LLM 的决策本质上是概率性的:输入越模糊,输出就越随机。
LLM 不执行,它只"说"
用户问:"青岛啤酒的收盘价是多少?"
LLM 开始做一连串判断:
- 训练数据里没有实时股价 → 不能直接回答。
tools参数里有get_closing_price→ 这个工具能解决问题。- 用户问的是"青岛啤酒" → 参数填
name: "青岛啤酒"。 - 输出
tool_calls,不输出文字。
于是 LLM 返回了这样一个结构:
json
js
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_closing_price",
"arguments": "{"name":"青岛啤酒"}"
}
}
注意:content 字段是空的。LLM 没有直接回答用户,而是输出了一段"指令",告诉外部系统:去,调这个函数,传这个参数。
但它自己不动手------也动不了手。
打个比方:LLM 像一个被反锁在房间里的人。门缝下面可以塞纸条。它把"帮我查青岛啤酒的股价"写在纸条上塞出去,然后等着。外面的人查完了,把答案从门缝塞回来,它再根据答案组织语言回复用户。
这张纸条,就是 tool_calls。
干活的其实是 Runtime
纸条塞出来了,总得有人去执行。
这个人就是 Runtime------也就是开发者写的代码。它的工作流程是这样的:
javascript
js
const response = await sendMessage(messages, tools); // 1. 发起请求,传入工具列表
const message = response.choices[0].message;
if (message.tool_calls) { // 2. 检测 LLM 是否请求调工具
const toolCall = message.tool_calls[0];
if (toolCall.function.name === 'get_closing_price') {
const args = JSON.parse(toolCall.function.arguments); // 3. 解析参数
const price = get_closing_price(args.name); // 4. 执行真实函数 → "67.92"
messages.push(message); // 5. 把 LLM 的工具调用记录追加到上下文
messages.push({
role: 'tool',
content: price,
tool_call_id: toolCall.id
});
const finalRes = await sendMessage(messages); // 6. 再次调用 LLM,带上工具执行结果
// → "青岛啤酒的收盘价是 67.92 元"
}
}
第 4 步那个 get_closing_price 函数,跟 AI 一毛钱关系都没有------它就是一段普通的 JavaScript:
javascript
js
function get_closing_price(name) {
if (name === '青岛啤酒') return '67.92';
if (name === '贵州茅台') return '1234.11';
return '未找到股票';
}
上面是 Demo 里的 mock 数据。真实场景中,这个函数会去查数据库、调第三方 API、读取文件------总之就是传统软件开发那一套。
新旧范式就在这里交汇。LLM 负责"说要调什么",传统代码负责"真的去调"。各司其职,然后通过 role: "tool" 那条消息重新接上头。
整个流程,一张图
text
js
用户:"青岛啤酒收盘价?"
↓
第 1 次调 LLM(附带 tools 参数)→ LLM 不回答,输出 tool_calls
↓
Runtime 检测到 tool_calls → 解析参数 → 执行 get_closing_price("青岛啤酒") → "67.92"
↓
Runtime 把 "67.92" 以 role: "tool" 拼回消息历史
↓
第 2 次调 LLM(上下文里多了工具结果)→ "青岛啤酒的收盘价是 67.92 元"
用户视角:问了一个问题,得到了正确答案。丝滑。
开发者视角:调了两次 LLM,中间 Runtime 插了一脚,跑了一个真实函数。一点也不丝滑。
但产品层把这中间的往返完全封装了。用户看到的只是一个"智能助手"------这就是 Tool Use 在产品设计上的精妙之处。
这东西为什么重要
没有 Tool Use 的 LLM 是"死"的。
它能跟你聊哲学、写诗、解释量子力学------因为这些训练数据里都有。但你不能问它"今天我该不该带伞",它没有实时天气。你不能让它"帮我把这封邮件发了",它碰不到邮件服务器。
Tool Use 把它从"死的"变成了"活的"。
查天气、查股价、搜网页、发邮件、读 Excel、操作电脑------你在网上看到的那些 AI Agent 的炫酷 Demo,底层逻辑都是同一套三板斧:注册工具 → LLM 决策调哪个 → Runtime 去执行。
豆包的网页搜索是这么做的,Claude 的 Excel 分析是这么做的,那些让 AI 自动操作电脑的"贾维斯"式 Demo,也是在这一层又一层的 Tool Use 上叠起来的。
本质上是一层精心设计的"假象"
写这篇文章,不是因为 Tool Use 技术本身有多难------拆开看,每一步都简单直白。
写它,是因为这个"假象"设计得太好了。
用户觉得 LLM 无所不能。但 LLM 从头到尾只做了一件事------猜下一个词。它凭的是一套精巧的外围机制:工具注册告诉它能用什么,意图识别让它知道什么时候该用,Runtime 替它把干不了的事干了。
公式只有一个:
text
ini
LLM + Tools = Agent
LLM 是大脑------只负责推理和语言。Tool 是手脚------只负责执行。Runtime 是中间的神经系统------把大脑的指令传到手脚,再把手脚的结果传回大脑。
下次你跟 AI 说"帮我查下今天的天气"时,可以想想------说话的这会儿,它刚刚往门缝下面塞了一张纸条。