主线 1|工具、MCP与协议

工具接口——为什么 agent 需要结构化 I/O

语言模型产出 token。程序执行动作。这两者之间的鸿沟就是工具接口:一份让模型能请求动作、让宿主能执行动作的契约。2026 年的每一套技术栈——OpenAI、Anthropic、Gemini 上的 function calling;MCP 的 tools/call;A2A 的 task part——都是同一个四步循环的不同编码。

类型讲解课语言Python(标准库,不调 LLM)前置要求模块2《LLM应用工程》(LLM 补全 API)预计时间约 45 分钟
下载本课源码

学习目标

  • 解释为什么一个只会生成文本的 LLM,单凭自己无法对真实世界采取行动。
  • 画出四步工具调用循环(describe → decide → execute → observe),并说清每一步归谁负责。
  • 把一个工具描述写成三部分:名字、JSON Schema 输入、一个确定性的执行函数。
  • 区分纯工具和有副作用的工具,并说明为什么这个划分对安全很重要。

问题背景

LLM 吐出的是下一个 token 的概率分布。这就是它输出的全部表面。如果你问一个聊天模型"班加罗尔现在天气怎么样",它能写出一句看似合理的话,但它没法接进天气 API。那句话可能碰巧对,也可能是三天前的陈旧数据。

弥合这道鸿沟,正是工具接口的目的。宿主程序——你的 agent 运行时、Claude Desktop、ChatGPT、Cursor,或一段自定义脚本——向模型广播一份可调用工具的清单。当模型判断需要采取行动时,它吐出一个结构化的载荷,指明工具名和参数。宿主解析这个载荷,真正去执行工具,再把结果喂回去。循环持续到模型判断不再需要调用为止。

这份契约的第一个版本于 2023 年 6 月随 OpenAI 的 "functions" 参数发布。Anthropic 紧随其后,在 Claude 2.1 里引入了 tool_use block。Gemini 几个月后加上了 functionDeclarations。如今每家 provider 都暴露相同的形状:输入是一份用 JSON Schema 定型的工具清单,输出是一个 JSON 载荷的工具调用。Model Context Protocol(2024 年 11 月)把这份契约一般化,让一个工具注册表能服务所有模型。A2A(2026 年 4 月,v1.0)在同一基元之上又叠了一层,用于 agent 之间的委派。

四步循环是这一切底下的不变量。模块3《工具、MCP与协议》 里其余的全部内容,都只是它的细化。

核心概念

第一步:describe

宿主用三个字段声明每个工具。

  • Name。 一个稳定、机器可读的标识符。是 get_weather,不是"天气那玩意儿"。
  • Description。 一段自然语言简介。"当用户问某个具体城市的当前天气时使用。不要用于历史数据。"
  • Input schema。 一个 JSON Schema 对象(draft 2020-12),描述工具的参数。

模型收到这份清单。现代 provider 会用各自特定的模板把这些声明序列化进 system prompt,所以你作为调用方只需打交道于结构化形式。

第二步:decide

给定用户消息和可用工具,模型从三种行为里选一个。

  1. 直接用文本回答。 不调用工具。
  2. 调用一个或多个工具。 吐出结构化的调用对象。在 parallel_tool_calls: true 下(OpenAI 和 Gemini 默认开启,Anthropic 需手动开启),模型可以在一轮里吐出多个调用。
  3. 拒绝。 严格模式的结构化输出可以产出一个定型的 refusal block,而不是调用。

一个工具调用载荷有三个稳定字段:调用 id、工具 name、一个 JSON arguments 对象。id 存在的意义在于让宿主能把后来的结果和具体那次调用对应起来——当并行调用乱序返回时,这一点很关键。

第三步:execute

宿主收到调用,按声明的 schema 校验参数,然后运行执行器。参数非法意味着模型幻觉出了一个字段,或用错了类型——这在弱模型上是非常常见的失败模式。生产环境的宿主在参数非法时会做三件事之一:快速失败并把错误抛回给模型、用受约束的解析器修复 JSON,或带着校验错误重试模型。

执行器本身就是普通代码。Python、TypeScript、一条 shell 命令、一次数据库查询。它产出一个结果,通常是字符串,但也可以是任意 JSON 值,或一个结构化内容 block(在 MCP 里是 text、image 或 resource 引用)。结果必须是可序列化的。

第四步:observe

宿主把工具结果追加进对话(作为一条 tool 角色的消息,带匹配的 id),再次调用模型。模型现在上下文里有了工具输出,可以产出最终答案,或请求更多调用。这个过程持续到模型停止吐出调用,或宿主撞上迭代次数的安全上限。

信任划分

工具分两种,这个区分对安全很重要。

  • 纯工具(Pure)。 只读、确定性、无副作用。get_weathersearch_docsget_current_time。可以放心地推测性调用。
  • 有后果的工具(Consequential)。 改变状态、花钱、碰用户数据。send_emaildelete_fileexecute_trade。必须设门槛。

Meta 在 2026 年提出的 agent 安全 "Rule of Two"(二选二规则)说:单独一轮里,以下三者最多只能组合其中两个——不可信输入、敏感数据、有后果的动作。工具接口正是你强制执行这条规则的地方——靠拒绝调用、要求用户确认,或提升权限范围。完整的安全章节见模块3《工具、MCP与协议》第15课,agent 层面的权限策略见模块4《Agent工程》第9课

循环在哪里发生

场景谁来 describe谁来 decide谁来 execute
单轮 function calling(OpenAI/Anthropic/Gemini)应用开发者LLM应用开发者
MCPMCP server经由 MCP client 的 LLMMCP server
A2AAgent Card 发布方调用方 agent被调用方 agent
Web 浏览器(function-calling agent)浏览器扩展 / WebMCPLLM浏览器运行时

