资讯详情

资讯详情

Tool Calling 的底层拆解:一次完整的来回到底发生了什么

一、为什么需要 Tool Calling —— 问题的根源大模型本质是一个纯文本进、纯文本出的函数f(prompt_tokens) → next_token 的概率分布它的训练目标只有一个给定上文预测最合理的下一个 token。这就带来三个天然缺陷知识是冻结的权重里固化的是训练截止前的世界模型无法知道今天天气股价多少。不会算你问它 387 × 946它靠的是背下来的模式猜答案不是真的计算。不能产生副作用它输出的只是文字发不了邮件、查不了数据库、动不了文件。Tool Calling 的设计思想就一句话模型不擅长做的事让模型学会请求别人去做并把结果拿回来继续想。关键认知转变模型从答题者变成了调度者/指挥者。它学会了一种特殊的说话方式——当被问到超出能力边界的问题时输出一段结构化的、程序能解析的文本相当于对宿主程序说帮我执行这个把结果告诉我。二、一次完整的来回逐字节视角假设用户问北京今天多少度第 1 步客户端构造请求你 → 模型API 请求体里除了用户消息还塞进了工具清单{ model: xxx, messages: [ {role: system, content: 你是助手}, {role: user, content: 北京今天多少度} ], tools: [ { type: function, function: { name: get_weather, description: 查询指定城市的实时天气, parameters: { type: object, properties: { city: {type: string, description: 城市名} }, required: [city] } } } ] }注意tools本质上也只是一段文本——它被序列化后拼进 prompt通常用特殊 token 包裹如|im_start|...或 XML 标签。模型看到的不是可执行的函数指针而是一段说明书。这就是为什么模型可能编出不存在的函数——它只是在续写文本文本而已。第 2 步模型返回 tool_call模型 → 你模型没有直接回答温度而是返回{ choices: [{ message: { role: assistant, content: null, tool_calls: [{ id: call_abc123, type: function, function: { name: get_weather, arguments: {\city\: \北京\} } }] } }], finish_reason: tool_calls }finish_reason: tool_calls是信号模型的话说到一半刻意停下了它在等你喂回结果。id是这个调用的唯一编号后面回传结果时要对应上。底层机制上这仍然是一次普通的自回归生成——只不过训练时模型被强化学习了在该停的地方输出这个 JSON 格式。早期没有原生 tool calling 的时代大家用 prompt 工程模拟如果你需要工具请输出{tool: ..., args: ...}然后用正则解析模型的输出——这就是 ReAct 模式的雏形。原生 tool calling 只是把这个约定固化进了 API 和训练里更可靠但本质没变。第 3 步你的代码执行模型不参与for call in response.choices[0].message.tool_calls: # ⚠️ 必须校验模型是提议不是保证 if call.function.name not in my_tools: # 防编造函数名 result json.dumps({error: unknown function}) else: args json.loads(call.function.arguments) # 按 schema 校验参数类型、必填字段用 pydantic / jsonschema result my_tools[call.function.name](**args)这里有一个被很多人忽略的点这个循环里模型是缺席的。你的程序在跑、数据库在查、天气 API 在调——模型只是安静地躺在服务端等下一段输入。一次工具来回从模型视角看是两次完全独立的推理生成 tool_call 一次生成总结一次中间靠消息列表串起来。第 4 步把结果回传你 → 模型构造新的请求完整的历史 工具结果一起发回去{ messages: [ {role: user, content: 北京今天多少度}, {role: assistant, tool_calls: [{id: call_abc123, ...}]}, {role: tool, tool_call_id: call_abc123, content: {\temp\: 18, \condition\: \晴\}} ] }role: tool的消息必须通过tool_call_id挂到对应的assistant消息上。这一步的本质是把工具的执行结果写进模型的上下文让它变成模型已经知道的事实。模型没有记忆、没有状态它每次都是从零读整个消息列表——所以历史必须完整重传。第 5 步模型生成最终回答模型读到消息列表发现工具结果已经在了于是输出自然语言北京今天晴气温 18°C适合出门。三、核心是什么剥掉所有术语核心就三层1. 本质是一次文本协议的循环嵌套。Tool calling 没有改变模型的任何能力——它依然是那个只会续写文本的函数。所谓调用工具是训练出来的一个约定在特定场景输出特定格式的文本由宿主程序解析、执行、再把执行结果作为新文本喂回去。模型 ↔ 工具之间不存在任何直接通道宿主程序是唯一的中介和事实来源。2. 模型负责决策程序负责真相。模型的价值在于理解意图、选择工具、填参数、解读结果、组织语言。而执行的正确性、数据的真伪、副作用的安全性全部由你的代码兜底。这就是为什么必须有 schema 校验、函数名白名单、超时重试、权限控制——因为模型输出的是概率最优的文本不是经过验证的指令。它可能编造函数、编错参数类型、把北京写成bei jing一切脏数据都要在程序侧拦下。3. 上下文即状态。模型本身无状态。整个多轮工具调用的工作状态——调了什么、拿到什么——全部编码在 messages 列表里。循环何时继续、何时停止看的是finish_reason是tool_calls就再跑一轮是stop就结束。控制权在客户端模型只负责在每一轮给出下一步建议。四、一张图总结用户问题 ──► [模型] ──► tool_call我要查北京天气 │ ▼ [你的代码] 校验 → 执行 → 拿到结果 │ ▼ 结果写入 messages ──► [模型] ──► 自然语言回答 ▲ │ └──────────── 循环直到模型说 stop ──┘模型始终只做一件事读文本产文本。工具调用、循环控制、安全校验、状态维持——全在模型之外在你的代码里
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →