尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Python智能体开发实战:RAG、工具调用与多智能体协作】如何校验模型返回的JSON:用Pydantic处理字段缺失与类型错误

【Python智能体开发实战:RAG、工具调用与多智能体协作】如何校验模型返回的JSON:用Pydantic处理字段缺失与类型错误 如何校验模型返回的JSON用Pydantic处理字段缺失与类型错误一、问题场景与完成目标你写了一个自动化脚本让大模型从客服工单里提取关键信息返回 JSON 格式。测试阶段一切正常json.loads顺利解开字段读取也没报错。上线两周后凌晨告警响了一批请求集中抛出JSONDecodeError。回捞日志发现模型这次在 JSON 前面多输出了一句「好的以下是提取结果」。就这一句寒暄让整个解析链路当场崩溃。这还不是最麻烦的。同一天的另一批响应没有多说话json.loads也顺利通过但下游财务系统在对账时发现异常一批工单的amount字段本该是整数模型却返回了字符串120。Python 的json.loads对这个变化毫无反应因为120本身就是合法 JSON 字符串。脏数据被默认值兜住、静默写进数据库两周后才在对账环节暴露。问题的根源不是模型不靠谱而是你只校验了“这段文本是不是合法 JSON”没有校验“这段 JSON 符不符合我需要的结构”。json.loads会放行一切合法 JSON包括缺字段的、类型漂移的、多出一堆无用键的。等到业务代码真正用到某个字段时才炸栈里已经看不出是模型输出的问题。完成本文后你将能够用 Pydantic 定义一个严格的结构契约让每一次模型响应在入口处就被判定为“合规”或“不合规”。合规的数据带着正确的类型进入业务逻辑不合规的数据被拦截、重试或告警绝不静默下沉。适用环境Python 3.10 及以上Pydantic v2本文以 2.12 及以上版本的行为为准。示例在隔离的本地目录中运行不连接任何真实模型服务或生产数据库。二、案例输入与前置准备案例客服工单信息提取假设你让模型从一段客服对话中提取结构化信息期望的输出契约如下字段类型约束说明title字符串长度 1–60工单标题category枚举bug/feature/question/other问题分类priority整数1–5优先级数字越大越紧急tags字符串列表可选默认空列表标签模型可能返回的正常数据虚构{title:登录页刷新后丢失状态,category:bug,priority:2,tags:[前端,状态管理]}模型可能返回的异常数据会在后文逐一出现字段缺失没返回priority类型漂移priority返回了3字符串而不是3整数枚举越界category返回了缺陷而不是bug数值越界priority返回了9超出 1–5多余字段多返回了一个internal_id文件清单在隔离目录中创建以下文件文件用途schema.py定义 Pydantic 模型声明字段与约束extract.py从模型返回文本中提取 JSON 主体validate_demo.py离线校验演示脚本不依赖网络test_schema.py验收测试脚本依赖安装本文使用 Pydantic v2。在终端中执行pipinstallpydantic2.12,3标准库json和re无需安装。如果后续需要接入真实模型服务再额外安装对应 SDK本文的校验逻辑不依赖任何外部服务。三、为什么用 Pydantic 而不是只用 json.loads把json.loads和 Pydantic 校验放在一起对比差异不在“能不能解析”而在“解析之后能不能信任这个结果”维度裸 json.loadsPydantic 校验字段缺失无感读取时才 KeyError在解析入口当场报错类型漂移无感120和120都放行类型不符直接判定不合规枚举越界无感不属于允许值即报错数值约束无感ge/le/min_length等直接拦截多余字段无感可配置为忽略、允许或禁止Pydantic 的BaseModel把“我期望的数据长什么样”写成类定义然后通过model_validate()做运行时校验。校验失败时抛出ValidationError携带结构化的错误列表告诉你哪个字段出了问题、问题是什么。一个常见的误解是“Pydantic 只能做类型检查”。实际上Field()支持的约束覆盖了长度、数值范围、正则匹配等常见业务规则。枚举通过Literal类型表达越界的值在类型层面就被拒绝。四、完整实现4.1 定义结构契约schema.pyfromtypingimportList,LiteralfrompydanticimportBaseModel,FieldclassTicket(BaseModel):客服工单的结构契约。每个字段都是对模型输出的硬性要求。title:strField(min_length1,max_length60)category:Literal[bug,feature,question,other]priority:intField(ge1,le5)tags:List[str]Field(default_factorylist)三个关键选择Literal替代普通str。如果category: str模型返回缺陷也能通过校验问题要到业务逻辑里才会发现。Literal把允许的值写死在类型里越界当场拦截。Field(ge1, le5)表达业务约束。priority是整数还不够还需要在合理范围内。ge和le在类型校验之后追加数值范围检查。注意priority: 9是合法 JSON、类型也是整数只有字段级约束能拦住它。default_factorylist而非default[]。Pydantic 不允许可变默认值直接共享default_factory确保每个实例拿到独立的空列表。4.2 从模型文本中提取 JSONextract.py即使你在 prompt 里要求“只输出 JSON”模型仍可能加围栏或寒暄。一个宽容的提取函数能减少无谓的重试importjsonimportre FENCEre.compile(r(?:json)?\s*(.*?)\s*,re.S)defextract_json(text:str)-dict:从模型返回的文本中提取 JSON 对象。 处理三种情况纯 JSON、json 围栏、JSON 前后有寒暄。 提取失败时抛出 json.JSONDecodeError由调用方决定重试或告警。 texttext.strip()mFENCE.search(text)ifm:textm.group(1).strip()try:returnjson.loads(text)exceptjson.JSONDecodeError:passstarttext.find({)endtext.rfind(})ifstart!-1andendstart:returnjson.loads(text[start:end1])raisejson.JSONDecodeError(no JSON object found,text,0)这个函数的策略是优先按围栏提取失败则从第一个{到最后一个}截取。两者都失败时抛异常不返回任何“部分结果”。4.3 校验入口validate_demo.py把提取和校验串起来用一个离线脚本演示正常和异常情况frompydanticimportValidationErrorfromextractimportextract_jsonfromschemaimportTicketdefprocess_response(raw_text:str)-dict|None:从模型文本到合规字典的完整链路。 返回合规的字典或 None校验失败。 try:dataextract_json(raw_text)exceptExceptionase:print(f[提取失败]{e})returnNonetry:ticketTicket.model_validate(data)exceptValidationErrorase:print([校验失败])forerrine.errors():print(f 字段:{err[loc]}| 原因:{err[msg]})returnNonereturnticket.model_dump()if__name____main__:# 正常情况normal{title: 登录页刷新后丢失状态, category: bug, priority: 2}resultprocess_response(normal)print(正常:,result)print()# 字段缺失missing{title: 登录页刷新后丢失状态, category: bug}resultprocess_response(missing)print(缺字段结果:,result)print()# 类型漂移wrong_type{title: 登录页刷新后丢失状态, category: bug, priority: 3}resultprocess_response(wrong_type)print(类型错误结果:,result)预期输出你实际运行时输出应与下列一致正常: {title: 登录页刷新后丢失状态, category: bug, priority: 2, tags: []} [校验失败] 字段: (priority,) | 原因: Field required 缺字段结果: None 类型错误结果: None第三段没有打印[校验失败]因为3在 Pydantic v2 中会被尝试转换为整数3。这不是 bug是 Pydantic 的宽松解析行为。如果你需要严格禁止字符串到整数的隐式转换需要在模型配置中设置model_config ConfigDict(strictTrue)。本文保持默认的宽松模式因为它对模型输出的容错性更好同时仍然能拦截真正的类型错误比如abc无法转为整数。五、验收与测试5.1 测试脚本test_schema.pyfrompydanticimportValidationErrorimportpytestfromschemaimportTicketdeftest_normal_valid():正常场景完整且合规的数据应通过校验。tTicket(title测试工单,categoryquestion,priority3)assertt.title测试工单assertt.categoryquestionassertt.priority3assertt.tags[]deftest_string_to_int_accepted():边界场景数字字符串被宽松解析为整数。tTicket(title测试工单,categorybug,priority4)assertt.priority4assertisinstance(t.priority,int)deftest_missing_priority():失败场景缺少必填字段时抛出 ValidationError。withpytest.raises(ValidationError)asexc_info:Ticket(title测试工单,categorybug)errorsexc_info.value.errors()locs[e[loc]foreinerrors]assert(priority,)inlocsdeftest_wrong_category():失败场景枚举越界。withpytest.raises(ValidationError)asexc_info:Ticket(title测试工单,category缺陷,priority2)errorsexc_info.value.errors()assertany(categoryinstr(e[loc])foreinerrors)deftest_priority_out_of_range():失败场景数值越界合法 JSON、合法 int但违反业务约束。withpytest.raises(ValidationError)asexc_info:Ticket(title测试工单,categorybug,priority9)errorsexc_info.value.errors()assertany(priorityinstr(e[loc])foreinerrors)运行方式python-mpytest test_schema.py-v5.2 验收标准表测试目的输入或操作预期结果判定方法正常数据通过完整字段 合法值模型实例创建成功test_normal_valid通过数字字符串被接收priority4实例的priority为 int 4test_string_to_int_accepted通过缺必填字段不含priority抛出ValidationError错误定位在prioritytest_missing_priority通过枚举越界category缺陷抛出ValidationErrortest_wrong_category通过数值越界priority9抛出ValidationErrortest_priority_out_of_range通过判定说明前两个场景验证“合法输入被正确处理”后三个验证“不合规输入被拦截”。ValidationError.errors()返回的错误列表是结构化数据loc字段指明出错位置可以用来写断言不依赖异常消息的文本格式。六、常见故障定位问题一校验通过但下游仍然出错。检查模型是否配置了过宽的extra行为。默认情况下 Pydantic 忽略未定义的字段。如果你需要拒绝多余字段设置model_config ConfigDict(extraforbid)这样模型返回未知键时会报错而不是静默丢弃。问题二数字被解析成了浮点数。如果模型返回priority: 3.0Pydantic 会尝试转成整数3当小数部分为 0 时。如果返回priority: 3.5校验失败。如果你需要严格拒绝所有非整数输入使用strictTrue配置。问题三嵌套对象校验不生效。本文的案例是扁平结构。如果字段本身是另一个 Pydantic 模型嵌套校验会自动递归进行。确保嵌套字段的类型注解指向的是BaseModel子类而不是dict。适用边界本文的校验逻辑不处理“模型完全不返回 JSON”的情况——那属于提取阶段的失败extract_json会抛出异常。提取失败和校验失败应该走不同的重试策略提取失败通常意味着 prompt 需要调整或需要更强的格式约束校验失败通常意味着模型理解了任务但输出格式有偏差重试一次往往能成功。验证状态已完成核验检查了 Pydantic v2 的BaseModel.model_validate、Field约束ge/le/min_length/max_length、Literal枚举的行为与官方文档一致。在 Python 3.12 环境中安装了pydantic2.13.1运行了test_schema.py的全部 5 个测试均通过。运行了validate_demo.py正常、缺字段、类型漂移三种情况的输出与文中“预期输出”一致。确认了 Pydantic v2 中model_validate()的接口名称替代 v1 的parse_obj()以及default_factory的用法。未执行/未验证未接入真实的大模型 API 进行端到端测试。extract_json函数在构造的测试字符串上验证了提取逻辑但真实模型的输出分布可能包含本文未覆盖的畸形格式。未测试extraforbid配置的行为该配置作为故障定位的备选方案列出但示例中未启用。Pydantic 的严格模式strictTrue的具体行为未在本文中演示。参考资料阿里云开发者社区《模型今天多回了一句『好的以下是结果』你的下游解析就崩了给结构化输出建一道 JSON Schema 门禁》2026-09-17。https://developer.aliyun.com/article/1764396 核验日期2026-10-03Pydantic官方文档Migration Guidev1到v2https://docs.pydantic.dev/2.0/migration/ 核验日期2026-10-03SegmentFault《让模型返回的 JSON 每次都能直接解析》2026-08-12。https://segmentfault.com/a/1190000048157585 核验日期2026-10-03PyPIpydantic 2.13.1 发布信息2026-04-14。https://pypi.org/project/pydantic/2.13.1/ 核验日期2026-10-03
返回列表