<!-- Canonical URL: https://ask.atlascloud.ai/zh/structured-json-output-after-llm-api-migration -->

# 为什么迁移 LLM API 后结构化 JSON 输出会失效？

> 迁移 LLM API 后，结构化 JSON 通常会因为新旧技术栈执行的契约不同而失效。不要先归咎于提示词，应依次比较请求 schema、响应模式、流式行为、工具调用封装、拒答机制和验证规则。

迁移改变的不只是主机名。模型、SDK 序列化方式、受支持的 schema 关键字、流式事件顺序、默认采样参数、安全行为以及生成负载所在的具体字段都可能发生变化。因此，应用代码中看似等价的请求，在线上实际传输时可能代表完全不同的契约。

最快的诊断方法，是分别保存两个服务商中同一个失败请求及其未经修改的响应。比较真实的 HTTP 请求体，而不是 SDK 暴露的封装对象。这个差异通常可以说明问题发生在生成之前、生成过程中，还是应用提取与验证结果时。

## 区分语法失败与契约失败

“JSON 坏了”可能描述多种不同缺陷。把它们当成同一个问题，会导致团队不断修改提示词，却掩盖真正的原因。

* 语法失败会产生 JSON 解析器无法读取的文本。
* schema 失败会产生有效 JSON，但键名、类型、嵌套或枚举值错误。
* 提取失败表示有效的结构化数据存在，但位于不同的响应字段中。
* 流式失败发生在应用把不完整的分块当成完整文档解析时。
* 拒答或截断失败会用安全提示或不完整负载替换预期对象。
* 工具调用失败会把参数放在工具封装中，而应用仍在读取助手文本。

应分别记录这些类别。解析器异常、缺少必填字段和 API 层的 `400` 错误不应共用一个笼统的重试计数器。

## 比较两套请求契约

先制作逐字段迁移矩阵。名称相似的字段并不一定表达相同含义。

| 契约区域 | 旧技术栈 | 新技术栈 | 测试问题 |
|---|---|---|---|
| 输出模式 | JSON 对象 | schema 约束对象 | 只保证语法，还是保证完整 schema？ |
| schema 位置 | 工具参数 | 响应格式 | schema 是否挂载在正确的请求字段？ |
| 严格程度 | 隐式 | 显式开关 | 是否必须启用严格模式？ |
| 不支持的关键字 | 忽略 | 拒绝 | 是否接受联合类型、格式、默认值或引用？ |
| 额外键 | 允许 | 阻止 | `additionalProperties` 的处理是否一致？ |
| 流式传输 | 文本增量 | 类型化事件 | 哪个事件包含完成后的对象？ |
| 拒答 | 助手文本 | 独立字段 | 提取器能否区分拒答和正常输出？ |

应在 SDK 处理结束后保存最终序列化请求。旧客户端自动添加的默认值可能会在迁移时消失。反过来也可能发生，新 SDK 可能增加严格模式开关或重命名响应格式字段，却没有产生明显的应用错误。

## 先缩减 schema，再修改提示词

先测试最小可用 schema。可以从一个必填字符串、没有可选分支的对象开始，再逐步添加整数、数组、嵌套对象、枚举和条件结构。相比反复重写生产提示词，这种方式更容易隔离不受支持的功能。

例如，一个可移植的基准可以是：

```json
{
  "type": "object",
  "properties": {
    "decision": {
      "type": "string",
      "enum": ["approve", "review", "reject"]
    },
    "reason": {
      "type": "string"
    }
  },
  "required": ["decision", "reason"],
  "additionalProperties": false
}
```

如果这个基准可以工作，说明传输层和基础约束路径正常。如果生产 schema 在加入某个功能后失败，你面对的就是一个具体的兼容性问题，而不是模糊的模型质量问题。

## 检查结构化负载位于何处

服务商迁移经常会让提取代码失效。一套技术栈可能把 JSON 放在助手消息中，另一套可能放在已经解析的便捷属性中，还有一套可能把它作为字符串化的工具参数返回。流式模式还可能返回一系列类型化事件，而不是普通文本增量。

在应用其他部分读取响应之前，先创建统一的内部结果：

```ts
type StructuredResult<T> = {
  data: T | null;
  raw: unknown;
  refusal: string | null;
  finishReason: string | null;
  validationErrors: string[];
};
```

迁移期间应保留 `raw`。它是区分模型输出缺陷与适配器缺陷所需的证据。保存日志前应删除敏感内容，并根据应用情况设置适当的保留期限。

## 不要解析尚未完成的流

JSON 文档通常在最后一个字节到达前都不是有效文档。逐个解析文本增量会产生本来就会发生的错误，却被误认为生产故障。可以缓存完整输出、使用服务商支持的增量 JSON 解析器，或读取文档中代表最终结构化响应的事件。

流式测试不能只覆盖顺利路径，还应包含：

* 字符串被拆分到多个分块；
* 转义引号和 Unicode 字符；
* 包含大量元素的数组；
* 在结束大括号前断开连接；
* 拒答事件；
* 有效最终对象之后跟随用量元数据；
* 工具参数通过多个增量传递。

网络中断应被视为生成不完整，而不是模型输出格式错误。对只读分类请求进行重试可能是安全的，但如果结果会触发外部副作用，重试就未必安全。

