一份"用表格列出行程安排"的输出被要求后,模型给出了漂亮的三列表格,但漏掉了"时区"这一列——因为任务要求里只写"列出时间与地点",而它不知道跨城市需要时区。这不是理解错误,而是提示没有把"表结构"说清楚。
结构化输出(表格、字段、列表)是提示工程里最容易被"想当然"的部分:人看到"表格"会补上缺失的列,而模型只会按你说的列。这个差别有个直接后果:"表没列全"在人看来是"信息不足",在模型看来是"任务已满足"。所以"漏一列"不会被它当成错误,也不会主动提示。
所以要它输出结构,就必须把结构写全。
1. 三种结构表达方式
| 方式 | 可控性 |
|---|---|
| 自然语言描述("用表格列出…") | 低 |
| 给出表头与示例行 | 中高 |
| 给 JSON Schema / 模板 | 高 |
| 让模型自己设计结构 | 最低 |
第一项最常用,而它的问题在"列数不确定":"列出时间、地点、备注"可能被理解成 3 列,也可能被合并成 2 列。第一项还有个"顺序"问题:列的先后可能被调整,而下游按位置解析时就会错位。所以"列顺序"也应当写进要求,或干脆用"带列名的解析"。
第三项的代价是"提示变长":Schema 占几百 token,而在"高频调用"里它是持续成本。所以"结构复杂"时用它,"结构简单"时用示例行就够了。
第三项最稳:字段名、类型、必填性都写清,于是"漏列"变成结构上的不可能。
第四项在"探索"阶段有用(看模型怎么组织信息),但不能用于生产——因为结构会随每次调用漂移。
2. 表头为什么要给"示例行"
| 给出内容 | 模型行为 |
|---|---|
| 只有列名 | 可能省略"空值"的列 |
| 列名 + 一行示例 | 更稳定地保留所有列 |
| 列名 + 示例 + 标注"必须全部输出" | 最稳 |
第一项的问题很具体:当某列在某行没有值时,模型可能整列不输出(因为"这一行用不上它")。第一项还有个"合并"版本:把"地点"与"时区"合并成一格("上海(UTC+8)"),看起来合理,但下游按列取时就拿不到时区。
第二项解决了这个:示例行里有值,它就有了"这列应该有"的证据。
// 提示里的表结构(示例行是关键)
请输出如下格式的 Markdown 表格,四列必须全部出现(缺失值写 —):
| 时间 | 地点 | 事项 | 时区 |
|---|---|---|---|
| 09:00 | 上海虹桥 | 出发 | Asia/Shanghai |
| 11:30 | 北京首都 | 到达 | Asia/Shanghai |
规则:跨时区行程必须在"时区"列标注;缺失值写 "—",不要省略该列。
3. 结构的三种常见失效
| 失效 | 表现 |
|---|---|
| 漏列 | 某列整列不出现 |
| 合并列 | "地点/时区"写在一格 |
| 层次丢失 | 嵌套信息被拍平 |
第三项还有个"表格套表格"的坏做法:用 Markdown 表格嵌在单元格里,而多数解析器不支持——于是"看起来对、程序读不了"。
第三项在"多层信息"上常见:"行程包含多段交通"被拍平成一行,于是"换乘关系"丢了。
对策是"显式声明嵌套":要么用两级表格,要么在同一格里用固定分隔符(如 A→B→C),并说明规则。
// 层次丢失的两种修法
1 两级:外层"天",内层"行程段"(两个表格 + 关联键)
2 单格:交通列写 "SHA→PEK→SZX",并在规则里说明"多个航段用 → 连接"
// 关键:选一种并写清,别让模型自己决定
4. 长表格与"截断"
| 风险 | 说明 |
|---|---|
| 行数太多被截断 | 输出上限 |
| 中途改格式 | 后半段少了列 |
| 重复表头 | 解析失败 |
第一项在"要列 200 行"时必然发生:模型会在某个点停下,而它不会说"我截断了"。第二项(中途改格式)在"长表格"里很常见:前 20 行严格四列,后面开始省略空格,而 Markdown 里"少一个 |"就变成三列。
第二项在长输出里很常见:前 20 行是四列,后 20 行变成三列,于是解析器报错。
对策是"分批":要求它每次输出 20 行并给出"是否还有"的标记,由调用方循环。这样既避免截断,也让"格式漂移"只影响一批。
// 分批输出的约定
每批最多 20 行;输出末尾附 "CONTINUE: yes/no";
下一批请求带上 "从第 N 行继续"。
// 收益:避免截断;代价:多次调用
"提示里的表结构"还有一个常被忽略的配套:在提示末尾重复一次"必须全部列"。因为长提示的中间部分容易被"注意力"稀释,而结尾是最常被遵循的位置。
// 结尾重申(三行,成本很低)
再次确认输出要求:
1 四列全部出现(时间/地点/事项/时区),不允许合并
2 缺失值写 "—",不允许省略列
3 只输出表格,不要额外解释
5. 结构化的验收
| 验收项 | 判据 |
|---|---|
| 列数 | 与要求相等 |
| 每行列数 | 与表头相等 |
| 必填列非空 | 不允许"整列缺失" |
| 类型正确 | 时间可解析、数字可转换 |
第二项的排查方式很直接:用脚本按 | 切分每行并比长度。一行代码就能发现"哪一行少了一列",比人眼逐行看快得多。
第二项最容易被忽略:表头四列、某行只有三列,而 Markdown 渲染时"看起来还行"——于是人眼通过,解析器失败。
所以验收应当是程序化的:解析表格、比列数、校验类型,而不是"看一眼觉得对"。
"结构漂移"可以用一个很便宜的回归测试监控:同一批 10 条输入跑 3 次,比对"列数与列名"。这不需要标准答案,只需要"结构一致"。
// 结构回归(不需要人工标注)
输入:10 条真实样例
断言:3 次运行的列名集合完全相同;每行列数 = 表头列数
失败即告警:说明提示或模型版本变了
6. 排错:五种"结构输出不稳定"
| 现象 | 成因 | 处理方向 |
|---|---|---|
| 经常少一列 | 只给了列名 | 加示例行 |
| 格式前后不一致 | 输出太长 | 分批 |
| 嵌套被拍平 | 未声明层次 | 显式规则 |
| 解析器报错 | 行内列数不等 | 程序化验收 |
| 每次结构都不同 | 让模型自设计 | 给模板 |
第五项在"探索期"看起来很好(模型设计得比你想的还全),但它不可用于生产第四项(行内列数不等)还会"传染":解析失败后若容错处理(缺列补空),于是"数据错位"被静默接受,而它比"报错"更难查。
:结构漂移会让下游解析全部失效。
最后一条"成本"视角:结构化要求会降低"内容质量"。因为模型会把注意力放在"格式对不对"上,而"内容是否周全"可能被牺牲。所以验收要同时看两件事:结构合格率与内容抽检——只看前者会得到一个"格式完美但内容变差"的系统。
// 两个验收维度(缺一不可)
结构:列数/列名/类型 的程序化断言(覆盖 100% 输出)
内容:每周抽检 20 条,人工判定"信息是否完整、是否有编造"
// 反例:只测结构 → 模型学会"输出漂亮空表"
7. 边界:什么情况不必强求结构
| 场景 | 为什么 | 做法 |
|---|---|---|
| 给人读的摘要 | 人可容错 | 自然语言 |
| 一次性探索 | 结构不重要 | 让模型自由 |
| 单字段输出 | 无结构可言 | 直接要求 |
| 已有强后置解析(容错) | 解析器能吸收差异 | 宽松要求 |
最后一条与"版本"有关的边界:结构一旦被程序依赖,它就是接口。改列名/加列应当按"接口演进"对待(兼容期 + 弃用),而不是"改个提示就行"。
判断口径一句话:这个输出会不会被程序解析。会,就必须给模板 + 程序化验收;给人读,结构可以宽。
8. 自测
- 四种结构表达方式里,哪一种"列数不确定"?为什么它最常用却最不可控?
- 为什么要给"示例行"?没有值的那一列会发生什么?
- 三种失效里,"层次丢失"怎么修?两种修法各适合什么?
- 长表格为什么必然被截断?"分批输出"如何同时解决截断与格式漂移?
- 为什么"行内列数不等"人眼看不出来?验收该怎么做?
- 判断"是否需要强求结构"的那个问题是什么?