从 Next Token 到函数调用,万字拆解 Tool Use 是怎样炼成的

从"缸中大脑"到"工具大师":一文搞懂 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 的本质,就是把"缸中大脑"和"外部世界"连接起来的桥梁

  1. 认知植入阶段:开发者用 JSON Schema 把工具能力"翻译"给 LLM
  2. 意图识别阶段:LLM 通过语义理解决定调用哪个工具,生成调用指令
  3. Runtime 执行阶段:开发者代码真正执行工具,把结果注入对话

整个过程说起来并不复杂,但其中蕴含的设计思想非常精妙:把一切能力都降维成语言,让一个只会预测下一个词的模型,也能"操作世界"。

这不是魔法,这是工程。


本文的完整示例代码可见 demo/index.mjs,使用 DeepSeek API + OpenAI SDK 实现了一个最简的 Tool Use 流程,推荐动手跑一遍加深理解。


如果觉得有帮助,欢迎点赞收藏~有问题可以在评论区交流 🚀

相关推荐
星期一研究室6 小时前
数据太乱找不到?让信息自动「找上门」
微服务·产品·设计
广州智造1 天前
HyperMesh 产品功能清单|有限元前处理软件|HyperMesh 中国代理
人工智能·教程·设计·cad·建模·cae
星期一研究室1 天前
同样是写文档,为什么别人图文清爽?
微服务·产品·设计
星期一研究室2 天前
用《颜色》管理项目,混乱现场快速变清晰
微服务·产品·设计
晨米酱3 天前
Umi Mock 如何从文件声明变成 HTTP 响应
前端·javascript·设计
星期一研究室4 天前
为什么有些文档你永远不想打开第二次
微服务·产品·设计
用户6919026813394 天前
Agent 上下文记忆原理
设计模式·架构·设计
星期一研究室5 天前
长文里的观点太容易混在一起?用引用块把重点分清楚
微服务·产品·设计
星栈6 天前
为什么独立开发者都应该试试 Open Design?
设计·视觉设计·交互设计
KECCO77 天前
IP与文旅品牌如何打造专属字体?定制字体在文化表达与商业延展中的应用分析
人工智能·字体·设计·字体设计·定制字体