Pydantic:让 Python 数据有“合同”

Pydantic 基于类型注解实现运行时数据校验:自动类型转换、约束检查、序列化与 JSON Schema 生成,让数据进入业务逻辑前先“签合同”。

Pydantic 是 Python 里用来做数据校验、类型转换和配置管理的库。

能力说明
数据验证类型、格式、约束检查
自动转换JSON/dict → Python 类型
序列化模型 ↔ JSON/dict
错误处理结构化错误信息

写 Python 时大概率遇到过这样的函数:

def train(model_name, batch_size, learning_rate):
    ...

调用的人可能传进来:

train("bert", "32", "0.001")
train(None, -1, 1e-5)
train({"name": "bert"}, 32.5, 0.01)

函数内部不知道到底拿到了什么。类型注解只能“看起来很美”,运行时并不一定拦得住。

Pydantic 的作用就是:把数据变成有约束、有类型、有默认值、可校验、可序列化的对象。

最小例子

from pydantic import BaseModel, Field
 
class TrainConfig(BaseModel):
    model_name: str
    batch_size: int = Field(default=32, gt=0)
    learning_rate: float = Field(default=1e-4, gt=0)
    use_amp: bool = True
 
config = TrainConfig(
    model_name="bert",
    batch_size="64",
    learning_rate="0.001"
)
 
print(config)

输出:

TrainConfig(model_name='bert', batch_size=64, learning_rate=0.001, use_amp=True)

注意两点:

  • "64" 被自动转成了 int
  • "0.001" 被自动转成了 float

传入非法值:

TrainConfig(model_name="bert", batch_size=-1)

直接报错:

ValidationError: 1 validation error for TrainConfig
batch_size
  Input should be greater than 0

这就是 Pydantic 的核心价值:数据进入系统时先签合同,不合格直接拒绝。

为什么 AI/算法项目特别需要它

算法代码通常不是死在模型里,而是死在数据和配置里:

  • 配置文件字段写错
  • 超参数类型不对
  • 数据集路径为空
  • API 返回 JSON 字段缺失
  • LLM 输出不是合法 JSON
  • 推理服务请求参数不合法
  • 训练参数混入脏数据

这些问题用 if/else 判断会越写越臭,Pydantic 更适合做一层统一的数据边界:

Python 里的数据结构 + 类型校验 + JSON Schema + 序列化。

常见用法:定义请求参数

做模型服务时,经常要定义输入结构:

from pydantic import BaseModel, Field
 
class PredictRequest(BaseModel):
    text: str = Field(min_length=1, max_length=512)
    top_k: int = Field(default=5, ge=1, le=100)
    temperature: float = Field(default=0.7, ge=0.0, le=2.0)

用户请求:

req = PredictRequest(text="介绍一下 Pydantic", top_k="10")
 
print(req.top_k)
print(type(req.top_k))

结果:

10
<class 'int'>

Pydantic 自动完成校验与转换。如果用户传:

PredictRequest(text="", top_k=1000)

直接抛 ValidationError。这比在接口里手写一堆校验逻辑清爽很多。

自定义校验器

简单类型检查之外还有业务规则。比如:模型路径不能为空,且必须以 .pt 或 .bin 结尾:

from pydantic import BaseModel, field_validator
 
class ModelConfig(BaseModel):
    model_path: str
 
    @field_validator("model_path")
    @classmethod
    def check_model_path(cls, v: str) -> str:
        if not v.strip():
            raise ValueError("model_path 不能为空")
        if not v.endswith((".pt", ".bin")):
            raise ValueError("model_path 必须是 .pt 或 .bin 文件")
        return v
ModelConfig(model_path="weights/bert.pt")    # OK
ModelConfig(model_path="weights/bert.onnx")  # ValidationError

注意:field_validator 里要返回最终字段值。

跨字段校验

一个字段依赖另一个字段时用 model_validator。比如:开启分布式训练必须指定 world_size:

from pydantic import BaseModel, model_validator
 
