拆解 LLM Tool Use 的完整机制:从缸中大脑到 Agent 觉醒

你用过 ChatGPT 的联网搜索,用过 Claude 分析 Excel 表格,用过豆包查询实时天气。你可能会觉得:这些 AI 真厉害,什么都会。

但真相是------ 那个在显卡里疯狂跑的 LLM,本质上是个词语接龙游戏。

它是被困在服务器里的"缸中大脑"------看不见屏幕,摸不到键盘,不知道自己跑在哪台机器上。它唯一会的技能是:给定上文,预测下一个 token

那么问题来了:一个只会预测下一个词的概率模型,是怎么突破物理限制的?它怎么调 API?怎么查数据库?怎么操作现实世界的工具?

这篇文章,我把整个机制拆给你看。


一、一个精心设计的错觉

先讲一个你可能没意识到的事。

你用豆包问"今天几号",它秒回。你以为豆包自己知道日期。

它不知道。

LLM 的训练数据有截止日期。就像一个在 2024 年被关进缸里的人,他对 2025 年、2026 年的世界一无所知。

那它怎么回答的?流程是这样的:

javascript 复制代码
你:"今天几号?"
  ↓
LLM(缸中大脑):"我训练数据里没有...但我'记得'有个工具叫 get_current_date..."
  ↓
LLM 输出:"帮我调 get_current_date"(这是给机器看的,不是给你看的)
  ↓
Runtime(外部程序)执行:new Date() → "2026-06-25"
  ↓
Runtime 把结果塞回给 LLM:"现在时间是 2026-06-25"
  ↓
LLM 转述给你:"今天是 2026 年 6 月 25 日。"

你看到的是「AI 回答了问题」。实际发生的是「AI 表达了意图 → 外部代码执行了操作 → AI 把这个结果包装成自然语言」。

AI 的自我意识?作为开发者,这是一个精心设计的错觉。

整个 Tool Use 机制,就是在精心维护这个错觉。


二、三段式流程:一套完整的"认知---决策---执行"闭环

我把它抽象成三个阶段:

arduino 复制代码
① 认知植入    →    ② 意图识别    →    ③ Runtime 介入
  告诉 LLM               LLM 决策               外部代码
  "有哪些工具"            "该用哪个"              "真正执行"

下面逐段拆开。


阶段一:认知植入 --- 把函数翻译成语言

LLM 不懂什么是 API,不懂什么是函数签名,不懂什么是数据库查询。

但它懂语言

所以你要做一件事:把你代码里的函数,"翻译"成 LLM 能看懂的说明书。这个翻译工具,叫 JSON Schema

javascript 复制代码
// 这是你的代码 --- 强类型、确定性执行的函数
function get_closing_price(name) {
    // 查数据库、调第三方 API...
    return "67.92";
}

// 这是给 LLM 看的"说明书" --- 纯文本描述
{
    "type": "function",
    "function": {
        "name": "get_closing_price",
        "description": "获取股票的收盘价",
        "parameters": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "股票名称"
                }
            },
            "required": ["name"]
        }
    }
}

左边是代码世界 ------ 编译器理解、确定性执行。右边是语言世界 ------ LLM 理解、概率推断。

JSON Schema 就是这两个世界的翻译层。 一个复杂的软件工具,被降维成了一个 LLM 能"读懂"的文本说明书。

这一步发生在什么时候?每次 API 请求时 ,这些工具定义随 tools 参数一起发给 LLM:

javascript 复制代码
const response = await client.chat.completions.create({
    model: "deepseek-v4-flash",
    messages: messages,
    tools: tools,        // ← 认知植入
    tool_choice: "auto"
});

LLM 收到后「学会」了:哦,有个叫 get_closing_price 的工具,能查股票收盘价,需要一个 name 参数。

这就是认知植入,Tool Use 的地基。

值得注意的是:description 不能随便写。LLM 是个概率模型,不是编译器,它靠描述来判断"该不该用这个工具"。如果描述模棱两可,LLM 就会用错或不用的。工具描述必须具体、清晰、无歧义


阶段二:意图识别 --- LLM 的"自言自语"

认知植入到位了。现在用户问一句:"青岛啤酒的收盘价是多少?"

LLM 的推理过程是这样的:

vbnet 复制代码
Step 1: 我的训练数据中有今天青岛啤酒的股价吗?
  → 没有,股价是实时数据

Step 2: 那我能用认知植入里的工具吗?
  → 有一个 get_closing_price
  → 描述是"获取股票的收盘价"
  → 匹配!

Step 3: 这个工具需要什么参数?
  → name(必填)
  → 用户提到了"青岛啤酒"
  → 参数:name = "青岛啤酒"

Step 4: 生成 tool_call
  → 停止对用户说话
  → 输出结构化的调用指令

然后 LLM 实际输出的东西是这样的:

json 复制代码
{
  "role": "assistant",
  "content": null,
  "tool_calls": [
    {
      "id": "call_abc123",
      "type": "function",
      "function": {
        "name": "get_closing_price",
        "arguments": "{\"name\": \"青岛啤酒\"}"
      }
    }
  ]
}

注意 content: null ------ 这是意图识别阶段最关键的信号。LLM 不再对你说话了,它转而在"自言自语",生成一段给机器解析的结构化指令。

它不是随意输出的。function.namearguments 的格式,全由阶段一注入的 JSON Schema 约束。LLM 靠的是强大的模式识别和逻辑推理能力,在概率世界中生成准确的结构化输出。

更有意思的是:LLM 其实在"赌"。 它不能执行代码,不知道自己生成的这段指令能不能真的被人响应。它只是根据训练数据中的模式推断:这种结构的输出,通常会有一个外部程序来处理。

就像一个被关在房间里的人,对着对讲机报了一串指令,赌走廊那头有人听了会去执行。


阶段三:Runtime 介入 --- 代码真正执行的那一刻

LLM 只负责"表达意图",真正动手的是 Runtime ------ 你写的 Node.js / Python / Java 代码。

javascript 复制代码
// Runtime 解析 LLM 的指令并执行
const tool_call = assistantMsg.tool_calls[0];
const args = JSON.parse(tool_call.function.arguments); // { name: "青岛啤酒" }
const price = get_closing_price(args.name);            // "67.92"

Runtime 可以做任何事:查数据库、调第三方 API、读写文件、操作硬件------这些才是真正"突破 LLM 物理限制"的动作。LLM 只是在生成意图和包装结果。

然后是整个流程中最容易被忽视的关键点:

Runtime 的执行结果,不是直接返回给用户的,而是喂回给 LLM。

javascript 复制代码
// 结果注入上下文,再发给 LLM
messages.push({
    role: 'tool',
    content: price,              // "67.92"
    tool_call_id: tool_call.id   // "call_abc123"
});
const finalResponse = await send_message(messages);
// LLM 生成:"青岛啤酒今日收盘价为 67.92 元。"

为什么必须这样做?因为 LLM 是唯一的用户交互接口 。如果把 67.92 直接丢给用户,用户看到的是一个冰冷数字,没有上下文、没有解读。Runtime 只管执行,LLM 管表达。


三、三个关键机制

以上是主线流程。但真正让这个机制在生产环境跑起来的,是三个容易被忽视的细节。

1. tool_call_id:因果链的锚点

LLM 可以一次生成多个 tool_calls

javascript 复制代码
// 用户:"茅台股价和上海天气?"
tool_calls: [
  { id: "call_aaa", name: "get_closing_price", arguments: '{"name":"茅台"}' },
  { id: "call_bbb", name: "get_weather", arguments: '{"city":"上海"}' },
]

问题来了:LLM API 本身是无状态 HTTP。每次请求都是一次独立的对话,服务器不记你是谁、刚才聊了什么。你必须手动把全部对话历史带过去。

在这种情况下,如果结果不能和调用一一对应,LLM 就无法正确处理。尤其是同一个函数被调了两次(比如查两个城市的天气),没有 ID 的话,LLM 根本分不清哪个结果对应哪个调用。

arduino 复制代码
没有 ID:"结果里有 25°C 和 18°C,但哪个是上海哪个是北京?"
有 ID:  "call_bbb 的结果是 25°C → 这是上海的天气"

tool_call_id 就是在无状态 HTTP 世界中,维持"这个结果是因为那个调用产生的"这个因果关系的唯一锚点。

2. tool_choice:LLM 行为的总开关

有时候你需要控制 LLM 的行为边界。tool_choice 就是那个开关:

设置 效果
"auto" 默认,LLM 自己决定要不要用工具
{type: "any"} 必须至少调用一个工具
{type: "tool", name: "xxx"} 强制只调用某个工具
{type: "none"} 禁止调用任何工具

日常开发基本用 "auto",但在特定场景下(比如你必须让 LLM 调用某个工具来走特定流程),其他选项能省下大量 prompt 对抗的时间。

3. stop_reason:LLM 的状态信号灯

每次 LLM 停下来,都会告诉你"为什么停的":

信号 含义 你该做什么
end_turn 讲完了 展示给用户
tool_use 帮我执行工具 执行并把结果喂回去
max_tokens 说不完就被截了 增大上限或调整策略
refusal 被安全机制拦了 降级处理

你的代码中 stop_reason 决定了程序走向。它不是可有可无的元数据,而是驱动 Agent 循环的状态机信号


四、一个容易被忽略的认知:不是两轮,是循环

我见过很多开发者以为 Tool Use 是固定的"两轮 HTTP 请求":

复制代码
请求1 → LLM返回tool_calls → 执行 → 请求2 → LLM返回最终结果

这个理解在简单场景下成立,但它掩盖了一个关键事实:

Tool Use 的本质是一个 while 循环,不是固定两轮。

