Prompt 输出契约应该如何设计?
简化版
输出契约要明确字段、类型、必填项、枚举、空值语义、错误状态和版本,而不只是给一个 JSON 示例。机器消费时优先使用 Structured Outputs/JSON Schema 约束解码,Prompt 解释业务语义,服务端再做 schema 与业务校验;失败要有限重试并保留原始输出,不能靠正则“修”出看似合法的错误数据。
详细版
先从下游调用方反推 schema:哪些字段必需、可缺失和可为 null,数字单位与时间格式是什么,证据如何引用,证据不足/拒绝/系统错误怎样区分。为契约加 schema_version,使用稳定 machine-readable 枚举,不把本地化文案当状态码。
Prompt 给字段语义和一两个边界例,API 能力负责语法约束;解析后执行范围、交叉字段和权限校验。例如 end >= start、金额币种存在、citation ID 属于已提供证据。验证失败只针对错误字段重试 1~2 次,仍失败则返回明确错误或人工处理,并监控各字段失败率。
Prompt语义 -> 约束解码 -> JSON解析 -> Schema校验 -> 业务不变量 -> 下游执行
完整版教学
一、契约服务的是调用方
结构化输出不是为了让回答看起来整齐,而是让下游程序可靠解释。设计要从业务动作反推:调用方需要哪些字段,缺失时能否继续,哪些值会触发不可逆操作。未被使用的字段只增加生成和维护成本。
契约还定义失败。模型“不知道”、安全拒绝、无权限和系统故障语义不同,不能都用空字符串。明确状态能让调用方选择追问、重试、转人工或终止。
二、示例不等于 Schema
一个 JSON 示例只展示一种合法实例,无法说明字段是否必填、可否为 null、字符串有哪些枚举。JSON Schema 能表达这些边界,并由程序验证。描述字段业务语义仍需 Prompt 或文档,两者互补。
{
"type": "object",
"required": ["schema_version", "status", "items"],
"properties": {
"schema_version": {"const": "1.0"},
"status": {"enum": ["ok", "insufficient_evidence", "denied"]},
"items": {"type": "array", "maxItems": 20}
},
"additionalProperties": false
}
additionalProperties:false 可防模型悄悄新增下游不认识的字段,但升级时需显式改版本。
记忆钩子:Schema 管“长什么样”,Prompt 管“每个字段是什么意思”,业务校验管“这些值能不能用”。
三、空值与缺失要怎么定义
字段缺失、null、空字符串和空数组应有不同语义,否则调用方会猜。可规定:不适用则字段缺失,应该有但证据不足则 null,集合没有成员则 []。不过越复杂越容易误用,很多场景更适合统一 status 加 errors。
| 状态 | 建议表达 | 下游动作 |
|---|---|---|
| 成功有结果 | status=ok | 使用数据 |
| 证据不足 | insufficient_evidence | 补充资料 |
| 权限不足 | denied | 不重试,提示权限 |
| 临时工具失败 | system_error | 有限重试 |
| 无匹配项 | ok, items=[] | 正常空结果 |
错误信息可以本地化,状态码必须稳定。
四、格式、范围与业务不变量分层
Schema 能验证 number、date-time、枚举和长度,却不一定表达所有关系。例如开始时间不得晚于结束时间,折扣价不得高于原价,citation 必须引用本次上下文。这些由业务校验器处理,不能因 JSON 可解析就信任内容。
金额应使用 {amount: "12.50", currency:"CNY"} 或明确最小单位整数,避免浮点与币种混淆。日期含时区或明确仅为日历日期。ID 作为字符串,防前导零丢失。契约越精确,下游补猜越少。
五、约束解码与 Prompt 各做什么
JSON mode 通常保证有效 JSON,不保证符合具体 schema;Structured Outputs 或 grammar constrained decoding 能限制字段和类型,但仍不保证事实正确。Prompt 应描述来源、选择条件和失败语义,不必反复强调括号与引号。
若平台不支持约束解码,可要求只输出 JSON 并做强校验,但失败率更高。不要从 Markdown code fence 中用宽松正则截取第一个花括号,这会隐藏额外文本和嵌套问题。使用真正 JSON parser。
六、重试和修复怎样安全
验证失败时把精确错误反馈给模型,例如“items[2].quantity 应为正整数”,并带原始结构重试;最多 1~2 次,避免循环。语法修复不能自动补造业务值。若证据字段缺失,应返回不足,而不是用默认 0 让校验通过。
高风险动作在校验后仍需权限和用户确认。保存原始输出、校验错误和修复版本,便于审计。缓存只存通过对应 schema_version 校验的结果。
七、版本演进如何兼容
增加 optional 字段通常可向后兼容,删除/改名/改变语义则需新主版本。下游声明支持版本,服务端可做 adapter,不要让模型同时猜多个 schema。Prompt、schema、解析器和测试应在同一发布清单中。
为 v1 准备 golden examples 和 property-based 边界测试,包括最大数组、Unicode、null、未知枚举和恶意字符串。v2 上线时双写或 shadow 验证,再迁移消费者。版本号不应由模型自由选择,而由调用方固定。
八、常见误区与追问
- 误区:给一个 JSON 示例就有输出契约。 示例未定义必填、枚举、空值和额外字段。
- 误区:JSON 可解析就可以执行。 还需 schema、业务不变量、权限和证据校验。
- 误区:解析失败可以用正则自动补齐。 宽松修复可能制造错误字段并掩盖根因。
- 追问:JSON mode 与 Structured Outputs 区别? 前者偏语法合法,后者按具体 schema 约束,具体能力以平台实现为准。
- 追问:如何表达模型不知道? 用稳定 status 和缺失原因,不要用空字符串冒充答案。
- 追问:重试几次? 通常有限 1~2 次并针对验证错误,超过后显式失败或转人工。
九、加强记忆
输出契约可记成“形、义、验、错、版”:Schema 定形,Prompt 定字段语义,程序验证业务不变量,失败状态和有限重试要明确,版本演进要兼容。结构化输出降低的是语法不确定性,不会自动保证事实与权限;真正可靠要走完解析、校验和执行门禁。