4.5 富 Schema:用 pydantic.Field 表达约束


文档摘要

4.5 富 Schema:用 pydantic.Field 表达约束 本节摘要:这是第 4 章的收尾。前面讲了类型注解能表达基础契约(类型、必填性),但现实里的约束更细——年龄必须在 0-150、邮箱要符合正则、状态只能是几个枚举值、每个参数要有给模型看的描述。光靠类型注解表达不了这些,这就是 的用武之地。本节讲清它如何在参数上叠加描述、范围、正则、枚举等约束,生成更精确的 JSON Schema,从而减少模型的错误调用。读完本节,你能把工具契约写到「模型几乎不会调错」的程度。 一、类型注解的局限 先看类型注解表达不了的几种约束: 光看 ,模型只知道「这是个整数」。

4.5 富 Schema:用 pydantic.Field 表达约束

本节摘要:这是第 4 章的收尾。前面讲了类型注解能表达基础契约(类型、必填性),但现实里的约束更细——年龄必须在 0-150、邮箱要符合正则、状态只能是几个枚举值、每个参数要有给模型看的描述。光靠类型注解表达不了这些,这就是 pydantic.Field 的用武之地。本节讲清它如何在参数上叠加描述、范围、正则、枚举等约束,生成更精确的 JSON Schema,从而减少模型的错误调用。读完本节,你能把工具契约写到「模型几乎不会调错」的程度。

一、类型注解的局限

先看类型注解表达不了的几种约束:

@mcp.tool() def set_age(age: int) -> str: """Set user age.""" ...

光看 age: int,模型只知道「这是个整数」。但它不知道:

  • age 必须在 0-150 之间(传 -5 或 999 是错的)
  • age 应该是成年(传 10 可能业务不允许)
  • 这个参数的更详细含义是什么

结果模型可能传 age=-5,你的工具执行出错或产生荒谬结果。类型注解只表达了「类型」,没表达「值的约束」。这就是 pydantic.Field 要补的缺口。

二、pydantic.Field 的核心能力

pydantic.Field 让你在参数上叠加多种约束,SDK 会把它们全部翻译进 JSON Schema:

能力 写法 Schema 体现
描述 Field(description="...") description 字段
范围 Field(ge=0, le=150) minimummaximum
长度 Field(min_length=1, max_length=100) minLengthmaxLength
正则 Field(pattern=r"^\w+$") pattern
默认值 Field(default=10) default
示例 Field(examples=[...]) examples
枚举 Literal["a", "b"](类型层) enum

用法示例:

