3839 字
约 12 分钟
14
3.2 结构化输出:让模型的话可被程序读
2026-10-25
无标签

3.2 结构化输出:让模型的话可被程序读

一句话结论:在 Agent 里,模型的输出几乎总要被程序读取。这意味着"格式是否稳定"不是美观问题,而是功能问题——格式解析失败,整个循环就断了。

读这篇之前

需要先读 3.1(三件套)和 2.4(温度与稳定性)。这一篇是把 3.1 的"输出要求"那一项彻底展开。


一、为什么这件事比想象中重要

先算一笔账。在一个典型 Agent 循环里(1.3 讲过),每一轮都要做:

模型输出 → 你的代码解析 → 校验 → 执行 → 结果回填 → 再输出

"你的代码解析"这一步,是纯程序逻辑,容不得半点含糊。

一旦模型输出不合法:

情况 后果
JSON 少一个括号 解析异常,整轮失败
字段名拼错 校验失败,或更糟——静默用了默认值
类型不对(字符串 vs 数字) 下游工具报错
多了一段解释文字 解析器不知道该从哪开始

而且这些问题不是偶发的:2.4 讲过,采样有随机性;2.3 讲过,模型会"顺手"完善格式。输出越长、嵌套越深,越容易在细枝末节上出错。


二、六个结构单元:把提示词写成"有骨架的"

在讲格式约束之前,先给一个通用骨架。生产级提示词通常由六个结构单元构成:[原文 707]

单元 作用 必填
Role 角色 设定身份,决定知识边界与回答风格 ✅
Directive 核心指令 描述要做什么 ✅
Context 背景信息 提供决策依据 可选
Exemplars 示例 few-shot,引导输出格式与风格 可选
Format 格式约束 JSON / Markdown / 表格 推荐
Style 风格约束 语气、长度、细节程度 可选

注意 Format 被标为"推荐"而非"可选"——因为只要输出要被程序读,它就不是可选项。而 Style 才是真正可选的,它只影响可读性,不影响功能。

原文给了一个示例(天气助手):

## 角色 (Role)
你是气象专家,熟悉天气数据的解读和出行建议。

## 核心指令 (Directive)
根据用户提供的城市名称,调用天气 API 获取数据,
然后给出穿衣建议和出行推荐。

## 背景信息 (Context)
- 当前季节:{{season}}
- 用户所在区域:{{region}}
- 天气 API 返回字段:温度、湿度、风力、降水

这个骨架的价值在于:它让"格式要求"变成提示词里的一个独立区块,而不是散落在散文句子里。模型对"独立区块"的遵循度,明显高于"顺口提一句"。



三、原生结构化解码:最重要的那一条

在 Agent 系统里,最重要的输出配置不是温度,而是结构化输出。现代主流模型都支持原生 JSON 模式,它的关键特征是:由服务端保证输出是合法 JSON 且符合 schema,而不是靠提示词"请求"它。[原文 707]

一个典型的调用形态:

response = client.chat.completions.create(
    model="...",
    messages=[...],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "AgentDecision",
            "strict": True,            # 严格模式:不允许额外字段
            "schema": {
                "type": "object",
                "properties": {
                    "thought":  {"type": "string"},
                    "action":   {"type": "string", "enum": ["call_tool","respond","ask_user"]},
                    "tool_name":{"type": "string"},
                    "tool_args":{"type": "object"},
                    "answer":   {"type": "string"}
                },
                "required": ["thought","action"]
            }
        }
    }
)

三个细节值得注意:

细节 作用
strict: True 不允许模型加额外字段——避免它"顺手"多给几个键
enum 约束 action 把"能做什么"限制在固定集合里,天然防住越权动作
required 只列两个 不是所有字段都必填;只在必要时才必填,减少失败率

为什么这一条比提示词技巧重要? 因为它是机制层面的保证,而不是"请求模型配合"。提示词可以被忽略,schema 校验不会。[原文 707]

如果所用 API 不支持原生结构化解码,退回到"指定 schema + 给示例 + 校验重试"的三件套,可靠度会低一档但可用。


四、写提示词的 C.L.E.A.R 原则

结构化输出解决了"格式",但提示词本身写得好不好,还有一套更通用的标准。好提示词有五个共性,可用 C.L.E.A.R 记忆:[原文 707]

原则 含义 反例 → 正例
C — Concise 简洁,剔除冗余 「帮我生成一个你觉得合适的设计」→「生成一个 SaaS 产品落地页,包含 Hero、Features、Pricing 三段」
L — Logical 有逻辑,条理分明 大段文字描述 → 用编号步骤:①分析需求 ②设计方案 ③输出代码
E — Explicit 明确,不留歧义 「写得专业一点」→「语气正式,用第三人称,每段不超过 3 句话」
A — Adaptive 可迭代优化 一次性写死 → 先草稿,测试效果后逐步改进
R — Reflective 可复盘复用 用过就丢 → 保存高质量 Prompt 到模板库,标注适用场景

这五条里,E(明确)是收益最高的。原话是"不留歧义"——而歧义最常见的表现就是形容词:"专业一点""简洁一点""详细一点"。这些词对模型没有约束力。

把形容词换成可数的事实:

❌ 简洁一点
✅ 每段不超过 3 句,全文不超过 300 字

❌ 专业一点
✅ 使用第三人称,避免口语词,术语首次出现给定义

A 和 R 这两条最容易被忽略,但它们决定了提示词能否积累。 一个团队如果每人都手写提示词、用完就丢,一年下来资产为零。


五、示例(Few-shot):说不如给

当你说不清想要什么时,给一个例子比写一百字描述更有效。这是 Few-shot(少样本提示)的核心思想。[原文 707]

一个典型对比:

Zero-shot(不给例子):
  判断以下评论的情感:"这个产品太棒了!"

Few-shot(给例子):
  判断以下评论的情感。
  示例:
    评论:"质量很好,推荐购买" → 正面
    评论:"太差了,完全不能用" → 负面
    评论:"还行吧,一般般" → 中性
  现在判断:
    评论:"这个产品太棒了!" →

原文给出的量级判断:Few-shot 的效果通常比 Zero-shot 好 20%~40%,尤其在分类、格式转换、风格模仿等任务上。[原文 707]

原因在 2.1 讲过:模型擅长从已有内容里"检索"模式,不擅长从抽象描述里"推理"出模式。示例把"推理"变成了"检索"。


六、让输出可靠的五个手段

从便宜到昂贵,五个手段叠加使用:

# 手段 做法 效果
1 指定 schema 在提示词里给出字段、类型、必填、取值范围 基础
2 给一个完整示例 展示一个"合法输出长什么样" 显著提升
3 用原生结构化解码 用模型 API 的 JSON mode / structured output 能力 大幅提升
4 schema 校验 + 重试 解析失败就把错误信息回传,让它修 兜底
5 降温度 输出类环节把温度设低(2.4) 降低抖动

第 3 条值得单独说明:主流模型 API 大多提供了"强制 JSON"或"按 schema 输出"的开关。这不是提示词技巧,而是服务端约束,可靠性远高于靠提示词"请求"它输出 JSON。

优先级判断:如果 API 支持结构化解码,用它;如果不支持,用"指定 schema + 给示例 + 校验重试"。

第 4 条:校验重试怎么做才对

关键在把错误信息喂回去,而不是重发同一句话:

❌ 重试时发同样的话 → 大概率错在同一个地方
✅ 重试时附上:你的输出不符合要求:字段 items 应为数组,
   实际收到字符串。请修正后仅输出 JSON。

这本质上是给模型一次自我纠正的机会,而纠正的依据是具体的、可行动的反馈。错误信息的质量决定了纠正的成功率——这条原则在 2.5 讲工具层时也出现过。


七、一个具体场景:让模型输出"可执行的任务清单"

假设你要 Agent 拆解任务。一个脆弱的写法:

❌ 请把任务拆成几步列出。

它会给你一段带编号的散文,格式每次都不一样。

一个稳的写法:

请把任务拆解为步骤,仅输出 JSON,不要输出任何其他文字:

{
  "steps": [
    {
      "id": 1,
      "action": "动作名(必须是 read_file / write_file / run_test 之一)",
      "target": "目标对象",
      "reason": "为什么需要这一步(一句话)"
    }
  ]
}

约束:
- steps 至少 1 个、最多 10 个
- action 只能取上述三个值,不得自创
- 若任务无法拆解,输出 {"steps": []}

注意最后那条空集约定——它给模型一个"合法退出"的出口,避免它为了满足"至少一个"而编造步骤。

这是通用技巧:凡是要求"必须输出某些内容"的 schema,都要想清楚"确实没有内容时"该怎么表达,并显式定义。


八、校验:程序侧的守门

无论提示词写得多好,程序侧都必须校验。三个层次:

层 检查什么 失败处理
语法 能否解析成 JSON 重试或报错
结构 必填字段在不在、类型对不对 重试并给出具体缺什么
语义 值是否在允许范围(枚举、区间、长度) 拒绝并说明原因

语义层最容易被省掉,但它是防误操作的关键。 例如:

{ "action": "delete_all_files" }

语法合法、结构合法,但 action 不在枚举里——只有语义校验能拦住它。

这就是 2.5 讲的"工具层参数校验"的第一道关。


九、动手:一个可以直接用的 JSON schema 模板

结构化输出的实操,最关键的是写出好的 schema。

模板

{
  "type": "object",
  "required": ["summary", "items"],
  "additionalProperties": false,
  "properties": {
    "summary": {
      "type": "string",
      "description": "一句话结论,不超过 50 字"
    },
    "items": {
      "type": "array",
      "maxItems": 20,
      "items": {
        "type": "object",
        "required": ["id", "severity", "detail"],
        "properties": {
          "id":       {"type": "string"},
          "severity": {"type": "string", "enum": ["high", "medium", "low"]},
          "detail":   {"type": "string", "maxLength": 300},
          "evidence": {"type": ["string", "null"],
                       "description": "支持这条结论的材料片段;无依据填 null"}
        }
      }
    }
  }
}

五个设计要点

要点 作用
additionalProperties: false 禁止模型加字段(3.2 strict)
required 只列必须的 减少失败率
enum 限定取值 天然防越权(14.4)
maxItems / maxLength 防止输出失控
evidence 可为 null 允许"无依据"= 允许说不知道(2.3)

最后一条最巧妙

如果强制 evidence 非空,模型会编一个片段来通过校验。

用 ["string","null"] 允许 null,反而更诚实——它可以选择"我没有依据",而不必编造。

这是一个通用技巧:凡是"希望模型诚实"的字段,都要允许"空"。


十、常见误区

误区一:要求"输出 JSON"就够了。
不够。模型可能包一层 ```json 代码块、加一句"这是您要的 JSON"、或漏掉必填字段。要指定 schema + 给示例 + 校验。

误区二:用正则从长文本里抠 JSON。
极脆弱。嵌套结构、数组、转义字符都会让它崩。用原生结构化解码,或让模型只输出 JSON。

误区三:校验失败就重试同一句话。
大概率重复同一个错误。必须把具体错误回传。

误区四:温度对 JSON 合法性没影响。
有影响。高温会增加格式偏差概率。输出类环节必须降温度。

误区五:schema 定得越细越好。
过细的约束(比如字段长度 20-25 字)会让模型频繁违规、反复重试。约束应覆盖"必须为真"的部分,不必过度限制格式细节。


十一、结构化输出的一个隐蔽成本

结构化输出很可靠,但有一个成本常被忽略:schema 本身占 Token。

schema 复杂度 大约 Token
3 个简单字段 50-100
10 个字段含描述 300-600
嵌套数组 + 枚举 600-1500

每次调用都要发一遍。

三个优化手段

手段 效果
只保留必要字段的 description 省一半
枚举值简短(h/m/l 而非 high/medium/low) 省一些
把 schema 放前面以利缓存 单价降低(5.3)

第三条最有效:schema 是稳定的,应该放在上下文的最前部享受缓存。

一个反例

错:每次调用重新生成 schema 字符串(顺序可能不同)
   → 缓存永远不命中

对:schema 定义为模块级常量,一次生成
   → 每次字节级一致,可缓存

十二、一个判断:什么情况下不需要 schema

不是所有输出都需要 schema。过度结构化也是成本。

情况 需要 schema 吗
输出给程序解析 ✅ 必须
需要多字段分别使用 ✅ 必须
只是给人看的一段话 ❌ 不需要
结果要存库/传下游 ✅ 必须
用于展示的建议文本 ⚠️ 可选

判据一句话:

有没有下游程序依赖它的结构?
  有 → schema
  没有 → 自然语言即可

很多团队给所有输出都加 schema,结果是"提示词变长、模型变拘谨、可读性下降",但收益为零。


十三、下一篇

格式稳了,但还有一个问题:模型"想"得对不对?

有些任务需要它先想清楚再答,有些任务想了反而更差。这个差别背后是有机制的,也对应了几种不同的提示技术。

下一篇讲思维链:它到底什么时候有用,什么时候反而有害。

3.2 结构化输出:让模型的话可被程序读
https://clxhxhhr.top/posts/4878/
作者
clxstart
发布于
2026-10-25
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。