Tool Use 背后的技术逻辑

Tool Use 背后的技术逻辑

不只是 API 调用。

引子:那些"聪明"的 AI

  • 豆包可以自动搜索网页------两个工具:日期获取工具、网络搜索工具
  • Claude 可以分析 Excel 表格------两个工具:读取文件工具、Excel 分析工具
  • AI Agent 可以操作电脑

Agent = LLM + tools

AI 有自我意识吗?作为开发者,这是一个精心设计的错觉------用户看到的是 LLM"完成"了工作,其实是它调用了 tools。

在显卡里疯狂跑的 LLM,本质上还是词语接龙游戏。它是被困在服务器里的大脑,看不见屏幕、摸不到键盘。一个只能做 Next Token Prediction 的概率模型,是怎么突破物理限制、调用 API、读数据库、操作物理世界的?

答案就是 Tool Use


三阶段模型

工具是函数。LLM 能调用工具,靠的是三个阶段:

阶段一:认知植入

在执行任务之前,在 system prompt 里配置工具 的时候,就在做一件非常精妙的事------认知植入

让 Tool 成为语言------用语言描述函数是什么、作用是什么、需要什么参数、返回什么结果。LLM 不懂什么是天气 API,也不懂数据库查询,但它听得懂语言。

JSON Schema 就是将复杂的软件接口函数,翻译成 LLM 能理解的"使用说明书"。

  • 外层用 JSON 声明工具格式(函数名、描述、参数列表)
  • 内层 parameters 字段用 JSON Schema 约束参数类型(string、number、required)

因为 LLM 有概率随机性,工具描述必须具体清晰。

json 复制代码
{
    "type": "function",
    "function": {
        "name": "get_closing_price",
        "description": "获取指定股票的收盘价",
        "parameters": {
            "type": "object",
            "properties": {
                "name": {
                    "type": "string",
                    "description": "股票名称"
                }
            },
            "required": ["name"]
        }
    }
}

在这个阶段,一个复杂的软件工具(get_closing_price),被降维成了一个纯粹的文本描述(JSON Schema)。用户提问"青岛啤酒的收盘价是多少?",LLM 回答不了------但它知道自己有工具可以用。

阶段二:意图识别

LLM 开始推理:训练数据里没有实时股价 → 回答不了 → 检查认知植入的工具 → 发现有 get_closing_price → 决定调用工具。

LLM 不再直接回复用户,而是输出 tool_calls------严格按照 JSON Schema 说明书,生成结构化的工具调用指令。注意 content 可能是空字符串,也可能附带一句过渡语(如"我来帮您查询"),取决于模型实现:

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

LLM 不能执行工具------开发者可以!LLM 只负责通过模式识别和推理,输出"要调哪个函数、传什么参数",实际执行完全在模型之外。

阶段三:你的代码介入

LLM 输出了 tool_calls 就停了。接下来是应用层代码(Node/Python/Java 等)接管------解析 tool_calls,匹配函数名,传入参数,真正执行工具函数,拿到结果。

关键点:结果不是直接返回给用户 ,而是以 tool 角色塞回 messages,再次发给大模型。大模型根据完整上下文(用户原始问题 → 自己做了什么决策 → 工具返回了什么结果)生成最终回复。


实战:消息流转全解

index.mjs 中"查询青岛啤酒收盘价"为例,拆解每一阶段 messages 数组的变化。

核心角色

角色 谁产生 含义
user 用户/前端 用户提的问题
assistant 大模型 大模型说的话(或工具调用指令)
tool 你的代码 工具函数的执行结果

阶段 0:初始 messages

js 复制代码
let messages = [{ role: 'user', content: '青岛啤酒的收盘价为多少?' }];
json 复制代码
[
  { "role": "user", "content": "青岛啤酒的收盘价为多少?" }
]

就一条用户问题,这是整个对话的起点。


阶段 1:第一轮调用大模型 → 对应三阶段模型的"意图识别"

js 复制代码
const response = await sendMessage(messages);

大模型看到用户问股价,训练数据里没有实时股价,于是检查自己的工具列表------发现了 get_closing_price不返回普通文字,而是返回 tool_calls

json 复制代码
{
  "role": "assistant",
  "content": "我来帮您查询青岛啤酒的收盘价。",
  "tool_calls": [
    {
      "index": 0,
      "id": "call_00_B05dAElKlxAKyio9AXDN4208",
      "type": "function",
      "function": {
        "name": "get_closing_price",
        "arguments": "{\"name\": \"青岛啤酒\"}"
      }
    }
  ]
}
tool_calls 结构
字段 含义
id 本次调用的唯一标识,后面 tool 消息靠它关联
type 固定为 "function"
function.name 要调用的函数名
function.arguments JSON 字符串,传入的参数

contenttool_calls 可以同时存在------content 是对用户说的"客气话",tool_calls 才是真正的动作。但大模型只会"说"要调什么,它自己不会执行

大模型怎么知道有哪些工具------这正是"认知植入"
js 复制代码
const res = await client.chat.completions.create({
    model: 'deepseek-v4-pro',
    messages,
    tools,          // ← 认知植入:JSON Schema 描述的函数说明书
    tool_choice: 'auto'
});

大模型不懂什么是 API,但它读得懂语言。这就是将函数降维为语言 ------把代码函数翻译成大模型能理解的文本描述。tool_choice: 'auto' 表示让大模型自己判断要不要用工具。


阶段 2:push assistant 消息

js 复制代码
messages.push({
    role: message.role,
    content: message.content,
    tool_calls: message.tool_calls
});
json 复制代码
[
  { "role": "user", "content": "青岛啤酒的收盘价为多少?" },
  {
    "role": "assistant",
    "content": "我来帮您查询青岛啤酒的收盘价。",
    "tool_calls": [
      {
        "id": "call_xxx",
        "type": "function",
        "function": { "name": "get_closing_price", "arguments": "{\"name\":\"青岛啤酒\"}" }
      }
    ]
  }
]

这一步告诉上下文:"刚才大模型看过用户问题,决定要调 get_closing_price('青岛啤酒')"。这是对话历史的一部分,第二轮调用时需要这个上下文。

常见 Bug :如果这里 push 了两遍 assistant 消息,API 会报 insufficient tool messages following tool_calls message------每个带 tool_calls 的 assistant 消息后面都必须跟对应的 tool 消息。


阶段 3:执行工具 → 对应三阶段模型的"代码介入"

js 复制代码
if (response.choices[0].message.tool_calls) {
    const toolCall = response.choices[0].message.tool_calls[0];
为什么取 0

tool_calls 是数组,大模型可以一次要求同时调多个工具。比如用户问"青岛啤酒收盘价和北京天气",大模型可能返回两个 tool_calls。这里取 [0] 是简化处理。

为什么要 JSON.parse
js 复制代码
const args = JSON.parse(toolCall.function.arguments);
// "{\"name\": \"青岛啤酒\"}"  →  { name: "青岛啤酒" }
const price = get_closing_price(args.name);  // "67.92"

arguments字符串 ,不是 JS 对象。不 parse 拿不到 .name 属性。

push 工具结果
js 复制代码
messages.push({
    role: 'tool',
    content: price,            // "67.92"
    tool_call_id: toolCall.id  // "call_xxx"
});
json 复制代码
[
  { "role": "user", "content": "青岛啤酒的收盘价为多少?" },
  { "role": "assistant", "content": "...", "tool_calls": [{"id": "call_xxx", ...}] },
  { "role": "tool", "content": "67.92", "tool_call_id": "call_xxx" }
]

tool_call_id 跟 assistant 消息里的 tool_calls[0].id 配对------大模型靠这个 id 知道 "67.92" 是"收盘价查询的结果",而不是天气查询的结果。

关键:结果不是直接返回给用户,而是返回给大模型。


阶段 4:第二轮调用大模型

js 复制代码
const finalRes = await sendMessage(messages);

大模型看到完整上下文------用户问了股价、自己决定调了 get_closing_price("青岛啤酒")、工具返回了 "67.92"------于是消化成自然语言:

json 复制代码
{
  "role": "assistant",
  "content": "青岛啤酒的收盘价为 67.92 元。"
}

完整流水图

csharp 复制代码
messages 变化过程(4 条消息,2 轮 API 调用):

[user]
  │
  ├─ 第 1 轮 sendMessage ────────── 意图识别:大模型决策调哪个工具
  │
  ▼
[user, assistant(tool_calls)]
  │
  ├─ 你的代码执行 get_closing_price ── 代码介入:实际执行工具
  │
  ▼
[user, assistant(tool_calls), tool(67.92)]
  │
  ├─ 第 2 轮 sendMessage ────────── 大模型消化结果,生成回复
  │
  ▼
[user, assistant(tool_calls), tool, assistant]

两轮调用的区别

第一轮 第二轮
大模型角色 决策者------要不要调工具、调哪个 总结者------把工具结果变成人话
对应阶段 意图识别 代码介入后的收尾
messages 里有 tool 吗
返回值 tool_calls(调工具的指令) content(给用户的答案)
谁执行 大模型只出指令 大模型只是解释结果

本质

Agent = LLM + Tools 的深层含义:

  • LLM 是大脑:做决策、理解语言、生成回复
  • Tools 是手脚:查数据库、调 API、操作文件
  • 应用代码是神经系统:连接大脑和手脚,把 LLM 的决策翻译成函数调用、把工具结果翻译回对话上下文

整个 Tool Use 机制就是这三者之间的消息传递协议。用户看到的"AI 很聪明"的错觉,来自这个精心设计的三阶段流水线------LLM 负责决策和表达,tools 负责执行,而串联这一切的,是 messages 数组里那几条 role 不同的 JSON 消息。

相关推荐
姗姗来迟了2 小时前
Vue3封装AI流式对话组件踩坑实录
人工智能
码上天下2 小时前
用Pinia管理AI多会话状态
人工智能
用户054324329703 小时前
Next.js接大模型流式SSE实操踩坑
人工智能
Assby3 小时前
从 Function Calling 到 MCP:理解 Agent 工具调用的底层通信机制
人工智能·后端
小星AI3 小时前
Claude Code 从入门到精通,一步到位
人工智能
后端小肥肠3 小时前
Codex + Obsidian 做人生副本视频:输入主题文案,直通剪映草稿
人工智能·aigc·agent
百度Geek说4 小时前
全链路研发智能体 ——从"体感能用"到"实际可用"的工程实践
人工智能
甲维斯5 小时前
500块的豆包,能帮我搞定这个么?!
人工智能
火山引擎开发者社区5 小时前
当 Agent 自己做 SRE:详解 ArkClaw 自动化可观测体系的工程实践
人工智能