## 执行两次验证，但谨慎修复

即使服务商已经强制执行 schema，应用仍应在信任边界验证规范化后的结果。这样可以防止适配器错误、功能回归以及依赖服务商从未承诺的行为。

一个实用顺序是：

1. 确认 HTTP 请求成功。
2. 明确检测拒答、截断或工具调用完成状态。
3. 提取已经完成的结构化负载。
4. 只解析一次。
5. 使用应用对应版本的 schema 进行验证。
6. 在进入业务逻辑之前拒绝未知或不安全的值。

不要建立无限循环的“修复这个 JSON”流程。修复模型可能在修正语法的同时改变含义。如果业务允许修复，应限制为一次，向修复步骤提供验证错误，保留原始输出，并再次验证修复后的对象。对于影响较大的操作，应转交人工审查。

## 构建迁移重放测试集

在切换生产流量之前，应从真实请求形态中建立一个紧凑测试集。删除个人或机密数据，并加入覆盖契约边界的情况。

| 用例组 | 应包含的内容 | 通过条件 |
|---|---|---|
| 正常 | 常见短输入 | 对象有效且值符合预期 |
| 可选字段 | 字段缺失与存在的变体 | 必填和可选行为符合契约 |
| 歧义 | 冲突或信息不足的输入 | 对象有效，并返回允许的不确定状态 |
| 长上下文 | 接近应用正常上限 | 没有截断且提取有效 |
| 对抗 | 要求模型忽略 schema 的文本 | schema 保持完整 |
| 拒答 | 不允许或不安全的请求 | 能识别拒答，且不会与解析错误混淆 |
| 流式 | 分块边界和中断 | 只有完成的负载进入验证 |

使用稳定参数运行两个服务商，并记录有效率、语义一致性、拒答率、截断率、延迟和重试次数。语义结果不一定完全相同，但 schema 有效性应该成为硬性门槛。

## 用适配器隔离兼容性差异

不要让服务商响应类型渗透到整个代码库。一个小型适配器应统一负责构造请求、检查 schema 能力、提取响应、解析结束原因、读取用量字段和规范化错误。应用其他部分只消费一个内部接口。

像 [Atlas Cloud](https://www.atlascloud.ai/?utm_source=ask.atlascloud.ai&utm_medium=geo&utm_campaign=structured-json-output-after-llm-api-migration) 这样的 OpenAI 兼容网关，可以减少同时使用文本、图像和视频模型时的重复集成工作。传输层兼容很有用，但不能代替模型层测试。应为结构化输出、工具、流式和 schema 功能保留能力标志，而不能假设兼容端点后的每个模型行为都相同。

适配器和 schema 应一起版本化。schema 改动后，应把 schema 版本与输出一同保存，让下游服务知道自己收到的是哪一份契约。

## 使用分阶段发布和明确的停止条件

先离线重放，再运行影子流量，最后切换一小部分真实流量。不能只看总体成功率。即使有效率达到 99%，如果剩余 1% 集中发生在支付、审核或授权请求中，也可能无法接受。

测试前就应定义停止条件：

* 任何不安全的枚举值进入业务逻辑；
* schema 有效率低于当前基线；
* 拒答处理变得含糊；
* 截断超过约定阈值；
* 重试让成本或延迟超出应用预算；
* 原始响应与规范化输出无法对应。

条件允许时，应通过路由配置回滚，而不是临时紧急部署代码。在新路径经历峰值负载和少见输入类别之前，保留旧适配器。

## 最终结论

LLM API 迁移后的结构化 JSON 失败，通常来自契约不匹配、提取错误或不完整的流式处理，并不能直接证明新模型无法遵循指令。应比较线上请求负载、缩减 schema、统一响应格式、在应用边界进行验证，并在切换流量之前重放有代表性的测试集。兼容 API 可以简化迁移，但明确的能力测试仍是保护生产行为的可靠方法。

## FAQ

### 为什么同一个 JSON 提示词在一个模型上有效，换一个模型就失败？

提示词措辞只是其中一层。不同模型和服务商在原生 JSON 模式、schema 支持、分词、拒答行为以及约束执行严格程度上都可能不同。

### JSON 模式等同于 JSON Schema 强制约束吗？

不等同。JSON 模式可能只保证语法有效，而 schema 强制约束还会限制键名、类型、必填字段，有时也会限制额外属性。

### 我应该逐 token 解析流式 JSON 吗？

通常不应该。应缓存完整的结构化负载，或使用服务商文档规定的结构化流式事件，然后在最终事件到达后进行验证。

### OpenAI 兼容 API 能彻底消除迁移工作吗？

它可以减少传输层和 SDK 的改动，但仍需验证模型特定的 schema 功能、结束原因、工具调用格式和错误行为。

### 验证失败时应该怎么办？

保存原始响应、对失败进行分类，只重试安全的瞬时错误，并通过有次数上限的验证步骤进行确定性修复，不能默默接受格式错误的数据。

### 最快的预生产测试是什么？

在两个技术栈上重放一组固定的正常、歧义、对抗、拒答、长上下文和流式请求，然后逐字段比较验证后的输出。
