结构化输出怎么保证模型按格式返回?先把“保证”拆开:一是响应能被解析,二是字段、类型和枚举符合约定,三是字段值在业务上正确。这三层不能只靠一句“请严格返回 JSON”同时解决。工程上要把模型生成、格式约束、服务端校验和失败处理连成一条链路。
为什么提示词写了格式,还是会出错?
让模型把客服工单归类为“故障、账单、其他”,并返回优先级和摘要。即使提示词给了示例,模型仍可能加上解释文字、漏字段、把优先级写成“紧急”、输出不完整 JSON,或者给出格式正确但分类错误的结果。提示词描述的是期望行为,不是应用程序的类型系统;采样、上下文变化、拒答和输出截断都可能影响结果。
先定义一个最小契约:
{
"category": "bug",
"priority": "high",
"summary": "登录后页面持续报错",
"needsHuman": true
}
字段名、类型、允许值、必填项和额外字段的处理方式都要明确。生产场景还需要版本号或清晰的接口版本管理,避免修改字段后下游仍按旧结构解析。
三种控制手段,强度不同
| 手段 | 能解决什么 | 仍需注意什么 |
|---|---|---|
| 提示词加示例 | 让模型理解任务和期望结构 | 不能保证合法 JSON 或固定字段 |
| JSON 模式 | 通常约束输出为可解析 JSON | 合法 JSON 仍可能缺字段、类型错误或语义错误 |
| Schema 约束输出 / 工具参数 | 在支持的模型与接口中约束字段和类型 | 支持的 Schema 子集、拒答、截断和语义正确性仍需处理 |
“结构化输出”在不同服务商的 API 中含义并不完全相同。有的通过受约束解码限制生成的 token,有的把结果放在工具调用参数里。具体是否支持严格模式、哪些 JSON Schema 关键字有效,要以所用模型和接口文档为准。即使用了严格模式,也只应把完整且成功的响应送去解析;拒答、超时、达到输出上限或工具调用未完成,都不是一条可用业务记录。
先设计 Schema,再写提示词
以工单分类为例,可以定义对象必填字段、枚举和禁止多余属性:
{
"type": "object",
"properties": {
"category": { "type": "string", "enum": ["bug", "billing", "other"] },
"priority": { "type": "string", "enum": ["low", "medium", "high"] },
"summary": { "type": "string" },
"needsHuman": { "type": "boolean" }
},
"required": ["category", "priority", "summary", "needsHuman"],
"additionalProperties": false
}
Schema 应尽量小:只要下游真会用到的字段。可选字段要统一缺失值的表示,别让一部分调用省略字段、另一部分返回空字符串或 null。枚举比自由文本更适合程序分支。摘要长度、日期格式等约束若供应商不支持,就放在服务端验证。
提示词负责说清判断标准,而不是重复一大段 JSON。例如写明:何时归入 billing,什么情况必须标记 needsHuman,以及证据不足时如何选择 other。输入的工单正文是待分析数据,不能让其中“忽略以上规则”之类的话覆盖系统规则。
服务端必须再验一次
模型返回后,按“响应完成 → JSON 解析 → Schema 校验 → 业务校验”的顺序处理。下面的 TypeScript 示例使用 Zod 演示应用侧校验;它独立于具体模型 SDK:
import { z } from 'zod'
const Ticket = z.object({
category: z.enum(['bug', 'billing', 'other']),
priority: z.enum(['low', 'medium', 'high']),
summary: z.string().min(1).max(120),
needsHuman: z.boolean(),
}).strict()
type Ticket = z.infer<typeof Ticket>
function parseTicket(raw: string): Ticket {
const json: unknown = JSON.parse(raw)
return Ticket.parse(json)
}
JSON.parse 可能抛语法错误,Ticket.parse 可能抛校验错误;调用方应捕获并记录错误类别,不能把失败当成空对象继续写库。这里的 Zod 规则也说明了一件事:模型接口接受的 Schema 与应用侧校验器可以各司其职,前者减少无效生成,后者守住自己的数据边界。
通过类型校验仍不代表内容为真。比如“无法登录”被分到 billing,或模型凭空写出未发生的退款承诺,都属于业务错误。对高影响字段增加规则检查、原文证据定位或人工复核;金额、权限和实际执行动作尤其不能只凭模型自由生成的值决定。
失败时怎么处理?
不要无限重试。一个实用策略是:对可恢复的格式错误或临时故障重试一到两次,向模型提供简短的校验错误;对拒答、持续失败或超出预算的请求,进入人工处理或明确的失败状态。重试要有超时、次数上限和成本上限,写库或触发动作前要考虑幂等性。不能悄悄把“解析失败”改成默认的 high 或 other,因为这会把系统故障混进正常业务数据。
流式输出时,传输中的片段可能不是完整 JSON。应等到响应完成并确认结束原因后再解析和校验;若需要逐步展示,可把预览和正式结果分开。日志记录请求 ID、模型版本、Schema 版本、校验错误与重试次数,避免把客户原文或敏感数据无必要地写进日志。
最后用一批真实样本做回归测试:统计解析成功率、Schema 通过率、业务准确率、人工接管率和平均重试成本。前两个指标只能说明“长得像正确数据”,第三个才更接近“做对了事”。结构化输出的可靠性来自多层约束和可观测的失败处理,而不是相信模型每次都会听话。
评论 0
暂无评论,来说两句吧。