结构化输出解决一个老问题:模型说的话,程序读不懂。给一份 schema,它就得照着填,填不对就重来。 实测四份输出只过一份:缺字段、类型不对、枚举值超出范围。 而错误信息的一半价值在措辞——它要能指导下一轮。
上一节讲怎么拦,这一节讲怎么收:让它的输出变成程序能直接用的形状。 这一节真跑了一个最小校验器:四样关键字,四份输出,两份过不了。
一、schema 里最常用的四样
type:这是什么类型。对象、数组、字符串、数字。 required:哪些字段必须有。缺一个就整份不合格。 properties:每个字段长什么样。可以层层嵌套。
enum:只能是这几个值里挑一个。比自由文本好校验得多。 四样就能覆盖大部分场景。剩下的(正则、范围、嵌套引用)等真需要了再加。
二、真跑:四份输出逐个校验
演示里四份:一份字段齐全、一份缺 file、一份 issues 给了字符串、一份 severity 写了「严重」。 结果只过一份。另外三份的原因分别是:缺字段、类型不对、枚举值不在范围内。 这三种恰好是结构化输出最常出的三类错——不是「它不会写」,是「它按自己的理解写了」。
所以校验器不是可选件,它是「把自然语言压成字段」这件事的必要成本。
三、错误信息要能指导重试
「校验失败」这四个字没用。「$.file 缺字段」才有用——模型下一轮就知道补哪个。 所以校验器的价值一半在判断,一半在措辞。 这一点和 8.6 节那个参数校验是同一件事:错误信息是给模型看的指令,不是给人看的日志。
一个实用的检验办法:把错误信息给你同事看,他能不能直接改对?能,就够清楚。
四、重试要有上限
校验不过就重试,但必须设次数上限。否则会转成一个死循环:它每次都犯同一个错。 两次还不过,就该换做法:把 schema 简化、把任务拆小、或者干脆改成自由文本加人工看。 这条和 4.8 节那个「打回循环」是同一个坑,只是换了个位置。
更隐蔽的一种:它每次都「通过」了校验,但填的内容是空的——校验器管形状,不管内容。
五、什么时候别用结构化输出
给人看的结论、需要解释的判断,硬套 schema 会把它压成几个字段,信息反而少了。 判据:这份输出会被程序读吗?会,就上 schema;只是给人看,就别管形状。 还有一类要小心:字段太多。二十个字段的 schema,它填错的概率比十个字段高得多。
所以 schema 要小,宁可分两次调用,也别一次要二十个字段。
六、动手 Demo:四份输出按 schema 校验,两份过不了
结构化输出解决一个老问题:模型说的话,程序读不懂。 这里真跑一个最小校验器:type、required、properties、enum 四样关键字,四份输出逐个查。
SCHEMA = {"type": u"object", "required": [u"file", u"issues"],
"properties": {"file": {"type": u"string"},
"issues": {"type": u"array"},
"severity": {"type": u"string",
"enum": [u"high", u"mid", u"low"]}}}
实跑输出:
--------------------------------------------------------------------
四份输出按 schema 校验,两份过不了
--------------------------------------------------------------------
结构化输出解决一个老问题:模型说的话,程序读不懂。
给一份 schema,它就得照着填;填不对就重来。
① schema 里最常用的四样
--------------------------------------------------------------------
| 关键字 | 管什么 | 例子 |
|---|---|---|
| type | 这是什么类型 | 对象、数组、字符串、数字 |
| required | 哪些字段必须有 | 缺一个就整份不合格 |
| properties | 每个字段长什么样 | 可以层层嵌套 |
| enum | 只能是这几个值里挑一个 | 比自由文本好校验得多 |
四样就能覆盖大部分场景。剩下的(正则、范围、嵌套引用)等真需要了再加。
② 真跑:四份输出逐个校验
--------------------------------------------------------------------
| 这一份 | 它长什么样 | 结果 | 第一个问题 |
|---|---|:---:|---|
| 第 1 份 | 四个字段齐全 | 通过 | 格式全对 |
| 第 2 份 | 缺 file 字段 | 不通过 | $.file 缺字段 |
| 第 3 份 | issues 给了字符串 | 不通过 | $.issues 应该是数组 |
| 第 4 份 | severity 写了「严重」 | 不通过 | $.severity 只能是 high、mid、low |
四份里 1 份通过。三份不通过的原因各不相同:缺字段、类型不对、枚举值不在范围内。
③ 错误信息要能指导重试
--------------------------------------------------------------------
「校验失败」这四个字没用。「$.file 缺字段」才有用——模型下一轮就知道补哪个。
所以校验器的价值一半在判断,一半在措辞。
这一点和 8.6 节那个参数校验是同一件事:错误信息是给模型看的指令。
④ 重试要有上限
--------------------------------------------------------------------
校验不过就重试,但必须设次数上限。否则会转成一个死循环:它每次都犯同一个错。
两次还不过,就该换做法:把 schema 简化、或者把任务拆小。
这条和 4.8 节那个「打回循环」是同一个坑,只是换了个位置。
⑤ 什么时候别用结构化输出
--------------------------------------------------------------------
给人看的结论、需要解释的判断,硬套 schema 会把它压成几个字段,信息反而少了。
判据:这份输出会被程序读吗?会,就上 schema;只是给人看,就别管形状。
! 这里的校验器是最小实现,真实库支持的关键字多得多,也更严格。
①四样关键字够用:type(什么类型)、required(哪些必须有)、properties(每个字段长什么样)、 enum(只能是这几个值之一)。 ②四份只过一份,三种错分别是:缺字段、类型不对、枚举值超出范围。 这三类恰好是结构化输出最常出的错——不是「不会写」,是「按自己的理解写了」。 ③错误信息要能指导重试:「$.file 缺字段」比「校验失败」有用得多。 校验器的价值一半在判断,一半在措辞。 ④重试要有上限:两次还不过就该换做法(简化 schema、拆小任务、或者改回自由文本)。 更隐蔽的是「通过了但内容为空」——校验器管形状,不管内容。
30 秒实验
- 把 required 里的 file 删掉,重跑一遍。通过几份? 这说明「必填」这一项怎么定,直接决定校验的松紧。
- 给 issues 加一条「最多十项」的限制,重跑一遍。手写的校验器能查吗?要查得改哪儿?
- 把 schema 从四个字段扩到十个(随便加),再想一下:它填错的概率会怎么变?
常见误区
- 错误信息只写「校验失败」。模型不知道该改哪儿,重试还是错。
- 重试不设上限。它每次都犯同一个错,转成死循环。
- 以为通过了校验就对了。校验器管形状,不管内容是不是空的。
- schema 一次要二十个字段。填错的概率比十个字段高得多,宁可分两次。
- 给人看的结论也硬套 schema。信息被压成几个字段,反而少了。
自测三问(点开对答案)
1. schema 里最常用的四样关键字是什么? —— type、required、properties、enum。四样能覆盖大部分场景。 2. 为什么错误信息的措辞很重要? —— 因为它是给模型看的指令。「$.file 缺字段」能让它下一轮改对,「校验失败」只能让它瞎猜。 3. 重试为什么要设上限? —— 不设上限会转成死循环:它每次都犯同一个错。两次不过就该换做法。输出能读了,下一节把这些拼起来:做一个能被别人调用的服务。
- ☐ (本篇完,下一篇 8.10 实战:构建代码分析 Web 服务)
相关笔记
- 自定义工具:参数校验也是同一件事
- 实战注意事项:打回循环那个坑
- Claude Code 橙皮书 · 索引
- 🐎 Claude Code 实战 · 索引