大厂 Agent 面试|明明让它返回 JSON,为什么程序还是报错?讲透结构化输出
“请严格返回 JSON,不要输出其他内容。”
这句话很多开发者都写过,但程序照样可能遇到多余说明、字段缺失、类型错误,甚至结构完全正确、内容却不符合业务要求的结果。
本文以订单查询助手为例,讲清合法 JSON、符合 Schema 和业务正确三个层次,以及提示词约束、原生结构化输出、工具调用和应用端校验分别承担什么职责。
一、模型觉得自己答得很好,程序却直接报错
你想让模型把用户需求转成一个对象,结果它返回:
好的,以下是你需要的 JSON:
{"order_id": "A1023", "intent": "query_shipping"}
人一眼就能看懂,但如果程序把整段文本交给 JSON 解析器,前面那句“好的”就可能导致解析失败。
你继续强调“不要解释”,模型终于只返回对象了,却把字段名改成了 orderId。
程序仍然拿不到预期的 order_id。
好不容易字段名也对了,模型又填了一个用户根本没提供过的订单号。
这就像收集报名表:有人交作文,有人把年龄写进姓名栏,还有人每个格子都填满了,但内容是编的。
让结果能被程序使用,需要同时解决格式、结构和内容问题。
二、三个层次一定要分清
| 层次 | 检查什么? | 典型问题 |
|---|---|---|
| JSON 语法 | 能不能解析成合法 JSON? | 多余说明、引号错误、内容截断 |
| Schema 结构 | 字段、类型、枚举是否符合约定? | 缺字段、类型错误、出现未知状态 |
| 业务语义 | 内容是否符合用户输入和业务规则? | 编造订单号、查错订单、权限不符 |
下面这段数据可能是合法 JSON:
{
"order_id": 1023,
"intent": "随便查一下"
}
但如果接口约定订单号是字符串,意图只能取固定枚举,它就不符合结构要求。
再看这一段:
{
"order_id": "A9999",
"intent": "query_shipping"
}
字段和类型都可能正确。但用户说的是 A1023,它仍然是错误结果。
JSON 能解析,不等于字段符合要求;字段符合要求,也不等于业务正确。
三、什么是结构化输出?把“自由作文”变成“按表填报”
结构化输出,是让模型按预先约定的数据结构返回结果,使程序能够稳定地读取字段并继续处理。
这个约定常用 JSON Schema、类型定义或数据模型表达。
例如,订单查询助手可以设计一份表格:
| 字段 | 类型 | 用途 |
|---|---|---|
status | 枚举字符串 | 可继续处理、需澄清或不支持 |
intent | 枚举字符串 | 查询物流、查询退款或未知 |
order_id | 字符串或 null | 从用户提供的信息中提取订单号 |
missing_fields | 字符串数组 | 还缺少哪些必要信息 |
其中 status 的“可继续处理”只表示提取阶段的信息条件满足,不表示已经查到订单,更不表示已经通过权限校验。
LangChain 的官方文档将结构化输出描述为返回可预测格式的数据,并提供模型提供方原生策略与工具调用策略等实现方式。
四、四种常见做法,区别在哪里?
| 做法 | 主要机制 | 能解决什么 | 仍需处理什么 |
|---|---|---|---|
| 提示词要求返回 JSON | 用文字与示例约束模型 | 快速约定输出风格 | 模型仍可能偏离格式 |
| JSON 模式 | 在支持的平台上约束 JSON 输出 | 提高 JSON 格式可靠性 | 不一定严格符合业务 Schema |
| 原生结构化输出 | 按接口支持的 Schema 约束生成 | 提高字段与结构一致性 | 支持范围、异常结束、语义错误 |
| 工具调用形式 | 用工具参数承载结果 | 与工具接口或框架流程衔接 | 严格程度取决于接口与配置 |
这些名称的具体行为与模型、平台和版本有关,不能只看到“JSON”或“strict”就认为所有要求都得到了保证。
原生结构化输出为什么更稳定?
可以用填表来理解:
提示词约束像在纸上写“请按格式填写”;约束生成则像给输入过程设置了格式限制,某些不符合结构的内容无法按普通自由输出的方式产生。
例如,Claude 官方文档介绍了把 Schema 编译为语法约束的方式。
这种机制主要帮助控制结构。
它不会因为订单号字段必须是字符串,就自动知道用户真正要查的是哪个订单。
工具调用是不是等于执行了业务操作?
也不是。
模型输出一组工具参数,首先只是形成了调用请求或结构化数据。是否实际执行查询、写库、退款等操作,由应用的后续逻辑决定。
有些框架借用工具调用机制收集结构化结果,这个“输出工具”本身不需要产生业务副作用。
五、完整案例:把用户的话转换成可处理的请求
用户说:
帮我查一下订单 A1023 的物流到哪了。
在下面这个示例设计中,期望输出为:
{
"status": "ready",
"intent": "query_shipping",
"order_id": "A1023",
"missing_fields": []
}
应用可以按以下顺序处理:
-
确认模型响应是否正常、完整结束。
-
解析结果并校验结构。
-
检查
ready状态下是否真的存在订单号和受支持的意图。 -
根据登录身份查询订单归属与访问权限。
-
在校验通过后调用物流查询接口。
这里的用户身份、租户和实际权限应来自可信的会话与服务端系统,不能让模型随意填写一个 authorized: true 就算通过。
用户没给订单号,怎么办?
用户只说:
查一下我的物流。
如果上下文和已授权数据中也没有足够信息,可以返回:
{
"status": "needs_clarification",
"intent": "query_shipping",
"order_id": null,
"missing_fields": ["order_id"]
}
这样,前端可以明确地请求用户补充订单,而不是拿一个编造的订单号继续查询。
好的输出结构,会给“信息不足”留位置。
如果 Schema 强迫模型必须填写所有具体值,却没有表达未知的方式,就容易把模型推向猜测。
六、Schema 怎么设计,才能既好用又不添乱?
1. 用明确类型代替模糊描述
“数量”是整数还是字符串?“金额”是元还是分?“时间”采用什么时区?
这些需要提前约定。
在程序接口中,可以把金额设计成整数分,并明确币种;时间明确采用带时区的格式。
具体选型按业务要求决定,但不能让不同环节各自猜测单位。
2. 固定状态使用枚举
如果后端只识别 ready、needs_clarification、unsupported,就不要让模型自由创造“基本可以”“有点不确定”等状态名。
面向用户的解释可以自由一些,供程序分支判断的字段应尽量明确。
3. 区分字段缺失、null 和空字符串
| 表达 | 可以约定的含义 |
|---|---|
| 字段缺失 | 当前结构允许不提供该字段 |
null | 字段存在,但当前没有可用值 |
"" | 一个长度为零的字符串 |
[] | 一个没有元素的数组 |
这些值不会天然表达完全相同的业务含义。
例如,missing_fields: [] 表示没有待补充字段,而 order_id: "" 未必是有效订单号。
4. 限制不需要的额外字段
可以在适用的 Schema 中限制未知字段,避免模型额外生成一个后端没有定义的 execute_now 或 is_admin。
但关闭额外字段仍然只是结构控制,不会替代权限校验。
5. 跨字段规则放在哪里?
例如:
status为ready时,order_id必须有可用值,missing_fields必须为空。
这属于字段之间的联合约束。
部分规则可以通过支持的 Schema 表达,其他规则由应用校验器完成。
不要默认模型接口支持 JSON Schema 的全部功能。以 Claude 文档为例,原生结构化输出存在 Schema 支持范围限制。应根据实际接口能力生成约束,并在应用端补足验证。
七、返回 JSON 失败了,是不是让模型重试就行?
先区分失败原因,再选择处理方式。
| 失败类型 | 更合理的处理 |
|---|---|
| 多余文本或格式不符 | 优先改用更可靠的输出方式;必要时有限修复 |
| 字段类型或枚举错误 | 提供明确校验错误,进行有预算的重试 |
| 输出被长度上限截断 | 调整输出规模或预算,获取完整结果 |
| 用户信息不足 | 澄清或使用设计好的未知状态 |
| 模型拒答 | 进入对应响应分支 |
| 订单不存在或无权限 | 按业务结果处理,不要求模型“修好” |
原生结构化输出也需要识别异常结束。Claude 文档明确列出了拒答、达到输出长度上限等情况下的特殊处理需求。
尤其不能把“订单不存在”当成模型输出错误,让模型换一个订单号试到成功为止。
修复格式与修改事实,是两件完全不同的事。
能不能直接用正则截出大括号里面的内容?
对于边界明确的简单包装,应用可以进行严格、有限的预处理。
但“找到第一个左大括号和最后一个右大括号”无法可靠处理多个对象、嵌套文本、字符串中的括号等情况,也不能证明提取出来的对象就是预期结果。
即使成功解析,后面的结构和业务检查仍然需要完成。
八、流式 JSON,能不能边收到边执行?
通常应该把展示和执行分开。
一个尚未接收完整的 JSON 片段,可能缺少后续字段,也可能最终因为截断而失败。
前端可以使用适当的增量解析方式做预览,但订单查询、写库等后续操作应等待约定的完整结果通过校验。
如果业务采用明确的分块协议,可以逐块验证和处理;那需要提前定义每块的边界和语义,不是对任意半截 JSON 直接执行。
看起来像一个字段值,不代表它已经成为经过验证的业务输入。
九、怎样验证输出方案真的可靠?
不要只测试一句“帮我查订单 A1023”。
可以增加这些样本:
| 输入情况 | 希望看到的行为 |
|---|---|
| 订单号和意图都明确 | 正确提取,进入后续校验 |
| 缺少订单号 | 标记需澄清,不编造 |
| 同时出现两个订单号 | 按任务语义处理,必要时澄清 |
| 用户纠正先前订单号 | 使用有效的最新要求 |
| 请求不在支持范围内 | 返回明确的不支持状态 |
| 很长的文本输入 | 保持字段提取与输出边界正确 |
| 输出截断、拒答或接口异常 | 正确进入相应错误分支 |
评测时至少区分:
-
JSON 解析成功率。
-
Schema 校验通过率。
-
字段提取正确率。
-
端到端任务成功率。
同时记录拒答、截断和接口异常的比例,说明统计分母,避免只挑正常完成的响应来展示一个漂亮的格式通过率。
十、面试精简总结
我会把输出可靠性分成三层:合法 JSON、符合 Schema、满足业务语义。提示词可以帮助模型理解要求,但正式接口优先使用可用的原生结构化输出或适当的工具调用机制,再进行应用端校验。
Schema 需要定义类型、枚举、未知值和状态,避免为了填满字段而编造内容。拿到结果后先检查完成状态,再解析和校验结构,最后检查跨字段规则、资源归属及权限。
对格式错误可以有限修复,但信息不足应澄清,业务失败按真实结果处理,拒答和截断单独分支。结构化输出让数据更容易被程序处理,并不保证内容真实或操作已经获准。
一句话记忆:模型负责按表填写,程序负责验表,业务系统负责决定能不能办。
更多推荐



所有评论(0)