本地测试时,结构化输出最容易给人一种"问题已经解决"的错觉:模型返回 JSON,解析器没有报错,Pydantic 模型实例化成功,数据库也顺利写入。真正上线一段时间后,团队才发现其中有些记录虽然格式完美,业务含义却不成立。
例如,工单优先级被填成了合法的 urgent,但原始请求只是普通咨询;情感标签是 positive,分数却只有 0.1;抽取结果中的日期顺序颠倒;本来没有任何命中项,模型却返回了几条看起来很像真的记录。
这些都不是 JSON 语法错误,而是语义质量问题。JSON Schema 能约束"字段长什么样",不能单独证明"字段为什么应该是这个值"。如果你的系统把结构化输出直接当成事实写入数据库,校验通过反而可能让错误更难暴露。

先把"结构正确"和"内容正确"分开
可以把一次模型输出拆成三层:
1.语法层 :内容能否被 JSON 解析。
2.结构层 :字段是否齐全、类型是否正确、枚举值是否在允许范围内。
3.语义层 :字段是否有原始输入依据,多个字段是否互相一致,整体结果是否符合业务规则。
约束解码、函数调用的严格参数校验,主要解决前两层。它们的价值仍然很大:减少解析重试,避免字段类型漂移,让下游程序可以稳定接收数据。但"稳定接收"不等于"放心使用"。
下面的 5 类问题,都会生成结构合法的结果。
1. 枚举值合法,但选择与上下文不匹配
枚举约束只能限定候选集合。例如:
{
"priority": "low"
}
如果 priority 的枚举是 low、normal、high、urgent,这个结果在结构上完全合格。但模型是否读懂了输入、是否正确判断了影响范围,Schema 并不知道。
这种问题常见于优先级、风险等级、内容类型、审核结论等字段。它的危险之处在于,错误值不是随机乱码,而是一个"看起来很专业"的合法词。
建议做法: 不要只统计解析失败率,还要记录每个枚举字段的长期分布,并按业务来源、用户类型和输入长度分组观察。如果某个值在不同输入上异常集中,或者版本升级后分布突然改变,就应该抽样回看原文,而不是只看接口成功率。
2. 必填字段让模型在没有依据时也必须作答
当 Schema 把所有字段都设为必填时,模型通常会努力填满它们。输入是一张无法识别的图片,模型仍可能返回完整的商户名、金额和日期;输入没有匹配项,模型仍可能编造一条摘要或一项实体。
这不是模型"违反了格式要求",恰恰是它遵守了格式要求。问题在于,结构设计没有给"不确定"留下位置。
对抽取任务来说,下面这种设计通常比强制返回字符串更安全:
{
"value": null,
"confidence": 0.08,
"evidence": "原文未出现可确认的订单号"
}
null 不是万能药。它需要配合字段级规则:哪些字段允许为空、什么情况下必须提供证据、低于什么置信度需要人工复核。置信度也不能直接当成事实概率,它更适合作为路由信号,用来决定是否进入二次检查。
3. 单个字段都正确,组合起来却互相矛盾
很多 Schema 校验器默认按字段检查类型,不会自动理解业务关系。以下结果可以通过基本类型校验:
{
"sentiment": "positive",
"score": 0.1,
"start_date": "2026-03-15",
"end_date": "2026-03-10"
}
真正的约束存在于字段之间:正面情感通常不应对应极低分数,结束日期也不应早于开始日期。
可以把这类规则写进应用层,而不是期待 JSON Schema 自动推断。以 Pydantic 为例,下面的模型只演示"跨字段检查"的思路,阈值仍需按业务数据校准:
from datetime import date
from pydantic import BaseModel, Field, model_validator
class Result(BaseModel):
sentiment: str
score: float = Field(ge=0, le=1)
start_date: date | None = None
end_date: date | None = None
@model_validator(mode="after")
def check_consistency(self):
if self.sentiment == "positive" and self.score < 0.5:
raise ValueError("positive sentiment requires score >= 0.5")
if self.start_date and self.end_date and self.end_date < self.start_date:
raise ValueError("end_date cannot be earlier than start_date")
return self
这类校验能抓住已经被明确描述的矛盾,但它不能覆盖所有语义错误。规则应当作为第一道拦截,而不是质量保证的终点。
4. 输出没有报错,分布却已经"塌缩"
模型服务最难排查的一类问题,不是某一条记录明显错误,而是大量输入开始得到相似答案:风险分数长期贴近 0.95,类别总是 general,数组长度几乎固定,低置信度从系统中消失。
单条记录看,这些值可能都合理;放在时间序列中,它们却暴露出模型正在使用安全默认值,或者提示词、解码参数、上游输入发生了变化。
可监控的指标包括:
1.枚举字段的频率和熵是否持续下降。
2.数值字段的均值、方差和分位数是否突然收窄。
3.数组长度分布是否从多峰变成固定长度。
4.不同输入类别是否产生近乎相同的输出。
熵监控适合发现"分布变窄",但不负责解释原因。告警后仍需检查提示词版本、模型版本、采样配置、输入清洗和业务流量构成。不要把一次分布变化直接归因于模型本身。
5. 空数组被模型"补成了几条结果"
抽取系统中,空数组是一个正常答案:页面没有优惠券、邮件没有订单号、日志没有匹配错误,都应该返回 \[\]。但在一些任务里,模型更倾向于生成对象,因为对象更像"完成了一项工作"。
因此,出现"空数组率长期为零"应当被视为信号,而不是好成绩。正确的检测方式不是规定所有数组必须为空或非空,而是根据历史标注数据估计合理区间,再观察线上结果是否偏离。
建议同时保留证据字段:
{
"matches": \[\],
"checked_text_span": "全文",
"reason": "没有发现符合规则的条目"
}
证据字段也可能被模型编造,所以高风险场景应在代码中重新定位原文片段,或让第二个检查步骤验证"结果是否能在输入中找到依据"。
为什么继续增加 Schema 规则仍然不够
对于已知错误,增加校验规则很有效:日期顺序、数值范围、必填条件、枚举组合都可以被明确表达。但语义错误的开放空间远大于结构错误。你只能为已经认识到的错误写规则,无法提前枚举模型下一次可能采用的错误解释。
这也是为什么"解析成功率"不能作为唯一质量指标。解析成功率回答的是:程序能不能读懂返回值。它没有回答:返回值是否由输入支持,是否符合业务目标,是否应该进入自动化流程。
在部分任务中,可以比较两条生成路径:
约束生成 :模型生成时就被限制在指定格式内,首轮解析成功率更高。
自由生成后重试 :先让模型完成推理或表达,再解析;解析失败时重新生成。
后者可能减少格式压力,但会增加重试、延迟和成本,而且并不自动解决幻觉。具体选择应使用真实任务集做评估,不能只根据某篇基准或单次演示下结论。参考资料中提到的 BAML、约束解码和格式化损失等研究结论,涉及模型、任务和实现版本,发布前应回到原始论文或官方报告核对。
一套更实际的三层防线
第一层:结构校验
使用 JSON Schema、Pydantic、Zod 或函数参数校验,拦截解析失败、缺字段、错误类型和非法枚举。记录失败样本,不要只自动重试后丢弃原始输出。
第二层:语义校验
把可明确表达的业务逻辑写成跨字段规则,同时增加统计监控和抽样审计。重点不是把所有判断都硬编码,而是先覆盖代价最高、最容易静默写入下游的字段。
第三层:不确定性与复核
在高风险字段旁边保留 confidence、evidence、needs_review 或可为空的结果。对低置信度、缺少证据、跨字段冲突和分布异常的记录,转入二次模型检查或人工审核。
二次模型也会犯错,因此复核提示中应要求它引用输入证据,输出"支持、反驳、无法判断"这类有限结论,而不是重新自由生成一份答案。对于金额、身份、风控和合规字段,仍应保留确定性程序或人工流程。
上线前可以执行的检查清单
1.解析成功率之外,是否有字段级准确率或人工抽检结果?
2.必填字段是否为"不确定"保留了 null 或拒答路径?
3.是否存在跨字段规则,以及规则失败后的处理方式?
4.枚举、数值、数组长度的历史分布是否被记录?
5.空数组率是否与离线标注集大致一致?
6.输入为空、含噪、冲突或超出范围时,系统是否会降级?
7.模型、提示词、Schema 或采样参数变化后,是否会重新跑回归集?
8.高风险结果是否能追溯到原文证据和模型版本?
如果这个流水线需要长期运行,日志、指标和样本留存比"接口返回 200"更重要。短期本地测试可以把重点放在规则和样本上;迁移到 Hostease 的 Linux VPS 环境后,还要补充进程守护、权限、密钥保护、日志轮转和告警。
结论:把 Schema 当作地板,而不是验收报告
结构化输出解决了"程序能不能稳定读到结果",但没有替你完成事实核验。枚举误判、无依据填充、跨字段矛盾、分布塌缩和数组幻觉,都可能在合法 JSON 中出现。
比较稳妥的工程路径是:先用 Schema 保证结构,再用业务规则检查关系,用统计监控发现整体漂移,最后为高风险字段增加证据和复核。这样做不会让模型永远正确,却能把最隐蔽的错误从数据库和自动化流程前面拦下来。
如果只是做概念验证,第一层通常已经够用;如果结果会触发付款、封禁、告警、合同处理或对外发布,就不要把"校验通过"当成"事实成立"。