引言:为什么 Prompt 诱导 JSON 会失败

很多开发者在实际项目里都用过这种方式——精心设计一段 prompt,让 Claude 帖乖乖吐出 JSON:

请以 JSON 格式返回用户信息,包含 name、age、email 三个字段。

看起来没什么问题,但在生产环境里,这种写法的失败率往往落在 5%–15% 之间。失败的原因五花八门:有时模型会用 Markdown 代码块把 JSON 包起来,有时某个字段莫名其妙消失了,有时字符串里混进了换行符把格式搞坏,更糟的情况是直接给你一段语法错误的 JSON。

每次失败都得重试、人工修复,业务流程也跟着卡住。当这类调用每天达到数千次,积累下来的成本就相当可观了。

Claude API 的结构化输出提供了一个从根本上解决这个问题的思路:通过定义 JSON Schema,在模型生成 token 的过程中直接施加约束,从源头保证输出格式合法。这不是"把 prompt 写得更好",而是换了一条完全不同的技术路线。在这里插入图片描述


三种 JSON 输出方式的权衡

在选方案之前,有必要先把三种路线的优缺点摆清楚。

Prompt Engineering(传统方案)

怎么工作的:在 prompt 里详细描述你想要的 JSON 格式,然后靠模型自己去理解和遵循。

优点是实现简单,不需要特定 API 版本,所有 Claude 版本都能用,改 Schema 也不用重新编译什么东西。

缺点也很明显:成功率不稳定,通常在 85%–95% 之间浮动;失败方式多种多样,很难预判;一旦遇到复杂的嵌套结构,失败率会显著上升;重试带来的额外 token 消耗大约在 10%–20%。

整体来说,JSON 合法率大概在 90% 左右。

结构化输出(推荐方案)

怎么工作的:通过 output_config 参数传入 JSON Schema,Claude 在生成过程中会受到 schema 约束,输出必定是合法的 JSON。

好处显而易见:JSON 合法率能达到 99.9%+,格式问题导致的重试基本消失;复杂嵌套结构也没有额外的失败率;Schema 会被缓存 24 小时,后续请求延迟更低。

当然也有代价:需要 Claude 3.5 Sonnet 或更新版本(Opus 4.1+);需要在请求头里指定 beta header,版本管理稍微复杂一点;Schema 设计有严格要求,比如必须设 additionalProperties: false;首次 Schema 编译会多出 50–100ms 延迟。

在成本上,token 消耗和 prompt engineering 差不多(差距不到 2%),但因为消除了重试,实际总成本反而能降低 10%–15%。

混合方案

有些场景不需要非此即彼。简单、低频的 JSON 用 prompt engineering 就够;复杂、高频的 JSON 上结构化输出;关键路径用结构化输出,同时保留降级到 prompt engineering 的容错机制。根据实际情况灵活搭配。


适用场景与迁移成本评估

场景选择参考

场景特征 推荐方案 原因
结构简单(<5 字段),低频(<100/天) Prompt Engineering 成本低,偶发失败可接受
结构复杂(>10 字段),高频(>1000/天) 结构化输出 稳定性收益远超成本
嵌套深度 >3 层 结构化输出 prompt 失败率会显著上升
模型版本受限(Claude 3.x) Prompt Engineering 不支持结构化输出
关键业务路径(数据质量影响收入) 结构化输出 稳定性不可妥协
动态 Schema(运行时生成) 混合方案 固定字段用结构化输出

迁移成本大概是多少

代码改动量不大,定义 Schema 加上修改调用参数,通常在 20–50 行左右。

学习曲线取决于你对 JSON Schema 规范的熟悉程度。如果本来就了解,上手可能不到一小时;如果是第一次接触,留出 2–4 小时学习比较稳妥。

上线风险属于中等水平,主要来自 Schema 定义错误或模型版本兼容性问题。建议先走灰度,从 10% 流量开始验证。


结构化输出的完整实现指南

前置条件:模型版本支持

模型版本 结构化输出支持 Beta Header 备注
Claude 3.5 Sonnet 需要 推荐版本
Claude 3 Opus 需要 推荐版本
Claude 3 Sonnet 不支持
Claude 3 Haiku 不支持

具体以官网最新说明为准。使用结构化输出时,请求头里需要加上:anthropic-beta: structured-outputs-2024-10-15

Schema 设计规范

规则一:必须设置 additionalProperties: false
{
  "type": "object",
  "properties": {
    "name": { "type": "string" },
    "age": { "type": "integer" }
  },
  "additionalProperties": false,
  "required": ["name"]
}

为什么这条规则不能省?因为如果不加这个约束,模型可能会"发挥创意",往 JSON 里塞进 descriptionnotes 这类你根本没定义的字段,直接导致 schema 验证失败。加上这条约束,模型只能老老实实生成你定义的字段。

