章节摘要:工具是三大原语里模型直接驱动的那一个,也是 SDK 投注设计精力最多的地方。本章的核心命题只有四个字——类型即契约。我们要回答:为什么
def add(a: int, b: int) -> int这一行函数定义,就足以让模型正确调用、让 SDK 正确校验、让应用拿到结构化数据?背后是 SDK 的函数元数据(func_metadata)机制在做一件事——从 Python 类型注解推断出 JSON Schema,再分发给模型与校验器。我们会讲透输入 Schema 的推断规则、结构化输出(返回类型注解即输出 Schema)、ToolAnnotations元信息、错误如何回传模型、以及用pydantic.Field表达更丰富的约束。读完本章,你将能向别人讲清楚「一个工具的契约是怎么生成、怎么校验、怎么演化的」。
阅读完本章,你应当能够:
func_metadata) 如何从 Python 类型注解推断出工具的输入 JSON Schema,并说清哪些类型会被支持、哪些不会。{"result": ...})与 Pydantic BaseModel 返回(直接作为 Schema)的差异。ToolAnnotations(如「只读」「破坏性」「开放」等元信息)向宿主与模型声明工具的行为特征。pydantic.Field 给参数加上描述、约束(范围、正则、默认值),生成更丰富的输入 Schema。整章逻辑可浓缩为一一句话:类型注解不是文档,而是可执行的契约——它同时是给模型的说明书、给 SDK 的校验器、给应用的结构化数据来源,一份注解三处生效,这是 SDK 与传统 RPC 框架的根本差异。
讲透函数元数据(func_metadata)的推断规则:基础类型(int/str/float/bool)、list / dict / 可选类型(Optional / X | None)、Pydantic 模型如何各映射成 JSON Schema;以及文档字符串如何成为工具描述。重点回答「为什么类型注解就够」。
区分两种返回:标量(如 -> int)会被包裹成 {"result": ...};Pydantic BaseModel 返回则直接以模型为输出 Schema。讲清 structured_content 与 content(给模型的文本)如何同时返回,以及如何用 output_schema 显式控制。
工具不只是「能被调用」,还有行为特征——是否只读、是否破坏性、是否对外公开。ToolAnnotations 让宿主据此决定授权策略、让模型据此决定调用谨慎度。这一节讲清这套元信息的语义与最佳实践。
工具执行会失败。讲清两条路径:抛异常(被 SDK 转成 isError: true 的错误响应,模型据此重试或换策略)、主动返回带错误标记的内容。对比两种写法的适用场景,以及「让模型看到错误」对工具调用循环的重要性。
类型注解能表达基础契约,但现实里的约束更细——数值范围、正则、枚举、描述文本。pydantic.Field 让你在参数上叠加这些约束,生成更精确的 JSON Schema,减少模型的错误调用。这一节是「让模型少踩坑」的关键。
本章遵循「输入契约 → 输出契约 → 行为声明 → 失败语义 → 精化约束」的递进,围绕「契约」这一核心概念层层展开:
输入 Schema (01) ── 模型靠它构造调用 │ ▼ 结构化输出 (02) ── 应用靠它拿数据 │ ▼ ToolAnnotations (03) ── 宿主靠它判行为 │ ▼ 错误处理 (04) ── 模型靠它学会重试 │ ▼ 富 Schema (05) ── 用 Field 精化约束,减少错误调用 │ ▼ 第 5 章:转向资源与提示词这两类「非模型驱动」的原语
输入与输出契约是工具的「两面」,缺一不可;行为声明让工具融入宿主的授权体系;错误处理让工具调用循环能自我修复;富 Schema 是把契约写到「模型几乎不会调错」的程度。本章是全书最硬核的章节之一,与第 7、8 章共同构成「研究级」核心。
前置知识:
@mcp.tool() 装饰器用法a: int 是什么)本章为后续章节奠定的基础:
call_tool 的返回结构)再次出现ToolAnnotations 会在第 12 章(中间件)被引用为拦截依据