class DistributedConfig(BaseModel):
    use_ddp: bool = False
    world_size: int | None = None
 
    @model_validator(mode="after")
    def check_world_size(self):
        if self.use_ddp and self.world_size is None:
            raise ValueError("开启 use_ddp 时必须设置 world_size")
        return self

这保证配置整体合理,而不只是单个字段合法。

序列化与反序列化

Pydantic 模型天然适合和 JSON 打交道:

class TrainConfig(BaseModel):
    model_name: str
    batch_size: int = 32
    learning_rate: float = 1e-4
 
config = TrainConfig(model_name="bert")

对象转 dict:

config.model_dump()
# {'model_name': 'bert', 'batch_size': 32, 'learning_rate': 0.0001}

对象转 JSON 字符串:

config.model_dump_json()

JSON 转对象:

json_str = """
{
    "model_name": "bert",
    "batch_size": 64,
    "learning_rate": 0.001
}
"""
 
config = TrainConfig.model_validate_json(json_str)
print(config.batch_size)  # 64

dict 同样可以直接转对象,字符串值也会被自动转换:

config = TrainConfig.model_validate({
    "model_name": "bert",
    "batch_size": "64",
})

V2 方法速查:

方法方向
model_dump()模型 → dict
model_dump_json()模型 → JSON 字符串
model_validate(data)dict → 模型
model_validate_json(json_str)JSON 字符串 → 模型

读取配置文件、调用 API、解析 LLM 输出时特别有用。

生成 JSON Schema

Pydantic 可以根据模型自动生成 JSON Schema:

print(TrainConfig.model_json_schema())

适用场景:

  • OpenAPI 接口文档
  • LLM function calling / structured output
  • 表单生成
  • 配置校验
  • 自动化测试

让 LLM 按固定结构输出时,可以把 schema 直接给模型,要求其输出符合该 schema 的 JSON。

在 LLM 应用里

LLM 应用最怕模型“胡说八道”地返回结构。比如期望返回:

{
  "answer": "Pydantic 是数据校验库",
  "confidence": 0.92,
  "reasoning": "它基于类型注解进行运行时校验"
}

定义输出模型并校验:

from pydantic import BaseModel, Field
 
class LLMAnswer(BaseModel):
    answer: str
    confidence: float = Field(ge=0, le=1)
    reasoning: str | None = None
 
result = LLMAnswer.model_validate_json(raw_output)
 
print(result.answer)
print(result.confidence)

如果模型返回 "confidence": 2.5,Pydantic 直接指出该字段不合法。

Pydantic 适合做:

  • LLM 结构化输出校验
  • Agent 工具调用参数校验
  • RAG 检索结果结构化
  • Prompt 配置管理
  • 模型 API 输入输出约束

配置管理:pydantic-settings

训练、推理、服务部署经常需要读取环境变量。Pydantic v2 把配置管理拆到了独立包:

pip install pydantic-settings
from pydantic_settings import BaseSettings
 
class Settings(BaseSettings):
    app_name: str = "ml-service"
    model_path: str
    batch_size: int = 32
    debug: bool = False
 
    class Config:
        env_file = ".env"

.env 文件:

MODEL_PATH=/data/models/bert.pt
BATCH_SIZE=64
DEBUG=true

读取:

settings = Settings()
 
print(settings.model_path)  # /data/models/bert.pt
print(settings.batch_size)  # 64
print(settings.debug)       # True

自动读取环境变量并做类型转换,比手写 os.getenv 干净太多。

与 FastAPI 配合

FastAPI 的请求体校验就是基于 Pydantic:

from fastapi import FastAPI
from pydantic import BaseModel, Field
 
app = FastAPI()
 
class PredictRequest(BaseModel):
    text: str = Field(min_length=1)
    top_k: int = Field(default=5, ge=1, le=100)
 
@app.post("/predict")
def predict(req: PredictRequest):
    return {"text": req.text, "top_k": req.top_k}

用户传错参数时,FastAPI 自动返回 422 错误,无需手写参数校验。模型推理服务、数据标注平台、LLM API 网关基本都是这套组合。

常见坑

默认值不要用可变对象

class Config(BaseModel):
    tags: list[str] = []  # 错误:所有实例共享同一个列表
