先区分格式合格与数据正确
结构化输出可以约束响应形状,但订单号、金额和备注是否来自原始材料,仍需要应用核对。JSON能解析、字段有值,只证明通过了某一层检查,不能据此把结果直接写入正式业务表。
本篇面向已经能发送Messages请求、正准备接入数据库的开发者。普通提示词如何约定输出另有专文;这里解决原始资料、API约束和入库门禁怎样配合。
先把原始材料变成可验收条件
虚构材料O1:订单A7,两项金额分别1200分、800分,没有提供备注。人工给定期望为订单号A7、总额2000分、备注null。分是本例唯一金额单位;不能把20元与2000分混在同一个字段里。
要求输出order_id字符串、total_fen整数、note字符串或null,三个字段都必填且不许额外字段。明确空值的含义,可以防止“无备注”被填成一个猜测的说明。
本站原创示意图:合法JSON还需事实验收。虚构订单;本地accepted不是正式入库授权。查看大图 ↗请求里放哪里,哪些条件留在应用层
当前官方原始请求形状是在output_config.format中设置type为json_schema并提供schema。文末buildOrderRequest(model)给出完整对象,不向服务器发送;模型ID由当前账户支持列表确定,不照抄文章中的旧版本号。
官方支持的JSON Schema有子集限制。这个请求只约束基本类型、必填字段和额外字段;非负整数、安全整数及与O1相等的金额由本地检查负责,不把服务器未支持的约束当作已经生效。
运行两个结果,观察业务门禁
将文末代码保存为structured-order-check.mjs,使用Node.js 22或24 LTS执行:node structured-order-check.mjs --demo。两行结果中,2000分的构造响应accepted为true,2100分的响应为false,原因是与原始订单不符。
两份输入均为人工构造的单文本响应,不是模型实测。2100仍是合法JSON与合法整数;因此它能演示“格式通过,事实失败”的差别。函数只适用于本例三个字段,不是通用Schema校验库。
异常路径先退出,不修补成看似合法
调用inspectOrder前应完成HTTP、认证、超时及响应解码检查。函数遇到非end_turn、无法解析JSON、字段缺失、多字段、类型异常或与人工给定原件不符时拒绝。
官方说明拒绝或max_tokens截断可能不符合结构约束。不能仅把截断的JSON补一个括号就宣布完整。保留任务标识、停止原因及脱敏错误,重试需受次数和预算限制。
入库前再加环境边界
真实系统还需要校验订单是否属于当前用户、币种、重复请求、数据库事务和人工确认;这些不在本例实现中。accepted表示通过这个给定样例的本地检查,不是授权扣款、发布文章或覆盖线上记录。
本地测试覆盖合法样例、错误金额、额外字段、错误类型、订单错配、拒绝与截断;没有使用真实密钥或付费API。上线前先用独立测试库验收,再接正式写入。
参考资料与核验范围
资料核对日期:2026年10月19日。以下为官方原始资料;产品入口和可用范围仍以当前账户为准。
1. [官方资料1](https://platform.claude.com/docs/en/build-with-claude/structured-outputs)
TRY THIS / 可复制模板
把方法变成一次练习
// 离线教学:不调用API,不替代通用JSON Schema验证器。
export function buildOrderRequest(model){
if(typeof model!=='string'||!model.trim())throw Error('需要当前账户支持结构化输出的模型ID');
return {model,max_tokens:512,messages:[{role:'user',content:'虚构订单A7:两项金额1200分与800分。提取order_id、total_fen、note;没有备注则note为null。'}],output_config:{format:{type:'json_schema',schema:{type:'object',properties:{order_id:{type:'string'},total_fen:{type:'integer'},note:{type:['string','null']}},required:['order_id','total_fen','note'],additionalProperties:false}}}};
}
export function inspectOrder(response,expected){
if(response?.stop_reason!=='end_turn')return {accepted:false,reason:'未自然结束'};
if(!Array.isArray(response.content)||response.content.length!==1||response.content[0].type!=='text')return {accepted:false,reason:'不是本例支持的单文本JSON'};
let data;try{data=JSON.parse(response.content[0].text);}catch{return {accepted:false,reason:'JSON语法无效'};}
if(!data||Array.isArray(data)||typeof data!=='object'||Object.keys(data).sort().join(',')!=='note,order_id,total_fen'||typeof data.order_id!=='string'||!Number.isSafeInteger(data.total_fen)||data.total_fen<0||!(data.note===null||typeof data.note==='string'))return {accepted:false,reason:'字段或类型无效'};
if(!expected||data.order_id!==expected.orderId||data.total_fen!==expected.totalFen||data.note!==(expected.note??null))return {accepted:false,reason:'与原始订单不符'};
return {accepted:true,data};
}
if(process.argv.includes('--demo')){
const expected={orderId:'A7',totalFen:2000};
for(const total of [2000,2100])console.log(JSON.stringify(inspectOrder({stop_reason:'end_turn',content:[{type:'text',text:JSON.stringify({order_id:'A7',total_fen:total,note:null})}]},expected)));
}第三方 AI 工具 · 邮箱注册 · 付费使用。模板为编辑设计,实际效果取决于输入与模型。