摘要:LLM本质是一个只会预测下一个词的缸中大脑,看不见屏幕也摸不到键盘。Tool Use通过认知植入、意图识别和Runtime执行三步,让模型"假装"会调API。本文用一个股票查询实战拆解Function Calling完整链路,揭开Agent工具调用真相。
目录
- [缸中大脑:LLM 的本质是一个 NTP 模型](#缸中大脑:LLM 的本质是一个 NTP 模型 "#%E4%B8%80%E7%BC%B8%E4%B8%AD%E5%A4%A7%E8%84%91llm-%E7%9A%84%E6%9C%AC%E8%B4%A8%E6%98%AF%E4%B8%80%E4%B8%AA-ntp-%E6%A8%A1%E5%9E%8B")
- [Tool Use 三步曲](#Tool Use 三步曲 "#%E4%BA%8Ctool-use-%E4%B8%89%E6%AD%A5%E6%9B%B2")
- 实战:股票查询中的完整调用链路
- 总结
一、缸中大脑:LLM 的本质是一个 NTP 模型
豆包能自动搜索网页,Claude 能分析 Excel 表格,AI Agent 能操作电脑------这些能力看起来像是 AI 有了自我意识,能够自主操作外部工具。
但这是一个精心设计的错觉。
那个在显卡里疯狂跑的 LLM,本质上还是一个词语接龙游戏------Next Token Prediction,根据上文预测下一个最可能的词。它是被困在服务器里的"缸中大脑":看不见屏幕,摸不到键盘,不知道今天几号,也查不了数据库。
那它是怎么调用 API、读取数据库、操作物理世界工具的?答案在三个字:Tool Use。
ini
LLM + Tools = Agent
二、Tool Use 三步曲
Tool Use 的核心,可以拆成三个阶段:认知植入 → 意图识别 → Runtime 执行。
第一步:认知植入------把函数降维成语言
LLM 不懂什么是天气 API,也不懂数据库查询。但它听得懂语言。
在执行任务之前,开发者在 system prompt 中配置工具 时,做了一件极其精妙的事:把复杂的软件接口函数,翻译成大模型能理解的"说明书"------JSON Schema。
javascript
const tools = [
{
type: "function",
function: {
name: "get_closing_price",
description: "获取指定股票的收盘价",
parameters: {
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"]
}
}
}
];
这个过程就是认知植入(Cognitive Implantation) :把 SDK 函数、API 接口降维为一段纯文本描述。LLM 不需要知道 get_closing_price 在底层如何实现,它只需要知道"有一个叫做获取股票收盘价的工具,需要一个股票名称参数"。
工具描述必须足够具体清晰------LLM 是一个概率模型,描述的模糊程度直接决定它能否在正确时机选择正确工具。
第二步:意图识别------LLM 的"自言自语"
用户问:"青岛啤酒的收盘价是多少?"
LLM 的推理引擎开始工作。它在原始训练语料中学不到这个实时价格,但认知植入给了它一条线索:存在一个 get_closing_price 工具。
于是 LLM 停止和用户的直接对话,转而开始"自言自语"------它严格按照工具 JSON Schema 的定义,生成一段结构化的调用代码,告诉外界它需要什么:
json
{
"tool_calls": [{
"type": "function",
"function": {
"name": "get_closing_price",
"arguments": "{\"name\": \"青岛啤酒\"}"
}
}]
}
LLM 不能执行这段代码------它只是一个预测下一个 Token 的概率模型。它只是在"赌":这段调用代码发出后,会有人响应。
这是 Tool Use 哲学上最深刻的一点:LLM 不是调用函数,而是表达了希望调用某个函数的意图。它依赖强大的模式识别和逻辑推理能力,把用户的自然语言问题翻译为精确的函数调用请求。
第三步:Runtime 介入------人(或代码)来执行
LLM 发出 tool_calls 后,轮到开发者写的 Runtime 代码上场:
javascript
// 传统软件世界 ------ 这是真实存在的函数
function get_closing_price(name) {
if (name === '青岛啤酒') {
return '67.92';
} else if (name === '贵州茅台') {
return '1488.21';
} else {
return '未找到该股票';
}
}
Runtime 拿到 tool_calls,解析出函数名和参数,执行真实函数,拿到结果------然后做一件关键的事:不是把结果直接返回给用户,而是返回给 LLM。
LLM 拿到 runtime 的执行结果后,结合原始问题和上下文,生成最终的、流畅的自然语言回复给用户。用户看来是"AI 帮我查了股价",实际上 AI 只是充当了自然语言与函数调用之间的翻译层。
三、实战:股票查询中的完整调用链路
把三步曲串成能跑的代码。首先是 LLM 客户端和发送函数------注意 tools 参数和 tool_choice: 'auto':
javascript
import OpenAI from 'openai';
import dotenv from 'dotenv';
dotenv.config();
const client = new OpenAI({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: process.env.DEEPSEEK_BASE_URL
});
async function sendMessage(messages) {
const res = await client.chat.completions.create({
model: 'deepseek-v4-pro',
messages,
tools, // 工具配置在此传入
tool_choice: 'auto' // 让LLM自行判断是否调用工具
});
return res;
}
然后是主流程:
javascript
async function main() {
let messages = [
{ role: 'user', content: '青岛啤酒的收盘价是多少?' }
];
// 第一次调用 ------ LLM 识别意图,返回 tool_calls
const response = await sendMessage(messages);
const message = response.choices[0].message;
// 把LLM的tool_calls响应存入对话历史
messages.push({
role: message.role,
content: message.content,
tool_calls: message.tool_calls
});
// Runtime 检查是否需要执行工具
if (response.choices[0].message.tool_calls) {
const toolCall = response.choices[0].message.tool_calls[0];
if (toolCall.function.name === 'get_closing_price') {
// 解析参数
const args = JSON.parse(toolCall.function.arguments);
// 执行真实函数
const price = get_closing_price(args.name);
// 将工具执行结果存入对话历史 ------ 关键!
messages.push({
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元。"
}
}
}
main();
完整消息流拆解
整个交互过程中 messages 数组的演化:
| 步骤 | role | content |
|---|---|---|
| 初始 | user |
青岛啤酒的收盘价是多少? |
| LLM 返回 | assistant |
tool_calls: [{get_closing_price, name:"青岛啤酒"}] |
| Runtime 注入 | tool |
67.92(附带 tool_call_id) |
| LLM 再次返回 | assistant |
青岛啤酒的收盘价是 67.92 元。 |
tool_call_id的作用:当一次请求中涉及多个工具调用时(如同时查股票A和股票B),每条 tool 消息必须通过tool_call_id关联到对应的tool_call,LLM 才能正确区分哪个结果对应哪个请求。
四、总结
Tool Use 用三步完成了一次"LLM 假装会调 API"的魔术:
- 认知植入:用 JSON Schema 把函数降维为 LLM 能懂的自然语言说明书,配置在 tools 参数中。
- 意图识别 :LLM 判断用户问题需要调用哪个工具,生成结构化
tool_calls请求,停止直接回复。 - Runtime 执行 :开发者代码解析
tool_calls、执行真实函数、把结果注入对话历史、再次调用 LLM 生成最终回复。
理解 Tool Use,就理解了为什么 LLM 不是一个"有意识的数字大脑",而是一个被精心设计的自然语言操作系统------它不做任何事,只是不停地预测下一个 Token;真正做事的是 Runtime 里开发者写的函数。LLM 是翻译官,Runtime 是执行者,两者协作才构成了一个完整的 Agent。
------ LLM 不会调 API,但它能告诉你它想调哪个,然后你替它调。