大可的 AI Builder 手记
跟随系统
颜色主题
长沙小桌实战
大可 Dako
大可 Dako大可 DakoAI Builder · Agent 实践者用 AI 做真正能用的产品。专注探索 Agent 工程化落地、Vibe Coding 实践与商业化交付。
内容平台即将上线,敬请期待
三层叠加,不是三选一——程序要读的输出,别只靠第一层提示词里请求写清格式、附一个格式样例它是请求不是保证,剩下的那部分放到线上就是每天的失败数生成层面强制按给定的字段清单约束生成过程,不合语法的字符根本出不来只保证语法合法,不保证内容正确自己校验兜底重试、降级、明确拒绝,并把解析失败率做成指标这一层不能省,哪怕前两层都上了三层是叠加,不是三选一
格式控制有三层,可靠性递增。只要输出要被程序读,三层都要上。

输出格式

程序要解析的输出,靠「请返回 JSON」这种请求撑着,就是每天几百次失败。格式要一层层卡住,还要把解析失败率盯起来。

只要输出要被程序接住,格式就不再是审美问题,而是可用性问题:自由发挥的文本,对下游代码来说是一笔负债。「在提示词里说清楚格式」这一招多数时候能对——多数时候不够,因为它错的那一部分落到线上,就是每天几百次解析失败。

读完你能回答:

  • 把格式写进要求,和从生成那一层卡住,中间差了什么?
  • 格式被强制之后,什么被保证了,什么没有?
  • 输出既要给人看、又要给程序读,该怎么拆?

写在提示词里的要求,本质是一次请求

这是门槛最低的一层:把想要的格式描述清楚。有两个技巧能把命中率明显抬上去:附一份格式样例(比描述格式有效得多,等于把给例子的招式用在这里),以及明确说「除了结构本身,其余一个字都不要——开头第一个字符就得是它」。

但它终究是一次请求,不是一条保证。它本质上是在做概率延续:模型读过的语料里,把答案裹进一句寒暄、或者在结果外面套一层代码块围栏的写法太多,这些毛病就永远有概率冒出来。把这一层当唯一手段,等于接受一个固定的失败率。

在生成那一步拦,和事后检查不是一回事

多数平台提供了这样的能力:你交给它一份字段清单,它在生成的时候就只允许产出符合这份清单的内容——不合语法的字符根本不会被生成出来。这属于从源头堵住,而不是生成完了再回头检查。厂商文档是确认「它到底强制了什么」的权威参照。openai-structuredclaude-structured

能用就一定要用,可靠性上这是提升最明显的一步。但它的边界要划清:结构一定合法,内容不一定正确。字段一个不缺,值却可能是编的;类型对得上,数字可能是错的。格式约束管不了编造。

这里有一个必须承认的取舍:强制格式会收窄它的自由度。在需要长段推理或创造性表达的任务上,把输出压进一个严格结构,可能让内容本身变差。所以别对每一次调用都上最强约束——给人看的走宽松,给程序读的走严格。

格式设计得顺不顺手,直接决定它填得对不对

光把约束加上还不够。这份格式设计得是否顺手,会直接影响它填得对不对。有四条经验。

一、字段名要能自解释。一个字段只叫 date,而材料里前后出现过三个日期,它就只能猜你要哪一个;写成 signing_date,猜的余地没有了。它填值的时候是顺着字段名的字面语义走的,名字给得含糊,它就只好猜。

二、别嵌套太深。一旦嵌套超过三层,填错的概率就明显上升。能拍平的层级尽量拍平。

三、给枚举值划定范围。要做分类,就把允许出现的选项一次列全。漏了这一步,它会自己造一批新类别出来,而且造得相当像样,短时间内你未必看得出来。

四、留一个「不确定」的出口。这一条最容易被忘掉。假如每个字段都设成必填,那材料里查不到的信息它就只剩一条路:编一个填上。反过来,允许空着,或者给一个「资料未提供」这样的合法取值,就等于给了它一条说实话的路——这是防编造最实在的做法之一。

另有一条常被忽略的:它得在动笔写内容之前就知道格式。先把结构摆出来让它往里填,与让它先写完再套结构,完全是两回事。前者是填空,后者是改写——改写更容易把信息弄丢。

给人看的和给程序读的,在流式这里撞车

这是产品设计上要提前想清楚的取舍,不少团队是在线上撞了才知道。

流式输出对「感觉上的速度」帮助极大:字符一个一个蹦出来,用户看着它在动,总好过对着一个转圈图标干等。可它和结构化输出天然冲突——结构只写了一半,解析器是读不了的。要解析就得等它把最后一段吐完,可等到那时候,流式的意义已经没有了。

怎么处理,先问一句:这段输出去哪儿?

给人看的,就别要结构。把流式打开,让结果就是一段读得下去的自然语言,别为了让界面显得整齐硬套一层结构。

给程序读的,就别开流式。像数据抽取、分类打标这类,输出本来就只给代码消费,用户看不到中间过程,流式毫无收益。直接关掉,改用严格约束。

两边都要,就拆成两段。这是最贴近实际的一类场景——界面上要有一段读起来自然的回答,程序那边又需要结构化的元数据。做法是:把给人看的正文排在前面,末尾再补一小段结构化数据,前端渲染前面的正文,程序解析末尾那一段。也可以更干脆,拆成两次调用,各管各的。

这一层必须在设计阶段就定下来,别等前端做完了才发现格式根本对不上。动笔写提示词之前,先回答一个问题:这段产出究竟是给人读的,还是要被代码消费的。

校验这一层要自己写,失败率要自己盯

前面几层都上了,这一层也照样不能省。解析失败怎么处理、字段缺失怎么处理、值明显不合理怎么处理——这些分支都得写出来。

常见的兜底策略有三种:重试一次(把错误信息带上,让它自己改)、降级(换个模型或退回简单模式)、拒绝(直接告诉用户这次没成,别把一堆乱码摆到他面前)。

写完分支,还得有个数字盯着自己:把解析失败率做成指标。不是等用户投诉,而是自己看着这个数字走。它的用途有三层。

第一,它告诉你第一层够不够。如果你只用了提示词请求,失败率是多少,就是你要不要上第二层的依据。

第二,它突然上涨,通常是上游变了。尤其是你用了会跟着上游走的模型名——它悄悄换了一个版本,格式遵从的习惯就变了,你的失败率是最早的信号。这时候该做的是重跑样例、确认影响范围,而不是就地加兼容逻辑。

第三,它是你唯一能自动发现的格式问题。内容错了要靠人看或者靠评测,但结构坏了是机器能立刻发现的——这类信号不该只留在日志里。

一条实践纪律:别用「加兼容逻辑」去压这个数字。解析器里塞进越来越多的容错分支,看起来失败率降了,实际上是把问题从「看得见的失败」变成了「悄悄填错的值」——后者严重得多。要么在源头修格式,要么明确地拒绝这次输出。

到这里,格式这件事算是卡住了。但还剩一个绕不开的变量:它每次抽的签不一样,所以同样的输入不保证同样的输出。把随机性当成一个可以调的工具,是温度与随机性要讲的事。

参考资料

  1. Structured Outputs — OpenAI
  2. Structured outputs — Anthropic