ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型返回 JSON 后,先过这道 Python 校验

大模型返回 JSON 后,先过这道 Python 校验

大模型返回 JSON 后,先过这道 Python 校验

让大模型“只返回 JSON”,往往只是接入工作的第一步。业务代码真正需要的是:字段齐全,类型正确,取值落在约定范围内,而且失败时能够停下来。

例如,下面这段内容可以被 Python 的json.loads解析:

{"title":"整理笔记","priority":"2","done":false,"tags":[]}

但如果下游要求priority是 1~3 的整数,这里的字符串"2"就不合格。直接入库或交给后续程序,错误只是被推迟了。

这次做一个不依赖第三方库的小实验:为“从文字中提取待办事项”设计一份固定格式,再用 25 个手工构造的输入检查验收逻辑。**没有调用真实模型 API,也没有测任何模型的正确率。**这里验证的是应用接到文本后,能否按自己的约定接受或拒绝它。

先写清楚要收什么

假设每次只接收一条待办,根节点必须是对象,且只有下面四个字段:

字段本例约定
title字符串,去掉首尾空白后的长度为 1~80
priority整数 1~3;不接受布尔值,也不把字符串自动转成数字
done布尔值 true 或 false
tags数组,最多 3 项;每项是去掉首尾空白后长度为 1~20 的字符串

这些长度和取值是这个示例的业务选择,不是 JSON 通用标准。代码检查长度时调用strip(),返回的内容仍保留原文,不会悄悄修改标题或标签。重复标签在本例中允许。

缺字段就拒绝,多出command之类的字段也拒绝。这样下游拿到的是明确约定的四个字段,而不是一份“看着差不多”的对象。

一个容易漏掉的类型问题

在 Python 中,下面的表达式返回 True:

isinstance(True,int)

因此,只写isinstance(priority, int)不能排除布尔值。本例用type(priority) is int,明确区分2、2.0、"2"和true,只接收第一个。

另一个问题发生在解析阶段:Python 的 JSON 解码器默认能够接收NaN等非标准数值,同名字段默认保留最后一个值。本例使用公开的parse_constant和object_pairs_hook参数,在它们进入业务对象前拒绝。不是修改解析器内部状态。

完整脚本:先解析,再检查约定

把下面代码保存为validate_task.py。示例仅使用 Python 标准库,命令是python validate_task.py。

"""Validate a synthetic task JSON response before downstream use."""importjsondefreject_constant(value):raiseValueError(f"JSON: non-standard number{value}")defunique_object(pairs):result={}forkey,valueinpairs:ifkeyinresult:raiseValueError(f"JSON: duplicate key{key!r}")result[key]=valuereturnresultdefvalidate_task(raw):iflen(raw.encode("utf-8"))>20_000:raiseValueError("JSON: input exceeds 20000 UTF-8 bytes")obj=json.loads(raw,object_pairs_hook=unique_object,parse_constant=reject_constant)iftype(obj)isnotdict:raiseValueError("$: expected object")expected={"title","priority","done","tags"}missing,extra=expected-obj.keys(),obj.keys()-expectedifmissingorextra:raiseValueError(f"$: missing={sorted(missing)}, extra={sorted(extra)}")title=obj["title"]iftype(title)isnotstrornot1<=len(title.strip())<=80:raiseValueError("$.title: expected nonblank string, 1..80 after strip")priority=obj["priority"]iftype(priority)isnotintornot1<=priority<=3:raiseValueError("$.priority: expected integer 1..3, not bool")iftype(obj["done"])isnotbool:raiseValueError("$.done: expected boolean")tags=obj["tags"]iftype(tags)isnotlistorlen(tags)>3:raiseValueError("$.tags: expected array, at most 3 items")forindex,taginenumerate(tags):iftype(tag)isnotstrornot1<=len(tag.strip())<=20:raiseValueError(f"$.tags[{index}]: expected nonblank string, 1..20 after strip")returnobjif__name__=="__main__":samples={"valid":'{"title":"整理笔记","priority":2,"done":false,"tags":["Python"]}',"string_priority":'{"title":"整理笔记","priority":"2","done":false,"tags":[]}',"bool_priority":'{"title":"整理笔记","priority":true,"done":false,"tags":[]}',"duplicate_key":'{"title":"A","title":"B","priority":2,"done":false,"tags":[]}',}forname,rawinsamples.items():try:validate_task(raw)print(f"{name}: ACCEPT")exceptValueErroraserror:print(f"{name}: REJECT{error}")

实际运行结果

四个演示输入的实际输出如下:

valid: ACCEPT string_priority: REJECT $.priority: expected integer 1..3, not bool bool_priority: REJECT $.priority: expected integer 1..3, not bool duplicate_key: REJECT JSON: duplicate key 'title'

除此之外,本地另运行了 25 项合成用例,25 项结果与预期一致。其中 3 项应接收、22 项应拒绝,覆盖了字符串/布尔/浮点优先级、缺字段、多字段、边界长度、标签类型和数量、根节点为数组、Markdown 代码围栏、尾随文字、重复字段、NaN、Infinity 和超长 UTF-8 输入。

本地实际解释器是 Python 3.10.6。这是本次实验环境记录;没有据此声称验证过其他版本或所有异常输入。25/25 是这组用例的验收结果,不代表模型准确率,也不是安全性保证。

建议自己复做时,先运行上面的四个样例,再把done改为字符串"false",或删掉tags,确认它们确实被拒绝。最后再试边界:标题 80 个字符与 81 个字符、标签 3 项与 4 项,分别观察结果。

接到模型输出以后怎么处理

在流程中把validate_task(raw_response)放在入库、生成文件、调用其他工具之前。成功时返回对象;失败时捕获ValueError,记录原始响应与错误位置,再进入明确的修正或人工处理分支。真实响应日志可能包含隐私,保存前要按实际数据要求处理。

不要为了提高“通过率”,直接用字符串替换把"false"变成布尔值,或者悄悄删掉多余字段。确实需要容错转换时,可以单独定义转换规则,并保留转换前后的记录。本例选择严格拒绝,不实施自动修复。

若要让模型重新生成,应限制尝试次数,并明确告诉它是哪条格式约定没通过。重新生成也只是得到下一份待验证的文本,不能跳过校验。这里没有演示真实重试调用。

这道校验还管不到什么

它检查的是结构和有限的业务约定。标题即使是错误的事实、恶意指令或无意义文本,只要符合长度与类型,仍可能通过。通过校验不表示可以把字符串当作程序执行,更不代表已经取得文件写入或外部操作权限。

20,000 字节的输入上限只是一道简单限额,不是完整的资源隔离;代码也没有实现嵌套深度控制、流式解析或通用 JSON Schema。复杂对象、跨字段关系、日期语义与业务事实,需要另加适合该场景的检查。

如果你的工作流还会产生付费、发文等外部动作,也要单独管理授权、重复提交与结果未知状态。结构正确和操作允许,是两项不同的判断。

文档参考:Python JSON 标准库文档,其中说明了解码参数、非标准数值、重复字段和输入资源限制。本例的业务约定、代码和结果来自上述本地实验。

创作说明:AI 辅助创作。示例已编写并验证。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进