这道题其实是在问,怎么让大模型规规矩矩地吐出我们想要的JSON,而不是自由发挥。我理解核心是要建一个三层防御体系,从软到硬,一层兜不住还有下一层。
先说第一层,提示词工程,这是最轻量的做法。我会在[[System Prompt]]里给一个严格的格式模板,配上几个[[Few-shot]]例子,告诉模型‘你就按这个格式输出,别加任何废话’。这一层无需改模型,但有输入token与模板维护成本,但可靠性很差。常见失败场景是模型还是会在JSON外面包一层markdown代码块,或者多了一个逗号、换行异常。所以它只适合内部工具、快速原型这种容错高的场景,生产环境我绝不敢只靠它。
第二层是约束解码,这是更硬的手段。主流方案分几类:一类是API内置模式:JSON mode主要管JSON语法,不保证指定schema;支持并启用strict Structured Outputs或strict工具参数时,才约束其支持的schema子集。不能把嵌套一律说成不支持,具体限制要看所用API。另一类是基于Grammar的解码,比如Outlines、XGrammar,它们把JSON Schema编译成有限状态机,解码时直接mask掉非法token,在文法受支持且生成正常完成时约束输出合法;拒绝、截断和服务失败仍要分支处理。XGrammar在vLLM生态里集成得比较好,性能也高。如果自己部署,还可以写一个自定义的logits processor,每步解码时根据已生成前缀和Schema算合法token集,把非法token的分数置为负无穷。说白了,这一层解决的是‘语法合法’问题,但代价是增加延迟,而且需要模型和框架支持。
第三层是后处理校验,这是最后的兜底。不管前面两层多强,总会有意外,所以我一定会加一段后处理逻辑。具体做法是:先清洗输出,去掉markdown标记和首尾空格;必要时尝试修复明显格式错误,但不凭空补出业务值,修复后重新校验;再用jsonschema或Pydantic做严格校验;如果还是失败,就降级到重试或者走人工队列。schema校验检查结构规则,如类型和枚举;金额是否与订单、证据和权限相符属于另一步业务校验,不能仅靠格式证明。重试必须有次数与时限上限。
落地时怎么选?我举个例子,比如做一个客服退款接口,需要模型输出包含退款金额、原因、订单号的JSON。如果只是内部测试,第一层加第三层就够了;但如果是高并发在线服务,延迟敏感,我会选XGrammar加Pydantic校验,再配合重试和降级策略。如果Schema特别复杂,有动态字段或条件分支,Outlines更灵活,但延迟会高一些,可以配合异步重试队列。
这里有一个点值得注意:约束解码虽然能保证语法合法,但它和模型的[[In-Context Learning]]能力有时会冲突,比如你强制了Schema,模型可能反而忽略了你给的示例内容。这个trade-off在实际部署中需要仔细调参。
所以我的判断是,没有银弹。真正可靠的做法是‘软硬结合、多层兜底’,根据场景选主方案,但永远留好降级路径。上线前我会特别关注异常比例和重试成功率,确保即使模型抽风,服务也不崩。