规则二:明确定义必填字段

required 数组指定哪些字段不能缺,可选字段在 properties 里定义但不加进 required 就好。

{
  "required": ["id", "name"],
  "properties": {
    "id": { "type": "string" },
    "name": { "type": "string" },
    "email": { "type": "string" }
  }
}

实测数据表明,明确标记必填字段后,字段遗漏率能从 2%–3% 降到 0.5% 以下。这个收益相当明显。

规则三:有限选项用 enum,别用自由 string
{
  "status": {
    "type": "string",
    "enum": ["active", "inactive", "pending"]
  }
}

enum 约束让模型只能从预定义的值里选,拼写错误或意外值从根本上就不可能出现。对于分类字段来说,这能把错误率从 1%–2% 压到接近零。

中文内容的特殊处理

这块是很多教程没提到的,但在实际项目里相当重要。

问题一:Key 的命名

模型理解英文 key 的能力明显强于中文 key。比较好的做法是用英文 key,然后在 description 里写中文说明:

{
  "properties": {
    "user_name": {
      "type": "string",
      "description": "用户名称"
    },
    "phone_number": {
      "type": "string",
      "description": "用户手机号"
    }
  }
}

实测下来,英文 key 加中文 description 的组合,准确率比直接用中文 key 高 5%–8%。

问题二:中文值的 token 消耗

中文字符在 token 计算上比较"贵"——一个中文字符通常消耗 1–2 个 token,而一个英文单词只要 0.2–0.5 个 token,也就是说中文内容的 token 成本大约是英文的 3–5 倍。如果字段里包含大量中文文本,建议在 prompt 里补充长度限制的说明:

{
  "description": {
    "type": "string",
    "description": "商品描述,不超过 100 个中文字符"
  }
}

问题三:不同模型版本的中文表现差异

Claude 3.5 Sonnet 对中文的处理能力整体优于 Claude 3 Opus,在复杂嵌套结构里尤其明显。如果你的 Schema 里有大量中文 description,优先选 Sonnet。


代码实现

Python 完整示例
import anthropic
import json

schema = {
    "type": "object",
    "properties": {
        "user_id": {
            "type": "string",
            "description": "用户唯一标识"
        },
        "user_name": {
            "type": "string",
            "description": "用户名称"
        },
        "age": {
            "type": "integer",
            "description": "用户年龄"
        },
        "status": {
            "type": "string",
            "enum": ["active", "inactive"],
            "description": "用户状态"
        }
    },
    "required": ["user_id", "user_name", "status"],
    "additionalProperties": False
}

client = anthropic.Anthropic(api_key="your-api-key")

response = client.messages.create(
    model="claude-3-5-sonnet-20241022",
    max_tokens=1024,
    messages=[
        {
            "role": "user",
            "content": "提取以下文本中的用户信息:张三,30岁,账户激活状态。"
        }
    ],
    output_config={
        "type": "json_schema",
        "json_schema": {
            "name": "UserInfo",
            "schema": schema,
            "strict": True
        }
    },
    betas=["structured-outputs-2024-10-15"]
)

result = json.loads(response.content[0].text)
print(result)
Node.js 完整示例
const Anthropic = require("@anthropic-ai/sdk");

const schema = {
  type: "object",
  properties: {
    user_id: {
      type: "string",
      description: "用户唯一标识"
    },
    user_name: {
      type: "string",
      description: "用户名称"
    },
    age: {
      type: "integer",
      description: "用户年龄"
    },
    status: {
      type: "string",
      enum: ["active", "inactive"],
      description: "用户状态"
    }
  },
  required: ["user_id", "user_name", "status"],
  additionalProperties: false
};

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY
});

async function extractUserInfo(text) {
  const response = await client.messages.create({
    model: "claude-3-5-sonnet-20241022",
    max_tokens: 1024,
    messages: [
      {
        role: "user",
        content: `提取以下文本中的用户信息:${text}`
      }
    ],
    output_config: {
      type: "json_schema",
      json_schema: {
        name: "UserInfo",
        schema: schema,
        strict: true
      }
    },
    betas: ["structured-outputs-2024-10-15"]
  });

  return JSON.parse(response.content[0].text);
}

extractUserInfo("张三,30岁,账户激活状态")
  .then(result => console.log(result))
  .catch(error => console.error(error));

错误处理与降级方案

几种常见错误的处理思路

Schema 验证失败

症状是 API 返回 400 错误,提示 “Invalid output schema”。通常是这几个原因:漏了 additionalProperties: false、用了不支持的类型(比如混用 numberinteger)、嵌套层级太深(超过 10 层)。排查时对照这几点逐一检查,酌情简化嵌套结构。

