← agent lab
// agent lab · deep dive 01

TOOL USE

工具调用深入 —— 报文级拆开一次 tool call:协议、Schema、并行、报错与 MCP
01 / THE WIRE PROTOCOL

一次工具调用的完整报文往返

入门教程里说"模型输出 JSON,你的代码执行"。现在把抽象拆掉,看真实 API 层面到底发生了几次 HTTP 往返、每条消息长什么样。关键认知:所谓"一次工具调用",其实是两次完整的模型调用夹着一次你自己的代码执行——模型与外部世界之间永远隔着你的程序。点击"下一步"逐帧播放👇

simulator · 用户问『东京现在几点?』· 报文已简化
请求 ①APP → API
响应 ①tool_use
本地执行YOUR CODE
请求 ②+ tool_result
响应 ②end_turn
三个容易忽略的细节

① 无状态:API 不记得上一次请求,所以请求 ② 必须把全部历史(包括模型自己的 tool_use)原样带上;② id 配对tool_resulttool_use_id 和调用一一对应,这是并行调用不串线的关键;③ stop_reason:你的循环就是在判断它——是 tool_use 就执行工具再发一轮,是 end_turn 就把文字给用户。Agent 框架的核心就这一个 while 循环。

02 / SCHEMA DESIGN

JSON Schema:模型的"填表说明"

工具定义是模型唯一的"使用说明书"。Schema 里每个字段都在影响模型行为——description 影响"何时调用",类型与约束影响"怎么传参"。点击左侧代码里的高亮字段,看它分别起什么作用👇

explorer · 点击字段查看作用

Schema 的语法是死的,描述的写法才是手艺。同一个查订单工具,两种写法对比👇

contrast · 含糊描述 vs 清晰描述

✗ 含糊版

"name": "query", "description": "查询订单", "properties": { "id": { "type": "string" }, "t": { "type": "string" } }
  • 名字太泛:查什么?和别的"查询"工具撞车后模型只能瞎选
  • 没说边界:能查别人的订单吗?查不到时返回什么?
  • 参数 t 是谜语:time?type?模型会按概率乱猜一个

✓ 清晰版

"name": "get_order_status", "description": "查当前登录用户某笔 订单的物流状态。只能查自己的订单; 订单号不存在时返回 NOT_FOUND。", "properties": { "order_id": { "type": "string", "description": "订单号,形如 ORD-20260610-001" } }
  • 动词 + 宾语命名:一眼看出干什么
  • 说清能力边界:模型不会答应"帮我查同事的订单"
  • 参数带格式示例:传参错误率大幅下降
检验标准

把工具描述给一个没看过你代码的新同事读:如果 ta 能仅凭描述正确使用这个接口,模型大概率也能。反过来,模型反复用错的工具,先别怪模型——回头读一遍自己的 description。

03 / PARALLEL CALLS

并行调用:一轮发出多个请求

当几件事互相不依赖(查北京天气 + 查上海天气),模型可以在同一条回复里输出多个 tool_use 块,你的代码并发执行,再把多个结果一次性塞回去——往返次数和延迟都省一半。看报文怎么写👇

demo · 『对比下北京和上海明天的天气』
// 响应 ①:模型一口气输出两个 tool_use 块(注意两个不同的 id) "content": [ { "type": "tool_use", "id": "call_A1", "name": "get_weather", "input": { "city": "北京" } }, { "type": "tool_use", "id": "call_B2", "name": "get_weather", "input": { "city": "上海" } } ] // 你的代码:Promise.all 并发跑两个请求,然后一条消息带回两个结果 "content": [ { "type": "tool_result", "tool_use_id": "call_A1", "content": "晴 31°C" }, { "type": "tool_result", "tool_use_id": "call_B2", "content": "中雨 22°C" } ]
什么时候不该并行

依赖关系的调用没法并行:得先 search_flights 拿到航班号,才能 book_flight。模型一般能自己判断依赖,但写操作要格外小心——并行发两个"扣款"就是事故。常见护栏:写操作强制串行、一轮只允许一个高危调用。

工具多了怎么办

每个工具定义都常驻上下文,几十个工具就是几千 token,而且职责重叠的工具会让模型选错。三个缓解思路:① 合并同类(一个 searchtype 参数,胜过五个 search_xxx);② 按任务动态挂载工具子集;③ 分层——前面没用的工具这轮就别给。

04 / ERROR HANDLING

工具报错了:别 crash,喂回去

工具会超时、会 404、会被传错参数。Agent 工程的妙处在于:错误信息本身也是观察结果——把它作为 tool_result(标记 is_error: true)喂回模型,模型往往能自己纠错。逐步看一个真实的自愈过程👇

simulator · 任务:『查一下 iPhone 17 的库存』
错误消息也要好好写

模型能不能自愈,取决于错误信息的质量。"Error 500" 没法自愈;"商品名不存在,可用的相近商品:iphone-17-pro, iphone-17"就能。把错误消息当成给模型的提示词来写:说清哪里错了、合法值长什么样、下一步可以试什么。

兜底护栏

自愈也可能变成死循环:同样的调用反复失败、模型反复重试。生产环境必须有:① 步数/花费上限;② 重复检测(连续 N 次相同调用直接熔断);③ 幂等设计——重试"创建订单"不应创建两单,给写操作带去重键(idempotency key)。

05 / MCP

MCP:把工具接入标准化

前面的一切都有个隐含成本:每个工具都要你亲手写定义、写执行代码。M 个 Agent 应用 × N 个工具 = M×N 次重复集成。MCP 把它变成 M+N:工具方实现一次 Server,任何支持 MCP 的应用都能即插即用。点击下面三个角色,看各自负责什么👇

architecture · 点击切换角色

MCP Server 暴露的能力不止工具一种,协议定义了三类原语(primitives)

primitives · server 能提供的三类东西

🔧 Tools · 工具

  • 是什么:可执行的动作,模型决定何时调用
  • 例子create_issuerun_query
  • 对应:本页前四节讲的全部内容

📄 Resources · 资源

  • 是什么:可读取的数据(文件、表、文档),由应用决定挂载哪些进上下文
  • 例子file:///readme.md、数据库 schema
  • 区别:工具是"做",资源是"读"

💬 Prompts · 提示模板

  • 是什么:server 预置的提示词模板,用户主动选用(如斜杠命令)
  • 例子/summarize-pr 带好了最佳实践提示词
  • 价值:把"怎么用好这个工具"也标准化了
MCP 不解决信任问题

接入第三方 MCP Server,等于把它的工具描述和返回内容都放进你的上下文——恶意 server 可以在描述里藏注入指令,或在返回里夹带"忽略之前的指示"。接 server 前像审计依赖库一样审计它,高危工具保持人工确认。

06 / QUIZ

随堂测验:检验一下

5 道题。答错没关系,解析比答案重要。

quiz · 点击选项作答
→ / KEEP GOING

继续深入