到处都是同样的四步。列名在变,结构没变。

为什么不干脆 prompt 模型吐 JSON 就行?

"让模型用 JSON 回复"是 function calling 之前的做法。在前沿模型上它有大约 5% 到 15% 的概率失败,在更小的模型上失败率高得多。失败模式包括缺花括号、多余逗号、幻觉字段、类型错误。然后你还得加一道 JSON 修复、一次重试,或一个受约束的解码器。

原生 function calling 更好,有三个原因。第一,provider 端到端地用确切的调用形状训练模型,所以严格模式下合法 JSON 的比例升到 98% 到 99%。第二,调用载荷待在它自己的协议槽位里,而不在自由文本中——所以工具调用永远不会泄进用户可见的回复里。第三,provider 用受约束解码强制 schema 合规(OpenAI 的 strict mode、Anthropic 的 tool_use、Gemini 的 responseSchema)。输出保证能通过校验。

模块3《工具、MCP与协议》第2课 把三家 provider 的 API 并排走一遍。模块3《工具、MCP与协议》第4课 深入讲结构化输出。

断路器

循环会在模型停止吐出调用、或宿主撞上最大轮数时终止。生产环境的宿主把这个值设在 5 到 20 轮之间。超过这个数,你几乎肯定陷进了一个模型出不来的循环。Claude Code 默认 20;OpenAI Assistants 是 10;Cursor 的 agent 模式是 25。

另一种做法——无界循环——每隔半年就以"agent 一夜之间烧掉 400 美元 API 调用"的复盘形式冒出来一次。没有设上限别上线。

模块4《Agent工程》第12课 深入讲错误恢复和自愈;生产基础设施实践 讲生产环境的限流。

模块3《工具、MCP与协议》 接下来走向何处

  • 第 02 到 05 课打磨 provider 层面的工具调用表面。
  • 第 06 到 14 课把这个循环一般化为 MCP。
  • 第 15 到 18 课为这个循环抵御恶意 server、对抗性用户,以及未认证的远程鉴权表面。
  • 第 19 到 22 课把这套模式扩展到 agent 间协作、可观测性、路由和打包。
  • 第 23 课用上每一个基元,交付一套完整的生态。

剩下的每一课都是这个四步循环的细化。把它当作不变量记在心里。

实际使用

code/main.py 不调 LLM 就跑通这个四步循环。一个假的 "decider" 函数靠对用户消息做模式匹配来模拟模型;而执行器、schema 校验器、observe 步骤的脚手架都是真的。跑一跑,看完整的请求/响应编排,中间状态可打印;之后某一课里再把假 decider 换成任意一家真实 provider。

要看什么:

  • 工具注册表每个工具存四个字段:name、description、schema,以及一个执行器引用。
  • 校验器是用纯标准库写的一个极简 JSON Schema 子集(types、required、enum、min/max)。模块3《工具、MCP与协议》第4课 交付一个更完整的。
  • 循环把迭代次数的上限定在 5。生产环境的 agent 正需要这种断路器。

拿去用

本课产出 outputs/skill-tool-interface-reviewer.md。给定一份工具定义草稿(name + description + schema + 执行器轮廓),这个 skill 会审计它是否适配循环:name 是否机器稳定、description 是否是一份完整的使用简介、schema 是否正确用了 JSON Schema 2020-12、纯工具 vs 有后果的工具的分类是否明确。

练习

  1. code/main.py 加上第四个工具,叫 get_stock_price(ticker)。把它的 description 写成"当用户用股票代码问当前股价时使用。不要用于历史价格或市场综述。"跑一遍脚手架,确认假 decider 会把提到股票代码的查询路由到这个新工具。

  2. 弄坏 schema 校验器。传一个 arguments 对象缺了必填字段的调用,确认宿主在执行前就拒绝它。然后传一个带多余未知字段的调用。做决定:宿主该拒绝还是忽略?用一条安全论证来证明你的选择。

  3. 把脚手架里的每个工具分类为纯工具或有后果的工具。给需要的注册表条目加上 consequential: true 标志,并改造循环,在每次选中有后果的工具时打印一行"将向用户确认"。这就是每个生产宿主都需要的确认门槛的形状。

  4. 拿纸画出四步循环,并把上面那张 provider 列表按你最喜欢的客户端(Claude Desktop、Cursor、ChatGPT,或一套自定义栈)填好。和模块3《工具、MCP与协议》第6课 里 MCP 特定的变体交叉对照。

  5. 从头到尾读一遍 OpenAI 的 function calling 指南。找出那个待在请求里、却不在本文这套四步循环里的字段。解释它带来了什么,以及它为什么是方便而非必需的。

关键术语

术语大家嘴上怎么说它实际是什么
Tool"模型能调的一个东西"name + JSON Schema 定型的输入 + 执行函数 三件套
Function calling"原生工具调用"provider 层面的 API 支持,能吐出结构化工具调用而非散文
Tool call"模型发出的行动请求"模型吐出的一个带 idnamearguments 的 JSON 载荷
Tool result"工具返回了什么"执行器的输出,包在一条带匹配 id 的 tool 角色消息里
Parallel tool calls"一次多个调用"一轮模型输出里的多个调用对象,相互独立,可按 id 排序
Strict mode"保证是 JSON"受约束解码,强制模型输出能通过声明 schema 的校验
Pure tool"只读工具"无副作用;可安全地重跑
Consequential tool"动作工具"改变外部状态;需要门槛、审计或用户确认
Four-step loop"工具调用周期"describe → decide → execute → observe
Host"agent 运行时"持有工具注册表、调用模型、运行执行器的那个程序

延伸阅读

本课配套资料4

下列文件已全部收录。可单独在线查看,也可使用页面顶部的“下载本课源码”一次下载。

图片与资源