模型返回空值或类型不匹配

输出的 JSON 里某些字段是 null 或者类型对不上。这一般是 prompt 不够清晰,或者 Schema 的 description 写得太模糊。解决办法是在 description 里补充更具体的说明,对于必填字段可以在 prompt 里加几个示例,同时检查 prompt 和 Schema 的表述是否一致。

快速降级到 Prompt Engineering

结构化输出出问题时,能快速切回 prompt engineering 很重要。可以参考这个设计:

def extract_user_info(text, use_structured=True):
    try:
        if use_structured:
            return extract_with_structured_output(text)
    except Exception as e:
        print(f"结构化输出失败: {e},降级到 prompt engineering")
        return extract_with_prompt_engineering(text)

def extract_with_structured_output(text):
    # 使用结构化输出的实现
    pass

def extract_with_prompt_engineering(text):
    # 使用 prompt 的实现,包含 JSON 格式验证
    pass

这样即使结构化输出出了问题,业务流程也不会整个卡死。


生产环境集成与稳定性保障

项目结构建议

把 Schema 单独放到独立文件里,方便版本管理:

project/
├── schemas/
│   ├── user_info.json
│   ├── product_info.json
│   └── __init__.py
├── api/
│   ├── claude_client.py
│   └── extractors.py
└── tests/
    └── test_structured_output.py

然后在 schemas/__init__.py 里统一加载和缓存:

import json
import os

SCHEMAS = {}

def load_schemas():
    schema_dir = os.path.dirname(__file__)
    for filename in os.listdir(schema_dir):
        if filename.endswith('.json'):
            name = filename.replace('.json', '')
            with open(os.path.join(schema_dir, filename)) as f:
                SCHEMAS[name] = json.load(f)

load_schemas()

监控指标

生产环境里需要关注这几个指标:

  • JSON 合法率:每小时成功解析的占比,低于 99% 就该告警
  • 错误率:API 返回错误的占比,超过 0.1% 就要注意
  • 延迟波动:与历史均值相比,增长超过 20% 时告警
  • Schema 缓存命中率:验证 24 小时缓存是否真的在发挥作用

灰度发布节奏

分三个阶段上比较稳:

第一阶段跑 1–2 天,先放 10% 流量,盯紧所有关键指标;没异常的话第二阶段扩到 50%,继续观察,收集反馈;确认稳定后全量,同时保留快速回滚能力。


常见问题

Q:什么时候值得迁移到结构化输出?

满足下面任意一条就可以考虑了:每天调用超过 500 次;JSON 失败率高于 2%;数据质量直接影响核心业务指标。

Q:结构化输出会增加成本吗?

token 消耗基本一样,但因为重试减少了,实际总成本反而会降 10%–15%。首次 Schema 编译会多出 50–100ms,之后因为有缓存,延迟就恢复正常了。

Q:模型版本不支持结构化输出怎么办?

升级到 Claude 3.5 Sonnet 或更新版本是最直接的办法。如果实在无法升级,就继续用 prompt engineering,但建议在代码里加好 JSON 格式验证和重试逻辑,减少失败带来的影响。

Q:动态 Schema 怎么处理?

如果 Schema 完全是运行时动态生成的,那还是得用 prompt engineering。如果主体结构固定、只有部分字段动态变化,可以定义一个基础 Schema,动态部分的要求在 prompt 里补充说明,两者结合来用。


总结:一个快速决策流程

如果不确定该用哪种方案,可以顺着这个逻辑想:

第一步:你的 JSON 提取任务每天超过 500 次吗?

  • 是 → 继续往下
  • 否 → prompt engineering 就够了,监控一下失败率

第二步:你用的模型支持结构化输出吗(Sonnet 3.5+ 或 Opus 4.1+)?

  • 是 → 继续往下
  • 否 → 升级模型,或者继续用 prompt engineering

第三步:能投入 2–4 小时学习和实现结构化输出吗?

  • 是 → 迁移过去,按灰度流程上线
  • 否 → 先强化现有的错误处理,等有时间再迁移

迁移 Checklist

  • 定义清晰的 JSON Schema,每个字段都写好 description
  • 中文内容用英文 key,在 description 里做中文说明
  • 写好错误处理和降级逻辑
  • 建立监控,至少追踪 JSON 合法率和接口延迟
  • 制定灰度计划,从 10% 流量开始
  • 准备好回滚方案,确保能快速切回 prompt engineering
  • 覆盖中文、特殊字符等边界场景的测试
  • 把 Schema 定义和维护流程文档化

做到这些,JSON 提取的失败率基本上能从 5%–15% 压到 0.1% 以下,成本还不会增加。对于依赖 API 稳定性的生产系统来说,这个投入是值得的。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