团队实战分享生产环境AI Agent迁移到GPT-5.6的量化成果;直观展示新模型在性能和成本上的显著优势。
截至今天,Ploy 的 Agent 已经开始运行在 GPT-5.6 Sol 上,这是 OpenAI 今早发布的该模型家族中的旗舰级模型。几个月来,始终没有一款模型达到我们替代 Claude Opus 的标准。GPT-5.6 Sol 做到了。经过正面对比评估后,我们将它设为了所有 Ploy workspace 的默认模型。
Ploy 的 Agent 负责构建和编辑生产环境中的营销网站。它会规划页面、读取代码库、编写组件、生成图片、截取自己的工作成果,并自行判断任务何时完成。我们会使用这套工作负载测试每一款新发布的前沿模型。在 Opus 占据默认模型位置的四个月里——先是 Opus 4.7,后来是 4.8——我们测试过的模型没有一个能击败它。GPT-5.6 是第一个做到这一点的模型。
第一轮 eval 暴露出了几种失败模式,我们会在下文逐一介绍。同时,它也取得了非常出色的结果:完成构建所需的实际时间不到原来的一半,成本降低了 27%,评分则达到或超过了我们原有的默认模型。这些结果足以证明迁移工作值得投入。
我们使用 Vercel 的 AI SDK,但从 Claude Opus 4.8 切换到 GPT-5.6 Sol,依然暴露出了整个技术栈中大量与特定 provider 绑定的假设。不同 provider 在模型填写 tool 参数、缓存 prompt,以及跨轮次重放 reasoning 的方式上都存在差异。
我们首先修复了 eval harness,随后依次处理了 tool schema、prompt caching 和 reasoning replay。
我们的 eval suite 会让生产环境中的 Agent 在 fixture workspace 上运行。它覆盖了数百种场景,从“从零开始构建一个首页”,到“执行这个 clone 请求是否安全”。对于构建类场景,一个视觉 judge 会对照参考设计执行十项二元检查,例如“hero 区域是否为全出血式摄影场景”,以及“主要 CTA 是否为圆角矩形,而不是胶囊形”。我们还会执行内容检查、tool 调用轨迹检查和文件断言。每个失败案例都会结合完整 trace 进行分类排查,其中包括 tool call 和模型输出的文本。
第一次跨模型运行暴露出了 eval harness 中的一个问题。
我们的 tool-call budget 是按照 Opus 的串行调用方式设定的。GPT-5.6 会并行发起调用,因此在一些本来正确解决的案例中,它却超出了这些预算。我们的 eval executor 也不支持批量读取文件;Opus 很少使用这种方式,而 GPT-5.6 经常使用。第一轮运行中,大约三分之一的原始失败来自 harness 的假设,而非模型本身的行为,并且这些失败的分布并不均匀。如果你正在评估一个挑战者模型与现有模型的表现,那么在相信通过率之前,一定要先排查 trace。否则,eval 实际奖励的将是新模型模仿旧模型行为的能力。
有一个 dataset 没有设置自己的 minScore 阈值,因此继承了默认值 1.0,而系统没有对这一回退行为给出任何提示。结果,GPT-5.6 的一个 hero 设计拿到了 0.98 分,却被判定为“失败”;Opus 的另一个案例则在通过每一项单独检查的情况下,仍被判定为“失败”。这两种设计其实都完全说得过去,真正让它们失败的是那个隐式阈值。
修复 harness 后,我们重新运行了 redesign suite。在这套测试中,Agent 需要参照给定设计,重建某个品牌的首页。
GPT-5.6 完成页面的速度提升至 2.2 倍,成本降低了 27%,使用的输出 token 约为原来的一半。它编写的代码也更少。在一组匹配对比中,Opus 生成的 globals.css 长达 17,957 个字符,包含 174 个 CSS 变量,其中大部分是几乎未被使用的色阶。GPT-5.6 只用了 2,508 个字符和 45 个变量,就生成了效果相当、有时甚至更好的渲染页面。
GPT-5.6 很擅长生成干净、网格严谨的布局,但如果没有强力引导,它往往会收敛到这种固定风格。在我们为 Opus 4.8 设计的旧版 harness 中,GPT-5.6 Sol 经常忽略现有设计系统,产出虽然干净、却缺乏特色的通用设计。
我们在这方面所做的改进值得单独写一篇文章。我们的设计和工程团队持续优化 steering,直到 GPT-5.6 达到我们对生产环境品牌一致性的要求。
我们 Agent 的代码 tool 有 25 个顶层参数。其中 action 是必填参数,其余都是可选参数。Claude 只会发送实际使用的两三个参数,并省略其他参数。GPT-5.6 则会在每次调用时发送全部 25 个参数,并为未使用的参数填入一些看似合理的值,例如 offset: 0、timeout: 120000 和 siteId: "00000000-0000-0000-0000-000000000000"。
我们在连续三天的生产环境 code(read) trace 中都观察到了这种模式。
问题并不在于参数太冗长,而在于文件读取实现无法区分一个编造出来的值和真正有意传入的值。它会将 offset: 0 当作实际参数处理,导致 GPT-5.6 的文件读取中有 52% 到 64% 返回空内容。无论读取结果有效还是为空,tool 都会返回 success: true,因此模型无法意识到自己读到的是空白文件。为了弥补这一点,它只能发起更多调用,结果也随之变差。
仅靠 prompt 无法解决这个问题。即使在 tool 描述中明确要求“省略未使用的参数”,模型仍会生成全部 25 个参数。给每个属性添加“OPTIONAL, omit if unused”提示,结果也完全一样。我们在启用 OpenAI strict mode 的情况下测得了相同的行为,而且采用该模式意味着必须从每个 schema 中移除 pattern、format 和数组边界验证。这种行为源于模型生成 function call 的方式,所以我们最终选择修改 schema。
真正有效的修复方案,是在 provider 边界执行一次 schema 转换。对于 OpenAI 系列模型,我们使用 anyOf: [T, null],将每个可选属性改写为必填但可为 null。这样,模型就能为未使用的参数提供一个明确的值。随后,我们会在共享的 tool invocation 边界、进入验证之前移除这些 null,因此不需要修改任何 tool 实现。模型看到的是一个能够表达“未使用值”的 schema,而 tool 收到的输入则与之前保持一致。
// Before: 25 keys, every one carrying an invented value
{ "action": "read", "file_paths": [...], "offset": 0, "timeout": 120000, ... }
// After: 25 keys, 4 real values, 21 explicit nulls (stripped before the tool runs)
{ "action": "read", "file_paths": [...], "offset": null, "timeout": null, ... }
完成修改后,空文件读取的比例从 52% 降至 0%。完成相同工作时,Agent 使用的 tool call 数量也减少了约 30%,因为它不再反复读取空白结果。
两家 provider 都提供所谓的“prompt caching”,但具体实现并不相同。在我们正确处理这些差异之前,GPT-5.6 看起来比 Opus 贵了大约 50%。问题出在我们的缓存配置上,模型定价本身没有问题。
我们 Agent 的 prompt 开头包含大约 29K token 的静态前缀,由 tool schema 和核心 system prompt 组成,并且每个对话都完全相同。在 Claude 上,我们使用 cache_control 标记缓存断点,前缀能够在整个组织范围内共享缓存。任意 workspace 中的任意对话都可以使用同一个共享缓存条目,而且不会受到单个 key 吞吐预算的限制。缓存命中率通常在 92% 到 96% 之间。
GPT-5.6 使用了另一种 OpenAI 缓存模型。早期的 GPT 模型会隐式缓存部分前缀匹配。GPT-5.6 取消了部分前缀匹配,因此隐式缓存现在会根据最新消息,创建覆盖完整 prompt 的缓存条目。一个共享了我们 29K 静态前缀的新对话,对这部分内容的缓存命中率是 0%。每个对话都会按照未缓存费率,重新计费整个前缀。GPT-5.6 还会对每个未缓存的 prompt 收取 1.25 倍的缓存写入附加费,无论应用是否实际使用缓存。
显式缓存机制需要使用 prompt_cache_breakpoint 标记,并且必须提供 prompt_cache_key。这个 key 是缓存身份的一部分,因此内容完全相同但 key 不同的 prompt 不会产生任何缓存命中。每个 key 都会映射到一个 cache node;在 OpenAI 将流量分散到其他拥有独立冷缓存的节点之前,该节点每分钟大约可以承载 15 个请求。
最关键的设计决策是:应该用什么实体来划分 key 的作用域。
按对话设置 key:新对话永远无法命中共享前缀。在这种配置下,我们测得首次调用的缓存命中率为 0%。
使用一个全局 key:所有请求都会被哈希到同一个 cache node。生产流量会超过每分钟 15 个请求的预算,导致请求溢出到冷节点。
按 workspace 设置 key:同一客户 workspace 中的所有对话共享缓存条目,同时每个 key 的流量仍能维持在较低水平。
我们最终上线了 workspace 作用域的 key,并将 system prompt 拆分为多个带断点的层级,与我们已经为 Anthropic 使用的结构保持一致:
request ──► hash(prompt head + prompt_cache_key) ──► cache node (~15 req/min per key)
│
┌──────────────────────────────────────────────────────┴───────────────┐
│ entries on the node, all namespaced by key ws:{workspaceId} │
│ │
│ [ tools + static prefix ]···················· A every session │
│ [ tools + static prefix + workspace context ]·· B same context │
│ [ ····················· + turn 1 + … + latest ] C this session │
└──────────────────────────────────────────────────────────────────────┘
条目 A 可以降低每个 session 首次调用的成本。当 workspace memory 发生变化时,请求无法命中条目 B,但仍然可以命中条目 A,随后再写入新的条目 B。这样只需执行一次与 context 大小相当的写入,而不必重新计费完整的 29K 前缀。条目 C 是 OpenAI 的隐式完整 prompt 链;由于我们的 prompt 严格采用只追加模式,因此它可以在 session 内正常工作。
OpenAI 的 key 分区机制让不同 workspace 无法共享静态前缀。Anthropic 可以共享这个前缀,因为它的缓存以组织为作用域,并且不进行 key 分区。在 GPT-5.6 上,每个 workspace 都需要在每个空闲窗口支付一次 29K token 的冷写入费用,大约为 0.18 美元。这笔成本有明确上限,也可以预测。
修改之后,首次调用的缓存命中率从大约 0% 提升至 83.7%,未缓存输入 token 总量减少了 28%,GPT-5.6 运行每套 suite 的成本也降到了 Opus 以下。我们之前测得的全部成本差距,都源于缓存配置错误。如果其中一个模型从冷缓存开始运行,那么比较两个模型的成本没有任何意义。
reasoning replay 曾导致生产环境中的对话间歇性失败。默认情况下,GPT-5.6 的 Responses API 会通过服务端 item reference 重放上一轮 reasoning,因此我们的对话有时会因 Item 'rs_...' not found 而失败。设置 store: false 后,SDK 会请求加密的 reasoning 内容,并重放自包含的数据块,而不是指向服务端状态的指针。我们还发现,即使应用发送的字节严格采用只追加模式,服务端 reasoning state 也可能改变实际生效的 prompt。
GPT-5.6 于今天发布,目前已经在 Ploy 上线。你可以在 ploy.ai 免费开始使用,让它为你构建一个网站,亲眼看看不到四分钟完成一次构建是什么样的体验。
Ploy 是一个 AI 层,能够规划、构建、发布并优化网站和营销活动。如果你觉得凌晨两点调试 cache-node fan-out 很有趣,我们正在招聘。