从"缸中大脑"到"工具大师":一文搞懂 LLM Tool Use 的底层原理
前言
你有没有好奇过:大语言模型(LLM)明明只是一个预测下一个词的"概率机器",它是怎么做到查询数据库、调用 API、操作文件的?
答案就是 Tool Use(工具调用)。
最近在捣鼓 AI Agent 相关的项目,用 DeepSeek 的 API 实践了一把 Tool Use。从"缸中大脑"这个比喻出发,把 LLM 如何突破物理限制这件事彻底搞明白了。这篇文章就来和大家聊聊 Tool Use 的核心原理和实现。
读完你将收获:
- 🧠 理解 LLM 为什么是"缸中大脑"
- 🔧 掌握 Tool Use 的三个核心阶段
- 📝 看懂 JSON Schema 如何成为 LLM 的"工具说明书"
- 💻 能用代码实现一个完整的 Tool Use 流程
一、LLM:被困在显卡里的"缸中大脑"
"缸中大脑"(Brain in a Vat) 是一个著名的哲学思想实验:一个大脑被放在营养缸里,它所有的感知(视觉、听觉、触觉)都是由计算机模拟的。它认为自己生活在一个真实世界里,但其实一切都是虚拟的。
听起来是不是很像 LLM?
arduino
┌──────────────────────────────────┐
│ GPU 服务器 │
│ ┌────────────────────────────┐ │
│ │ 大语言模型 (LLM) │ │
│ │ │ │
│ │ 它看不到屏幕 │ │
│ │ 它摸不到键盘 │ │
│ │ 它连不上网络 │ │
│ │ │ │
│ │ 它唯一能做的: │ │
│ │ 预测下一个 Token │ │
│ └────────────────────────────┘ │
│ │
│ 本质上就是个"词语接龙"游戏 │
└──────────────────────────────────┘
一个在显卡里疯狂运行的 LLM,本质上是 Next Token Prediction(下一个词预测)------它只是在不断预测下一个最可能出现的词是什么。它无法真正"看见"屏幕、"摸到"键盘、调用 API。
但为什么我们感觉豆包能搜索网页?Claude 能分析 Excel?AI Agent 能操作电脑?
因为这是开发者精心设计的------让 LLM "觉得"它拥有这些能力,而实际执行的是我们的代码。
核心公式:LLM + Tools = Agent
二、认知植入:把工具"降维"成语言
2.1 核心思想
LLM 不懂什么是函数调用,不懂什么是 API,但它听得懂语言。
Tool Use 的第一步,就是把一个复杂的软件函数,降维 成一个 LLM 能理解的文本描述。这个过程,我把它叫做 "认知植入"。
怎么植入?答案是 JSON Schema------用结构化语言给 LLM 写一份"工具使用说明书"。
2.2 代码示例
javascript
// 定义工具 ------ 这就是 LLM 的"说明书"
const tools = [
{
"type": "function", // 工具类型:函数
"function": {
"name": "get_closing_price", // 工具名称(LLM 的决策依据)
"description": "获取指定股票的收盘价", // 工具描述(LLM 判断是否调用此工具的关键!)
"parameters": { // 参数约束(JSON Schema)
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "股票名称" // 参数说明
}
},
"required": ["name"] // 必填参数
}
}
},
{
type: 'function',
function: {
name: 'get_weather',
description: '获取指定城市的天气',
parameters: {
type: 'object',
properties: {
city: {
type: 'string',
description: '城市名称'
}
},
required: ['city']
}
}
}
];
2.3 关键知识点
为什么 description 这么重要?
LLM 是通过 description 字段来理解工具功能的。如果把 description 写得含糊不清:
javascript
// ❌ 糟糕的描述
{ "name": "get_data", "description": "获取数据" }
// ✅ 清晰的描述
{ "name": "get_closing_price", "description": "获取指定A股股票的当日收盘价,输入参数为股票中文名称" }
知识点①:LLM 本质上是一个概率模型,它会根据 description 的语义来决定是否调用以及何时调用工具。描述越具体清晰,调用越准确。
三、意图识别:LLM 的"自言自语"时刻
3.1 发生了什么?
当用户问"青岛啤酒的收盘价是多少?",LLM 内部发生了一个精妙的推理过程:
arduino
用户提问: "青岛啤酒的收盘价是多少?"
↓
┌─────────────────────┐
│ LLM 推理引擎 │
│ │
│ ① 查训练语料 │
│ → 没有实时股价 │
│ │
│ ② 扫描认知植入的工具 │
│ → 发现 get_closing_price │
│ │
│ ③ 生成调用指令 │
│ → 停止正常回复 │
│ → 输出 tool_calls │
└─────────────────────┘
↓
不是直接回答用户,而是
"自言自语"一段函数调用指令
3.2 LLM 实际返回的内容
json
{
"role": "assistant",
"content": "", // ← 注意:content 是空的!
"tool_calls": [{
"id": "call_abc123", // 调用ID(用于后续关联结果)
"type": "function",
"function": {
"name": "get_closing_price", // LLM 决定调用这个工具
"arguments": "{\"name\":\"青岛啤酒\"}" // LLM 自动提取参数!
}
}]
}
关键发现:LLM 并没有真正"执行"任何操作。 它只是根据自己的理解,生成了一段结构化的调用指令。
知识点②:LLM 通过 Tool Choice 机制,在 content 为空的情况下输出 tool_calls。这不是"执行",而是"意图表达"------它告诉开发者"我需要调用这个工具,请帮我执行"。
3.3 LLM 的"赌博"
LLM 生成这段 tool_calls 时,本质上是在 "赌":
- 它赌这个工具名是对的(依赖 description 匹配)
- 它赌参数格式是对的(依赖 JSON Schema 约束)
- 它赌外面有人会响应这段调用(依赖开发者的 Runtime)
这不是意识,这是强大的模式识别能力。 LLM 通过训练学会了"当遇到需要实时数据的问题时,如果系统提供了相关工具,就应该生成 tool_calls 而不是强行编造答案"。
四、Runtime 介入:真正干活的人
4.1 谁来执行?
LLM 发出了 tool_calls 指令,但它自己无法执行 。这时候就需要 Runtime(运行时)------也就是我们开发者写的代码来接管。
javascript
// 传统软件世界 ------ 这才是真正执行工具的地方
function get_closing_price(name) {
if (name === '青岛啤酒') {
return '67.92';
} else if (name === '贵州茅台') {
return '1488.21';
} else {
return '未找到该股票';
}
}
真实场景中,这个函数可能是:
- 调用第三方股票 API
- 查询数据库
- 读取本地文件
- 发送网络请求
4.2 Runtime 的核心职责
Runtime 只做一件事:执行,拿到结果,返回给 LLM。
arduino
用户
│
① "青岛啤酒收盘价?"
│
▼
┌──────────┐
│ LLM │ ② 识别意图,生成 tool_calls
└──────────┘
│
③ tool_calls 指令
│
▼
┌──────────┐
│ Runtime │ ④ 真正执行 get_closing_price("青岛啤酒")
└──────────┘
│
⑤ 结果 "67.92"
│
▼
┌──────────┐
│ LLM │ ⑥ 结合上下文,生成自然语言回复
└──────────┘
│
⑦ "青岛啤酒收盘价 67.92 元"
│
▼
用户
4.3 代码实现:完整的多轮对话
javascript
async function main() {
// 第一轮:用户提问
let messages = [
{ role: 'user', content: '青岛啤酒的收盘价是多少?' }
];
// 发送给 LLM(附带 tools 配置)
const response = await sendMessage(messages);
const message = response.choices[0].message;
console.log('第一轮 LLM 返回:', JSON.stringify(message));
// 输出: {"content":"","tool_calls":[{...}]}
// 保存 LLM 的 tool_calls 到对话上下文
messages.push({
role: message.role,
content: message.content,
tool_calls: message.tool_calls
});
// 检查 LLM 是否要求调用工具
if (response.choices[0].message.tool_calls) {
const toolCall = response.choices[0].message.tool_calls[0];
if (toolCall.function.name === 'get_closing_price') {
// Runtime 执行工具
const args = JSON.parse(toolCall.function.arguments);
const price = get_closing_price(args.name); // 真正执行!
console.log('工具执行结果:', price); // "67.92"
// 将工具执行结果返回给 LLM
messages.push({
role: 'tool', // role 是 "tool"!
content: price, // 工具执行的返回值
tool_call_id: toolCall.id // 用 id 关联之前的调用
});
// 第二轮:让 LLM 根据结果生成最终回复
const finalRes = await sendMessage(messages);
console.log('最终回复:', finalRes.choices[0].message.content);
// 输出类似: "青岛啤酒的收盘价是 67.92 元。"
}
}
}
4.4 对话上下文的全貌
当 Runtime 把工具结果塞回对话后,LLM 看到的完整上下文是这样的:
javascript
[
{ role: 'user', content: '青岛啤酒的收盘价是多少?' },
{ role: 'assistant', content: '', tool_calls: [{...}] },
{ role: 'tool', content: '67.92', tool_call_id: 'call_abc123' }
]
知识点③:role 为
tool的消息是 Tool Use 的关键。它告诉 LLM "你刚才要求的工具已经被执行了,这是结果"。LLM 拿到结果后,结合最初的用户问题,生成最终的自然语言回复。
五、Tool Use 的完整技术架构
把上面的流程串起来,Tool Use 的完整架构如下:
javascript
┌──────────────────────────────────────────────────────┐
│ Tool Use 生命周期 │
├──────────┬───────────────┬───────────────────────────┤
│ 阶段 │ 谁在做 │ 做什么 │
├──────────┼───────────────┼───────────────────────────┤
│ 认知植入 │ 开发者 │ 把函数翻译成 JSON Schema │
│ │ │ 挂载到 system prompt 中 │
├──────────┼───────────────┼───────────────────────────┤
│ 意图识别 │ LLM │ 理解用户问题,匹配工具 │
│ │ │ 生成 tool_calls 指令 │
├──────────┼───────────────┼───────────────────────────┤
│ Runtime │ 开发者代码 │ 解析 tool_calls │
│ 执行 │ │ 调用真正的函数/API │
├──────────┼───────────────┼───────────────────────────┤
│ 结果注入 │ LLM │ 接收 tool 消息 │
│ │ │ 结合上下文生成最终回复 │
└──────────┴───────────────┴───────────────────────────┘
六、进阶思考
6.1 Tool Choice 策略
大多数 API 支持 tool_choice 参数来控制 LLM 的工具调用行为:
| 值 | 含义 |
|---|---|
"auto" |
LLM 自己决定是否调用工具(推荐) |
"none" |
禁止调用工具 |
"required" |
强制调用工具 |
{"type": "function", "function": {"name": "xxx"}} |
强制调用指定工具 |
6.2 并行工具调用
实际业务中,一次可能需要调用多个工具:
javascript
// 用户问:"对比一下青岛啤酒和贵州茅台的股价"
// LLM 会同时返回多个 tool_calls:
{
"tool_calls": [
{ "id": "call_1", "function": { "name": "get_closing_price", "arguments": "{\"name\":\"青岛啤酒\"}" } },
{ "id": "call_2", "function": { "name": "get_closing_price", "arguments": "{\"name\":\"贵州茅台\"}" } }
]
}
知识点④:tool_call_id 在并行调用中至关重要------它确保每个工具执行结果能正确关联回对应的调用,避免 LLM 把贵州茅台的股价当成青岛啤酒的。
6.3 Agent 的本质
ini
LLM + Tools ≠ 简单的 API 调用
LLM + Tools = Agent(智能体)
当一个 LLM 可以:
- 自主决定调用哪个工具
- 根据工具返回结果调整策略
- 循环执行"思考→调用→观察→再思考"
它就不再是一个简单的问答机器,而是一个能自主完成复杂任务的 Agent。
七、总结
Tool Use 的本质,就是把"缸中大脑"和"外部世界"连接起来的桥梁:
- 认知植入阶段:开发者用 JSON Schema 把工具能力"翻译"给 LLM
- 意图识别阶段:LLM 通过语义理解决定调用哪个工具,生成调用指令
- Runtime 执行阶段:开发者代码真正执行工具,把结果注入对话
整个过程说起来并不复杂,但其中蕴含的设计思想非常精妙:把一切能力都降维成语言,让一个只会预测下一个词的模型,也能"操作世界"。
这不是魔法,这是工程。
本文的完整示例代码可见 demo/index.mjs,使用 DeepSeek API + OpenAI SDK 实现了一个最简的 Tool Use 流程,推荐动手跑一遍加深理解。
如果觉得有帮助,欢迎点赞收藏~有问题可以在评论区交流 🚀