ini 复制代码
while (true) {
    response = LLM(messages, tools);
    if (不需要工具了) break;        // end_turn
    results = 执行所有tool_calls;
    messages.push(results);       // 注入结果
    // 下一轮循环,LLM 判断是否还要再调工具
}

为什么需要多轮?举个真实例子:

css 复制代码
用户:"帮我分析茅台和青岛啤酒哪个更值得投资,顺便看看两地的天气"

轮次 1: LLM 调 get_closing_price("茅台")
轮次 2: 拿到茅台价格,调 get_closing_price("青岛啤酒")
轮次 3: 拿到两个价格,调 get_weather("贵州")、get_weather("青岛")
轮次 4: 综合全部数据,生成最终分析 → end_turn

四轮 HTTP 请求才完成一个用户问题。如果 LLM 发现还需要更多数据,甚至可以继续调。

理解这个 while 循环,是理解 Agent 的基础。


五、把一切串起来:消息数组的完整演化

如果你看一遍整个过程中消息数组的变化,一切都会变得清晰:

css 复制代码
第 1 轮请求:
[{ role: "user", content: "茅台股价和上海天气?" }]

第 1 轮响应(LLM 返回 tool_calls):
{ role: "assistant", content: null,
  tool_calls: [{id:"c1", name:"get_closing_price", arguments:'{"name":"茅台"}'},
               {id:"c2", name:"get_weather", arguments:'{"city":"上海"}'}] }

第 2 轮请求(Runtime 注入结果):
[
  { role: "user", content: "茅台股价和上海天气?" },
  { role: "assistant", content: null, tool_calls: [...] },
  { role: "tool", content: "1488.21", tool_call_id: "c1" },
  { role: "tool", content: "晴 25°C",  tool_call_id: "c2" }
]

第 2 轮响应(LLM 生成最终回复):
{ role: "assistant", content: "贵州茅台收盘价 1488.21 元,上海晴 25°C。" }

消息数组的 role 轮换规律是严格的:user → assistant(tool_calls) → tool → assistant(最终回复)。理解这个规律,你就理解了整个对话的骨架。


六、一张图总结

javascript 复制代码
┌───────────────────────────────────────────────────────────┐
│                     Tool Use 完整循环                       │
├───────────────────────────────────────────────────────────┤
│                                                           │
│   ① 用户输入 + tools 定义(认知植入)                         │
│        ↓                                                  │
│   ② LLM 推理 → 意图识别                                     │
│       生成 tool_calls(id + name + arguments)               │
│       content = null(停止对用户说话)                         │
│        ↓                                                  │
│   ③ Runtime 执行(突破物理限制的唯一环节)                      │
│       查数据库 / 调 API / 读文件 / 操作硬件                     │
│        ↓                                                  │
│   ④ 结果注入消息数组,再次发给 LLM                             │
│       LLM 判断:还需要工具吗?                                 │
│       ├── 要 → 回到 ②(while 循环)                          │
│       └── 不要 → 生成最终回复,返回用户                        │
│                                                           │
└───────────────────────────────────────────────────────────┘

七、写在最后

Tool Use 是 LLM Agent 的核心机制。把整个过程拆到底,你会看到一套精妙的接力:

  • LLM 负责理解意图、生成调用指令、包装结果输出。但它什么都执行不了。
  • JSON Schema 是翻译层,把代码世界和语言世界对接起来。
  • Runtime 负责真正执行------调用 API、查数据库、操作文件。但它的结果不直接给用户。
  • tool_call_id 在无状态 HTTP 中锚定因果链。
  • while 循环(而非固定两轮)才是完整的运行模式。

用一句大白话总结:

LLM 是大脑,只负责想和说。Runtime 是手,负责做。JSON Schema 是它们之间的暗号。而你作为开发者,就是搭建这套"大脑指挥手"系统的人。

从这个角度看,"AI 有没有自我意识"不是个哲学问题,而是个工程问题------这个错觉能被维护到什么程度,取决于你的 Tool Use 系统设计得有多好。


相关推荐
沉默王二3 小时前
震惊!Claude Code这五个核心概念我居然才知道!
agent·ai编程·claude
老梁agent4 小时前
一个 Agent 不够用?工业场景下的多 Agent 路由模式实战
物联网·agent
贵慜_Derek4 小时前
复杂系统没法一把梭重构:Semi-Autoresearch 怎么小步迁移还不掉功能
人工智能·agent·ai编程
HjhIron4 小时前
工具调用:当LLM学会使用"武器",AI Agent的底层逻辑拆解
llm·agent
小鼻子的猫4 小时前
独立开发 30 天:2.5 万行代码,23 个 Bug,5 次重构——一个 AI 社区的诞生
架构
武子康4 小时前
调查研究-195 从 AmEx 支付系统看 Cell-based Architecture:真正的高可用,不是无限重试,而是控制失败边界
人工智能·openai·agent
咖啡八杯4 小时前
GoF设计模式——命令模式
java·设计模式·架构
米小虾4 小时前
Prompt Engineering —— 意图的精确表达
人工智能·agent