从 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 流程,推荐动手跑一遍加深理解。


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

相关推荐
德莱厄斯1 天前
简论中国式产品
产品·设计
冬奇Lab4 天前
每日一个开源项目(第137篇):Penpot - 真正开源的设计协作工具,SVG 原生格式消灭设计-开发鸿沟
前端·开源·设计
songgeb8 天前
iOS Dark Mode 适配笔记
设计·ui kit
星心源七境14 天前
七境体系全解析:从六韬兵法到AI锁颜,一套贯穿古典智慧与现代应用的成长操作系统
人工智能·设计模式·设计
广州智造16 天前
如何在HyperMesh的两片相邻体单元间批量创建RBE3实现载荷传递
人工智能·设计·建模·网格·网格划分·hypermesh·前处理
_code_bear_18 天前
如何设计 Agent 场景下的 Prompt
程序员·开源·设计
湖南精循科技19 天前
Ansys 案例研究 | 刹车片应力变形仿真
设计·仿真·ansys·机械·cae·大变形
bryant_meng20 天前
【Design Patterns】23 Design Patterns: The Ultimate Developer‘s Toolkit
设计模式·编程·计算机科学·设计·工程