LLM结构化输出:JSON之外的校验
模型返回了合法 JSON,服务仍可能写入错误数据。格式只解决“程序能不能读”,字段约束解决“形状是否符合约定”,而来源证据和业务规则解决“内容能不能用”。LLM 结构化输出应当接在这条校验链上。
提示词与输出约束分别做什么
提示词解释任务和字段含义,例如要求模型依据材料判断是否找到答案。输出约束限制允许出现的形式。vLLM 的官方文档提供 JSON Schema、限定选项、正则及语法等结构化生成方式;不同约束适合不同任务。
只在提示词里写“请返回 JSON”,不能与服务端启用结构约束混为一谈。反过来,结构约束也不知道业务事实。一个格式正确的字段值,仍可能来自误读或猜测。
一个小协议怎样设计
假设检索回答服务只允许两个字段:status 和 evidence。status 为 found 或 not_found;evidence 为来源ID字符串数组。我们另加一条业务规则:found 必须带至少一个ID,not_found 必须带空数组。
| 响应 | 问题 |
|---|---|
| found + 空数组 | 类型可以正确,业务关系却矛盾 |
| found + [doc-1] | 结构通过后,仍须检查doc-1是否存在且支持结论 |
| not_found + 空数组 | 表示未找到证据,不等于证明答案不存在 |
保留“未找到”这种结果有实际意义。如果协议只允许成功答案,就容易把信息不足藏进一个看起来完整的对象里。来源ID也不应由模型凭空创造;可以限定候选来源,并在应用层再次核对。
解析成功以后继续校验
下面用标准库实现这个小协议的检查,已经执行7个用例,覆盖正常结果、错误枚举、错误类型、额外字段、业务矛盾和损坏JSON。它不是通用JSON Schema校验器,也没有调用模型或运行vLLM。
import json
def validate(obj):
if not isinstance(obj, dict) or set(obj) != {'status', 'evidence'}:
raise ValueError('expected exactly status and evidence')
if obj['status'] not in ['found', 'not_found']:
raise ValueError('unknown status')
if not isinstance(obj['evidence'], list) or not all(type(x) is str for x in obj['evidence']):
raise ValueError('evidence must be a list of strings')
if (obj['status'] == 'found') != bool(obj['evidence']):
raise ValueError('status and evidence disagree')
return obj
for text, expected in [
('{"status":"found","evidence":["doc-1"]}', True),
('{"status":"not_found","evidence":[]}', True),
('{"status":"found","evidence":[]}', False),
('{"status":"maybe","evidence":[]}', False),
('{"status":"found","evidence":"doc-1"}', False),
('{"status":"not_found","evidence":[],"extra":1}', False),
('{broken', False),
]:
try:
validate(json.loads(text)); actual = True
except (ValueError, TypeError):
actual = False
assert actual == expected
print('7 validation cases passed')
程序输出“7 validation cases passed”。最后一条语义检查并不会验证doc-1的内容,这一步需要实际来源。对检索服务,可以参考Cross-Encoder重排,但排序分数同样不能替代事实核验。
业务规则要放在可测试的位置
有些字段关系可以表达为Schema约束,也可以保留为独立业务函数。选择取决于服务支持的Schema范围,以及规则是否需要数据库或外部证据。无论放在哪一层,都应有可执行的失败用例,而不是只靠自然语言说明。
额外字段如何处理、缺失字段能否默认、空数组表示什么,都应该显式约定。悄悄把“maybe”改成“found”,或在解析失败后补出默认成功结果,会抹掉真实失败信息。需要重试时,限定次数,并区分格式失败、语义失败与证据不足。
接入推理服务时检查什么
先确认安装版本、请求参数与所选后端支持的约束。vLLM文档存在参数迁移说明,因此不宜从旧代码中复制字段后就假定接口兼容。本文不提供未经实际推理验证的部署命令,也不宣称某个Schema对所有后端都可用。
还要检查响应是否完整:流式传输中的半个对象不能当作最终结果;超时或长度限制导致的截断,应进入失败路径。记录格式通过率、业务通过率和来源核验通过率时使用各自明确的分母,避免用单一“成功率”隐藏问题。
结构化输出适合把模型结果接入程序,但可信性仍来自约束、校验和证据共同工作。一个能被解析的对象,只是流程的起点。
参考资料
补发说明:本文实际补发日期为北京时间2026年10月10日,文章日期保留原计划2026年10月9日14:00。示例为原创协议校验,不是模型基准测试。
支持
如果这篇文章对你有帮助,欢迎支持本站。
二维码可点击放大。更多支持方式见支持页面。


