You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Pydantic构建含动态枚举的JSON Schema时报类型声明错误

问题原因

你遇到报错的核心原因是字段类型标注错误:EnumMeta是Python枚举类的元类,用于构造枚举类本身,并非实际的枚举值类型,Pydantic无法将元类映射为JSON Schema支持的合法类型,因此抛出声明失败的错误。
另外你原有代码里的根校验器逻辑存在缺陷:直接统计values的长度会把值为None的默认字段计入,会导致单字段传值时也误触发"选择超过一个"的报错。

解决思路

因为枚举值从外部API动态获取,无法提前定义固定的枚举类和模型类,采用动态类工厂的方式实现即可:拿到API返回的枚举值后,先动态生成对应枚举类,再动态生成绑定了这些枚举类型的Pydantic模型,后续用生成的模型做校验、导出JSON Schema都可以正常运行。

可直接运行的实现代码
from enum import Enum
from typing import Optional, Dict, Any
from pydantic import BaseModel, root_validator
import json

def _build_dynamic_enum(enum_members: Dict[str, str]) -> type[Enum]:
    """根据API返回的枚举键值对动态生成Enum类"""
    return Enum("DynamicLangEnum", enum_members)

def _build_language_model(lang_enum_map: Dict[str, Dict[str, str]]) -> type[BaseModel]:
    """动态生成包含三个语言字段的Language模型"""
    # 为三个语言字段分别生成对应枚举
    audio_enum = _build_dynamic_enum(lang_enum_map.get("language_audio", {}))
    booklet_enum = _build_dynamic_enum(lang_enum_map.get("language_booklet", {}))
    live_enum = _build_dynamic_enum(lang_enum_map.get("language_live", {}))

    class Language(BaseModel):
        language_audio: Optional[audio_enum] = None
        language_booklet: Optional[booklet_enum] = None
        language_live: Optional[live_enum] = None

        @root_validator(pre=True)
        @classmethod
        def check_single_selection(cls, values):
            # 仅统计值非None的选中项
            selected_fields = [k for k, v in values.items() if v is not None]
            if len(selected_fields) > 1:
                raise ValueError("仅可选择一种语言类型选项")
            if len(selected_fields) == 0:
                raise ValueError("必须选择一种语言类型选项")
            return values
    return Language

def build_customer_info_model(lang_enum_map: Dict[str, Dict[str, str]]) -> type[BaseModel]:
    """对外暴露的模型生成方法,传入API返回的枚举配置即可得到可用的Pydantic模型"""
    LanguageModel = _build_language_model(lang_enum_map)
    class GetYourGuideCustomerInfo(BaseModel):
        """客户信息校验类"""
        language: Optional[LanguageModel] = None
        hotel: Optional[str] = None
        supplier_requested_question: Optional[str] = None
    return GetYourGuideCustomerInfo


# ------------------- 使用示例 -------------------
if __name__ == "__main__":
    # 模拟从外部API获取的枚举配置
    api_enum_config = {
        "language_audio": {"zh": "中文", "en": "英语", "ja": "日语"},
        "language_booklet": {"zh": "中文", "en": "英语"},
        "language_live": {"zh": "中文", "en": "英语", "ko": "韩语"}
    }
    # 生成绑定了动态枚举的模型类
    CustomerInfoModel = build_customer_info_model(api_enum_config)
    # 正常生成JSON Schema,不会报错
    schema = json.loads(CustomerInfoModel.schema_json(indent=2))
    # 数据校验
    valid_instance = CustomerInfoModel(language={"language_audio": "zh"}, hotel="测试酒店地址")
注意事项
  • 每次从外部API拉取到最新的枚举值后,重新调用build_customer_info_model生成新的模型类即可,不需要修改代码里的固定定义
  • 不要使用EnumMeta作为Pydantic字段的类型标注,该类型不属于可被序列化为JSON Schema的合法值类型
  • 根校验器统计选中字段时,必须先过滤掉值为None的字段,否则会误判选中数量

内容的提问来源于stack exchange,提问作者Oz Cohen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:27:16