from typing import Annotated, Literal from pydantic import Field @mcp.tool() def set_age( age: Annotated[int, Field(ge=0, le=150, description="用户年龄,0-150")], ) -> str: """Set user age.""" ... @mcp.tool() def create_user( name: Annotated[str, Field(min_length=1, max_length=50, description="用户名")], email: Annotated[str, Field(pattern=r"^[\w.]+@[\w.]+$", description="邮箱")], role: Literal["admin", "user", "guest"], # 枚举 ) -> str: """Create a user.""" ...

模型看到的 Schema 会包含所有这些约束,据此构造合法的调用参数。

三、约束如何减少错误调用

富 Schema 的价值,在于让模型在调用前就知道边界,从而避免越界调用:

没有 Field 约束 模型:set_age(age=-5) ← 不知道下界,瞎填 工具:报错或产生荒谬结果 有 Field(ge=0, le=150) 约束 模型看到 Schema:age 的 minimum=0, maximum=150 模型:set_age(age=25) ← 知道范围,填合法值 工具:正常执行

几种约束各自阻止的错误:

约束 阻止的错误
ge / le(范围) 越界数值(负年龄、超大数)
min_length / max_length 过短或过长字符串
pattern(正则) 格式错误(非法邮箱、非法 ID)
Literal(枚举) 非法选项(传了不存在的角色)
description 误解参数含义(填错语义)

四、description 字段:给模型看参数说明

description 特别值得单独讲——它直接发给模型,是「参数级」的说明。区别于工具的文档字符串(描述整个工具),description 描述单个参数:

@mcp.tool() def search( query: Annotated[str, Field( description="搜索关键词,支持中文,最长 100 字" )], limit: Annotated[int, Field( ge=1, le=50, default=10, description="返回结果数,1-50,默认 10" )], ) -> list: """Search items by query.""" ...

模型看到 Schema 时,会读到这些参数级描述,从而更准确地填值。好的 description 直接提升调用准确率——它告诉模型「这个参数该填什么、有什么限制」。

💡 技巧:写 description 时,说清三件事:是什么(数据类型/含义)、限制(范围/格式)、默认(可省略时默认值)。例:「搜索关键词,支持中文,最长 100 字」同时说了含义、格式、限制。这种描述让模型填参数时几乎不会错。

五、枚举:用 Literal 限定取值

当参数只能取几个固定值,用 Literal 类型(而不是 str)。这会被翻译成 Schema 的 enum:

from typing import Literal @mcp.tool() def set_env( env: Literal["dev", "staging", "prod"], # 只能这三个值 ) -> str: """Set deployment environment.""" ...

模型看到 Schema 里 envenum: ["dev", "staging", "prod"],只会填这三个值之一,绝不会传 productiontest。这比让模型猜「环境名该叫啥」可靠得多。

枚举适合的场景:

  • 状态/角色(admin/user/guest)
  • 环境名(dev/staging/prod)
  • 排序方向(asc/desc)
  • 资源类型(任何有限取值集合)

六、富 Schema 的代价与平衡

富 Schema 这么好,是不是越多越好?不是。过度的约束会让 Schema 臃肿,反而让模型困惑。平衡原则:

原则 说明
约束要反映真实业务规则 没有业务意义的约束不要加
description 要简洁有用 长篇大论不如一句精准描述
枚举要完整 别漏掉合法取值,否则模型没法填
别用正则表达过于复杂的格式 模型可能理解不了,反而出错

⚠️ 注意:约束是「告诉模型边界」,不是「强制模型听话」。模型仍可能尝试越界(尤其指令强烈时),你的工具仍要做参数校验(第 4.1 节的 Schema 校验)。约束减少错误调用,但不替代运行时校验——两者配合才安全。

七、一个完整的「契约精准」工具

把本章学的合起来,看一个契约精准的工具:

from typing import Annotated, Literal from pydantic import BaseModel, Field from mcp.server import MCPServer from mcp.server.mcpserver import ToolAnnotations mcp = MCPServer("UserOps") class User(BaseModel): id: int name: str role: Literal["admin", "user", "guest"] @mcp.tool(annotations=ToolAnnotations(read_only=False, destructive=False)) def create_user( name: Annotated[str, Field( min_length=1, max_length=50, description="用户名,1-50 字符" )], email: Annotated[str, Field( pattern=r"^[\w.]+@[\w.]+$", description="合法邮箱" )], age: Annotated[int, Field( ge=0, le=150, description="年龄,0-150" )], role: Literal["admin", "user", "guest"], ) -> User: """创建用户,返回新建的用户对象。""" if email_exists(email): raise ValueError(f"邮箱已存在:{email}") return User(id=gen_id(), name=name, role=role)

这个工具的契约包含:

  • 输入 Schema(类型 + Field 约束 + 枚举 + 描述)
  • 输出 Schema(User 模型)
  • 行为特征(ToolAnnotations)
  • 错误处理(抛异常)

模型看到这份契约,几乎不可能调错——这就是「类型即契约」做到极致的样子。

本节要点回顾

  1. 类型注解只表达类型,pydantic.Field 表达值的约束(范围、长度、正则、枚举)。
  2. 核心能力:描述、范围、长度、正则、默认值、示例、枚举(Literal)。
  3. 约束让模型在调用前就知道边界,避免越界调用。
  4. description 是参数级说明,说清「是什么、限制、默认」三件事,提升准确率。
  5. Literal 限定枚举取值,模型只会填合法值,适合有限取值集合。
  6. 约束要平衡:反映真实业务规则,简洁有用,别过度。
  7. 约束不替代运行时校验,两者配合才安全——约束减少错误,校验兜底。

第 4 章结束。你已经吃透了「类型即契约」的全部:输入 Schema、结构化输出、行为声明、错误处理、富 Schema。第 5 章转向资源与提示词这两类「非模型驱动」的原语。


作者与出处
原作者: 灏天文库
来源:modelcontextprotocol
许可证:MIT
整理: 灏天文库整理
由灏天文库结构化整理,提供目录导航、全文检索与在线阅读,便于系统化学习
发布者: 作者: 灏天文库 转发
评论区 (0)
U