让大模型稳定吐 JSON,我们用 schema 约束把解析失败率压到近乎零
我们存在一项凭借大模型去抽取信息的功能哩, 要求它将一段文本之中的关键字段给提取出来, 接着返回 JSON 以供后面的程序所使用。在早期的时候, 这个东西隔三岔五就会出现报错的情况, 错误日志内全部都是「JSON 解析失败」这样的内容。
翻了下模型究竟吐出了些什么, 真是哭笑不得, 它多数时候是没问题的, 然而时不时会犯抽, 有时在 JSON 前面添上那么一句“好的, 以下是为您提取的信息: ”, 有时把 JSON 包裹于 ```json 代码块之中, 有时少个引号或者多了个逗号, 有时更是擅自作主塞进去好几个我并未要求的字段, 我的后续程序依据标准 JSON 进行解析, 它这般发作抽风, 解析瞬间就崩溃了。

在那段时间里, 我撰写了好多难看的字符串处理代码, 去处理遗留问题, 正则表达式用来剔除多余的话语, 去除代码块标记, 还要应对各种格式错误。代码越写越杂乱, 总有新的情形没有覆盖到。
治本的办法:从「求它」到「约束它」
那之后才想清楚, 依靠在输出的后续部位打补丁这种方式, 仅仅只能解决表面问题, 而想要从根本上解决, 就得在出现让它生成状态的时候, 把其格式严格管控住。
我们试过几个层次的办法,效果一层比一层硬。
起初较为初级的情形是, 在提示词当中再三叮嘱, 即只 返回 JSON这般的格式, 而一定不要有任何多余的表述话语, 同时也不要出现代码块这种情况。虽说如此做有一定作用存在 , 可还是不太保险, 因为模型偶尔还是会出现不听从指令的状况。这就相当于一种向它“求”求它的行为 样, 完全取决于它此时的心情如何。
进一步而言, 是运用模型所提供的「JSON 模式」, 许多模型存在这样一个开关, 将其打开后, 它能够保证吐出的内容至少属于合法的JSON, 不会再出现前言后语错乱以及格式混乱的情形。如此一来, 便解决了绝大部分问题, 起码不会再因为「多说了一句话」而导致解析失败。
但是最为彻底的, 是「结构化输出」, 直接给予模型一个, 将你所需要的 JSON 的样子, 也就是有哪些字段、各个字段是什么类型、哪些是必填的, 进行严格的定义, 借以使模型的输出被迫符合这个结构 , 它不再是「尽量按照你所说的去做」, 而是「输出被约束得仅仅只能是这个结构」, 代码方面呈现为这样的:
# 定义你要的结构:字段、类型、必填项,一个都不含糊
= {
"type": "",
"": {
"name": {"type": ""},
用于表示金额的, 那个类型为空的内容, 必须是数字, 不能再给你一个字符串了。
"date": {"type": ""},
},
"":
"name", "", "date"
, # 这三个必须有,缺一个都不行
"": False, # 不准它自作主张多塞字段
# 调用时把 作为约束传进去,让输出被强制限定成这个结构
resp = llm.chat(
=
{"role": "user", "": text}
={"type": "", "": },
所获取到的结果能够直接运用json.loads , 基本上无需再去进行容错处理——整个结构是得到保障的。
data = json.loads(resp.)
关键在于那个“和”, 把结构固定之后, 模型输出的内容被强制限制在这个范围内, 不会再给你一个字符串, 必填字段不会缺失, 也不会多塞杂乱无章的字段。那个“: False”尤其给力, 专治它“擅自增加字段”的问题。我后面那一堆处理字符串的擦屁股代码, 全部删除了, 解析失败率从时不时报错, 降到了几乎为零。
还有两点别忽略
结构化输出基本解决了「格式」问题,但有两点得记着。
首先, 格式正确并不意味着内容也正确。它仅仅能够确保「所返回的是一个具备合法性、结构无可挑剔的 JSON」, 可是对于里面的值究竟是否正确却无法给予保证——即便如此, 它仍然存在错把金额抽出来选错的可能性。故而, 一定要进行相应的业务方面的校验, 格式校验和内容校验完全是两回事, 这部分绝对不能忽略。
二是不是对于所有的模型, 以及所有的场景而言, 都支持那种严格的约束。倘若你所使用的模型仅仅支持到「JSON 模式」的这一层次, 那就采取次之办法运用它, 再结合必要的容错方式。要是能够施加强约束那就施加, 倘若无法施加那就退而求之次优选择, 总而言之, 千万别再愚笨地只在提示词当中「求取」它。
目前回过头去瞧那一堆往昔所撰的字符串擦屁股代码, 实在是用力用错了地方 , 模型不依从, 与其在它的后边拿着扫帚四处去擦拭 , 倒不如在它开始做事之前就把规则确立得死死的。
更多推荐
所有评论(0)