from pydantic import Field
 
class Config(BaseModel):
    tags: list[str] = Field(default_factory=list)  # 正确

validator 必须返回值

@field_validator("name")
@classmethod
def check_name(cls, v):
    if not v:
        raise ValueError("name 不能为空")
    return v  # 漏掉这行会得到 None

不要把所有东西都塞进 Pydantic

Pydantic 适合校验结构化数据,不适合处理超大文件、超大张量、超大数据集。10GB 图像数据不要塞进模型做全量校验,合理分工:

  • 元数据、文件路径、参数配置用 Pydantic
  • 大张量用专门的数据加载器处理

输入模型和输出模型分开

一个模型从头用到尾,容易把密码、token、内部状态泄露出去:

class UserCreate(BaseModel):
    username: str
    password: str
 
class UserRead(BaseModel):
    id: int
    username: str

输入模型负责校验用户提交,输出模型只返回安全字段。

工程化模板:嵌套配置

AI 项目里可以这样组织配置:

from pydantic import BaseModel, Field
 
class DataConfig(BaseModel):
    train_path: str
    val_path: str
    batch_size: int = Field(default=32, gt=0)
    num_workers: int = Field(default=4, ge=0)
 
class ModelConfig(BaseModel):
    name: str
    hidden_size: int = Field(default=768, gt=0)
    dropout: float = Field(default=0.1, ge=0.0, le=1.0)
 
class TrainConfig(BaseModel):
    learning_rate: float = Field(default=1e-4, gt=0)
    epochs: int = Field(default=10, gt=0)
    use_amp: bool = True
    seed: int = 42
 
    data: DataConfig
    model: ModelConfig

嵌套结构用 dict 直接传入,自动递归校验:

config = TrainConfig(
    data={
        "train_path": "/data/train.jsonl",
        "val_path": "/data/val.jsonl",
        "batch_size": "64"
    },
    model={
        "name": "bert",
        "hidden_size": 768,
        "dropout": 0.2
    },
    learning_rate="0.0001"
)
 
print(config.data.batch_size)  # 64
print(config.model.dropout)    # 0.2

非常适合复杂训练配置。

工具对比

工具优势适用场景
Pydantic类型注解集成、高性能API 验证、配置管理
Marshmallow灵活自定义 Schema复杂序列化
Dataclasses轻量级无需验证的简单结构

Pydantic 的本质

把 Pydantic 理解成“类型检查工具”并不准确,它更像一个运行时数据契约系统:

原始数据
  ↓
Pydantic 校验
  ↓
合法对象
  ↓
业务逻辑

它保证进入业务逻辑的数据是可信的:训练配置、推理请求、模型输出、API 数据、实验参数。很多 bug 不是算法问题,而是数据没守住边界——Pydantic 就是帮你守边界的。

什么时候该用

推荐使用:

  • API 请求/响应结构定义
  • 训练/推理配置
  • 环境变量读取
  • LLM 结构化输出
  • Agent 工具参数
  • 数据集元信息
  • 实验参数记录
  • 模型服务输入校验

不推荐使用:

  • 超大规模数据逐条校验
  • 高性能数值计算核心路径
  • 替代 NumPy/Pandas 做数据分析
  • 替代 PyTorch/TensorFlow 做张量处理

总结

Pydantic 不是让模型变强的库,但它是让 AI 工程更稳的库。核心价值三句话:

  • 用类型注解定义数据结构
  • 在运行时自动校验和转换数据
  • 方便地序列化、反序列化和生成 Schema

做模型服务、LLM 应用、RAG 系统、Agent 工具调用、训练配置管理、FastAPI 接口、数据管道入口校验时,Pydantic 基本值得成为默认工具。最简单的上手方式:

pip install pydantic

从一个配置类开始:

from pydantic import BaseModel, Field
 
class TrainConfig(BaseModel):
    model_name: str
    batch_size: int = Field(default=32, gt=0)
    learning_rate: float = Field(default=1e-4, gt=0)

当你第一次因为 Pydantic 提前拦下脏数据时,你会明白它的意义。