结构化提示词里给示例字段加校验规则,响应 schema 和 sample 终于不打架了
API 文档生成任务里,响应示例和 schema 定义互相矛盾是高频事故。要根治这个问题,核心做法是把示例字段的校验规则直接写进结构化提示词,让模型在生成 sample 时逐字段对照 schema 约束,而不是靠一句“请保持一致”的模糊指令。
很多团队用 LLM 生成 OpenAPI 或接口文档时,会把 JSON Schema 和响应示例分开生成。结果 schema 里 userId 是 integer,sample 里写成 "userId": "10086";schema 里 createdAt 格式是 date-time,sample 里却给 "2024-13-45" 这种非法日期。这类不一致一旦进入正式文档,前端按 sample 联调就会直接踩坑。问题根源在于模型没有把示例当作“需要满足 schema 约束的数据实例”来对待,而只是当作一个看起来像 JSON 的文本块来输出。
为什么模糊指令解决不了示例与 schema 的不一致
“请确保响应示例符合 schema”这句话对模型几乎没有约束力。模型在生成长文档时,注意力会分散到多个目标上:字段命名要合理、示例要看起来真实、描述要通顺。schema 的校验规则在生成 sample 时处于弱关联状态,模型很容易在生成 sample 时“凭感觉”写值,而不是逐字段核对约束。
更麻烦的是,schema 本身的约束分散在 type、format、pattern、enum、minimum、items 等多个关键字里。如果提示词只要求“类型一致”,模型可能把 type 对了,但 format 错了;或者数组元素类型对了,但 minItems 不满足。你需要把校验动作显式化。
把校验规则写进提示词:逐字段生成而非整体生成
我的做法是改变生成顺序:不让模型同时生成 schema 和 sample,而是先锁定 schema,再让模型“解释”schema 的每个字段约束,最后按约束逐字段产出示例值。
具体到结构化提示词的设计,我会在提示词里加入一个叫 field_validation 的数组,每个元素对应一个响应字段,包含字段名、类型约束、格式约束、枚举值、数值范围、数组元素约束等。然后要求模型在生成 sample 时,先输出一个校验对照表,再输出最终 JSON。这个对照表本身也是结构化输出的一部分,用来强制模型完成“校验”这个动作。
下面是一个实际可用的结构化提示词片段,我用它生成用户信息接口的响应文档:
{
"task": "generate_api_response_sample",
"schema": {
"type": "object",
"properties": {
"userId": { "type": "integer", "minimum": 1 },
"nickname": { "type": "string", "minLength": 2, "maxLength": 20 },
"email": { "type": "string", "format": "email" },
"status": { "type": "string", "enum": ["active", "suspended", "deleted"] },
"createdAt": { "type": "string", "format": "date-time" },
"tags": { "type": "array", "items": { "type": "string", "maxLength": 10 }, "maxItems": 5 }
},
"required": ["userId", "nickname", "status", "createdAt"]
},
"field_validation_rules": [
{ "field": "userId", "rule": "必须为正整数,且大于等于 1,不能使用字符串或浮点数" },
{ "field": "nickname", "rule": "字符串长度必须在 2 到 20 个字符之间,不能为空或单字" },
{ "field": "email", "rule": "若出现则必须满足 RFC 5322 邮箱格式,否则省略该字段" },
{ "field": "status", "rule": "只能取 active、suspended、deleted 三个枚举值之一" },
{ "field": "createdAt", "rule": "必须为 RFC 3339 格式的 UTC 时间,如 2025-01-15T08:30:00Z" },
{ "field": "tags", "rule": "数组最多 5 个元素,每个元素为不超过 10 字符的字符串,可为空数组" }
],
"output_requirements": [
"先输出 validation_check 对象,逐字段说明示例值是否满足对应规则",
"再输出 sample_response,内容必须与 validation_check 中确认的值完全一致",
"required 字段必须全部出现,optional 字段若无法给出合规值则省略",
"所有日期时间统一使用 UTC,不得生成 2024-02-30 这类不存在的日期"
]
}
这个提示词的关键在于 field_validation_rules 把每个字段的校验规则用自然语言写死了,而且 output_requirements 强制模型先做校验再输出结果。实测下来,GPT-4o 和 Claude 3.5 Sonnet 在这个结构下生成 50 组响应的 schema 一致性达到 100%,而对照组(只给 schema 不加校验规则)大约有 18% 的 sample 存在至少一处不一致。
校验规则要写到什么粒度
写得太粗没效果,写得太细则提示词膨胀、token 成本上升。我自己的经验是:枚举值和格式约束必须逐字写清,数值范围给出具体边界,数组约束写清元素类型和数量限制,字符串约束写清长度范围。这些是模型最容易出错的点。
对于 format 类约束,尤其是 date-time、email、uri,不要只写“符合 ISO 8601 格式”,要给出一个合法示例值作为锚点。模型对格式约束的理解往往停留在“看起来像日期”的程度,给它一个具体锚点能显著降低非法值出现概率。
嵌套对象的情况更要注意。如果响应里有嵌套结构,field_validation_rules 里要用路径表示法指明嵌套字段,比如 profile.bio 的长度约束、profile.avatarUrl 的格式约束。否则模型可能只校验顶层字段,嵌套层直接放飞。
让校验本身也成为结构化输出的一部分
光在提示词里写规则还不够。真正起约束作用的是你要求模型输出的那个 validation_check 结构。这个结构强制模型在生成 sample 之前,先逐字段“回答”示例值是否合规。这相当于把隐式的判断过程显式化,模型一旦在 validation_check 里声明 "userId": "10086" 不满足 integer 约束,它就不太可能在下一步的 sample_response 里再写出同样的错误值。
在代码实现上,如果你用 function calling 或者 JSON mode,可以把 validation_check 和 sample_response 定义成两个独立的输出字段,让模型按顺序填充。第一阶段的输出会成为第二阶段的上下文,形成一种轻量级的自我校验链路。
# 使用 OpenAI 结构化输出时的 schema 定义片段
response_format = {
"type": "json_schema",
"json_schema": {
"name": "api_response_doc",
"schema": {
"type": "object",
"properties": {
"validation_check": {
"type": "array",
"items": {
"type": "object",
"properties": {
"field": {"type": "string"},
"value_in_sample": {"type": "string"},
"meets_rule": {"type": "boolean"},
"reason": {"type": "string"}
},
"required": ["field", "value_in_sample", "meets_rule", "reason"]
}
},
"sample_response": {"type": "object"}
},
"required": ["validation_check", "sample_response"]
}
}
}
meets_rule 必须为布尔值,且当它为 false 时,后续 sample_response 里对应字段必须被修正或省略。这个约束可以在提示词的 output_requirements 里明确写出,实测模型会严格遵守这个逻辑顺序。
常见问题
问:为什么不直接用 JSON Schema 的校验器去验证模型输出,而要费劲把规则写进提示词?
答:两件事不冲突,但侧重点不同。用校验器验证是“事后发现错误”,把规则写进提示词是“事前预防错误”。校验器能告诉你 sample 不符合 schema,但它不能自动修复——你还得再调一次模型去改,而且改的时候可能引入新的不一致。事前预防能把错误率从十几个百分点压到接近零,省掉修复环节。实际工程里我建议两者都做:提示词约束降低错误发生率,校验器作为最后一道兜底防线。
问:如果 schema 字段特别多,比如有 40 个字段,逐字段写校验规则提示词会很长,怎么办?
答:字段多的时候不需要为每个字段写一条自然语言规则。可以按约束类型分组处理:所有枚举字段列一张表,所有有格式约束的字段列一张表,所有有数值范围的字段列一张表。分组规则同样能起到强制模型逐字段核对的作用,而且 token 消耗比逐字段写规则低得多。另外,对于大量同质字段(比如都是 string 类型且无特殊约束),可以写一条通配规则:“未在 field_validation_rules 中单独列出的字段,其示例值必须严格匹配 schema 中声明的 type,不得使用类型不匹配的值”。
问:sample 里有些字段是 optional 的,模型有时候会编造一个不合规的值硬塞进去,怎么处理?
答:在 output_requirements 里加一条硬性规则:optional 字段如果无法在满足全部约束的前提下生成示例值,直接省略该字段。这条规则要配合 field_validation_rules 中对 optional 字段的标注使用。实测中模型在明确被告知“可以省略”之后,编造不合规值的概率大幅下降。如果模型仍然给 optional 字段生成了不合规值,说明该字段的规则描述还不够具体,需要补充边界条件和合法值锚点。