技术附录附录 A

Pydantic 数据校验与 Settings 管理

集中说明 Pydantic 在请求模型、配置管理和结构化输出中的作用,用类型声明与运行时校验收紧跨模块数据边界。

3 分钟阅读

项目中的应用位置

使用位置 作用 代码
全局配置 .env加载并校验配置 Settings(BaseSettings)
API 请求校验 自动校验 JSON 请求体的字段和类型 RetrievalDebugRequest(BaseModel)
LLM 结构化输出 限制 LLM 返回固定字段 QueryVariants(BaseModel)
检索计划 序列化检索参数供诊断使用 RetrievalPlandataclass
数据隔离 封装多租户参数 DataScopedataclass

一、Pydantic 是什么

Pydantic是 Python 最流行的数据校验库。它的核心思想:用 Python 类型注解定义数据结构,运行时自动校验类型

from pydantic import BaseModel

class User(BaseModel):
    name: str
    age: int
    email: str

# 正确数据
user = User(name="张三", age=28, email="zhangsan@example.com")

# 错误的类型 → 自动报错
user = User(name="张三", age="二十八", email="zhangsan@example.com")
# ValidationError: age: Input should be a valid integer

对比手写校验:

# 手写校验(容易遗漏、重复代码多)
def create_user(data):
    if "name" not in data:
        raise ValueError("name is required")
    if not isinstance(data["name"], str):
        raise ValueError("name must be string")
    if "age" not in data:
        raise ValueError("age is required")
    # ... 20 行校验代码 ...
    return {"name": data["name"], "age": int(data["age"])}

# Pydantic(一行声明,自动校验)
class User(BaseModel):
    name: str
    age: int

二、BaseModel 核心能力

2.1 基础类型校验

from pydantic import BaseModel

class RetrievalDebugRequest(BaseModel):
    query: str                      # 必填,字符串
    session_id: str | None = None   # 可选,默认 None
    scenario_id: str | None = None
    source_filter: str | None = None
    tenant_id: str = "default"      # 可选,默认值 "default"

当 FastAPI 收到一个 JSON 请求体时:

{"query": "入职流程", "tenant_id": 123}

Pydantic 会自动校验: -querystr✅ -tenant_idint但期望str❌ → 返回 422 错误,附带清晰描述

2.2 Field 约束

from pydantic import BaseModel, Field

class RetrievalDebugRequest(BaseModel):
    query: str = Field(
        ...,                      # ... 表示必填
        min_length=1,             # 至少 1 个字符
        max_length=2000,          # 最多 2000 字符
        description="用户问题"     # 用于生成 OpenAPI 文档
    )
    session_id: str | None = Field(
        default=None,
        max_length=64,
    )
    tenant_id: str = Field(
        default="default",
        pattern=r"^[a-z0-9_]+$",  # 只允许小写字母+数字+下划线
    )

Field 支持的常用约束:

约束 说明
min_length/max_length 字符串长度范围
ge/le/gt/lt 数值范围(≥/≤/>/<)
pattern 正则表达式匹配
description OpenAPI 文档描述
examples OpenAPI 示例值

2.3 嵌套模型

class DataScope(BaseModel):
    tenant_id: str = "default"
    dataset_id: str = "default"

class RetrievalDebugRequest(BaseModel):
    query: str
    data_scope: DataScope  # 嵌套模型

# JSON 请求
{
    "query": "入职流程",
    "data_scope": {
        "tenant_id": "company_a",
        "dataset_id": "production"
    }
}

三、BaseSettings — 环境变量管理

这是 Pydantic 的一个特殊子类,专门用于管理配置:

from pydantic_settings import BaseSettings

class Settings(BaseSettings):
    # 自动从环境变量或本机 .env 文件读取
    llm_api_key: str = ""
    milvus_uri: str = "http://127.0.0.1:19530"
    mysql_port: int = 3306
    api_rate_limit_per_minute: int = 120

    model_config = {
        "env_file": ".env",        # 本机调试读取 .env 文件
        "env_file_encoding": "utf-8",
    }

# 使用
settings = Settings()
print(settings.milvus_uri)  # 优先环境变量,其次 .env,最后默认值

# 优先级
# 1. 环境变量 export MILVUS_URI=http://prod:19530
# 2. 本机 .env 文件中的 MILVUS_URI=http://prod:19530
# 3. 代码中的默认值 "http://127.0.0.1:19530"

上面是 Pydantic 的通用示例。本项目的约定更具体:本机 API 调试时,Settings从进程环境变量和项目根目录.env读取;Docker Compose 部署时,Compose 读取.env.compose并注入到 API 容器的进程环境变量里。因此不要把.env.compose复制成.env使用,也不要把.env里的localhost配置拿去跑 API 容器。

本项目中的 Settings 实例被@lru_cache缓存为全局单例:

# qa_core/config/settings.py
from functools import lru_cache

@lru_cache(maxsize=1)
def get_settings() -> Settings:
    return Settings()

# 任何模块导入即可获取同一份配置
from qa_core.config.settings import get_settings
settings = get_settings()  # 整个进程只加载一次 .env

四、with_structured_output — LLM 输出约束

这是本项目中 Pydantic 最高级的用法:让 LLM 按指定结构返回结果。

from pydantic import BaseModel, Field

class QueryVariants(BaseModel):
    variants: list[str] = Field(
        default_factory=list,
        min_length=1,
        max_length=3,
        description="查询变体列表",
    )

# 使用 LangChain 的 with_structured_output
model = ChatOpenAI(...).with_structured_output(QueryVariants)
decision = model.invoke([...])  # 返回 QueryVariants 对象

# 不是返回一段自由文本
# 而是返回 QueryVariants(variants=["入职流程", "新人入职步骤", ...])

工作原理:LangChain 将 Pydantic 模型的 JSON Schema 嵌入 System Prompt,告诉 LLM 按这些字段回答。LLM 返回的 JSON 会被 Pydantic 自动校验;如果字段类型或数量不符合约束,就会报错。

五、在本项目中的使用速查

Pydantic 类 定义位置 作用
Settings(BaseSettings) qa_core/config/settings.py 读取运行时配置:本机.env或 Compose 注入的环境变量
RetrievalDebugRequest(BaseModel) qa_core/schemas.py /api/retrieval/debug请求体校验
FeedbackRequest(BaseModel) qa_core/schemas.py 反馈请求校验
QueryVariants(BaseModel) qa_core/pipeline/query_variants.py 查询变体结构化输出约束

讨论

留下你的想法

评论功能尚未配置。启用 Giscus 后,这里会显示基于 GitHub Discussions 的评论区。

图片预览

100%