1. 为什么 ChatGPT App 的发布流程比普通部署更复杂
在上一部分中我们讨论了如何观测 App 的质量与稳定性(日志、指标、SLO)。现在来看看,如何设计发布流程,确保这些指标不会在每次部署时被破坏。
在普通 Web 应用中,一切相对直观:你部署了新的后端和前端——用户刷新页面后就使用新版本。如果出了问题,往往可以直接回滚部署。
在 ChatGPT App 中技术栈更“狡猾”。至少有四个层次以不同节奏演化:
- 清单和工具模式(MCP tools / OpenAPI);
- 基础设施:Next.js 应用与 MCP/Agents/ACP 服务器;
- System‑prompt 及其他提示词;
- 数据:product feed、设置、配置。
问题在于,模型活在它自己的“心智世界”。聊天上下文可能持续数小时甚至数天。清单与 tools 描述由 OpenAI 侧加载并被缓存,在现有对话中不会立刻更新。如果你直接改了工具的签名(例如从 input‑schema 中移除字段),那么在旧对话里模型仍会发送旧的 payload,而你新的后端会拒绝它。最终——出现 400 错误、聊天里出现奇怪回答,用户非常不开心。
因此,在 ChatGPT App 的世界里,“发布”并不等于“部署一个新的 Docker”。而是对多个层次的协同修改,配合谨慎的版本管理、功能开关与可回滚性。
2. 版本矩阵:到底要给什么做版本
不要只看“App 1.3 版本”,而是同时看各层的版本矩阵。以 GiftGenius 为例,大致如下。
| 层 | 版本化什么 | 示例值 | 存放位置 |
|---|---|---|---|
| App / Next.js | 代码与构建 | |
package.json,Git 标签 |
| MCP / API 接口 | tools 集合及其模式 | |
代码中的常量、注解 |
| System / prompts | System‑prompt、辅助提示、示例 | |
独立文件 + 元数据 |
| Commerce / ACP / feed | product feed 与 ACP 合同的格式 | |
数据仓库中的 schema |
注意,这是不同的维度。你可以发布应用版本 1.4.2,其中 tools 的 schema 仍是 v1.7,而 prompt 升级到 v3.2。这在日志中都应该可见;这样之后更容易排查为什么在 prompt v3.2 之后 checkout 转化忽然下滑。
对代码而言,使用语义化版本(SemVer)很方便:MAJOR.MINOR.PATCH。MAJOR —— 破坏性变更,MINOR —— 向后兼容的新功能,PATCH —— 缺陷修复。对接口类版本(schema、prompts)也类似:MAJOR 表示 breaking change。
特别需要单独标注 tools 的接口版本。对 LLM 来说至关重要:如果你修改了工具契约,而模型“以为”契约还是旧的,问题就来了。因此常见策略是:在 MINOR 版本里只添加新的可选字段,不重命名/删除旧字段;破坏性变更只通过新工具进行,例如 suggest_gifts_v2。
现在我们按层拆分了版本,接着看看这些版本在发布周期中如何“流转”——从 dev 到 prod。
3. GiftGenius 的基础发布流程
先约定阶段。你很可能已有环境(在部署模块中提过),现在从发布的视角来看它们。
通常会划分:
- dev —— 本地开发、ChatGPT 的 Dev Mode、隧道;
- staging —— 与生产尽量一致:相同类型的数据库、MCP/ACP,但使用测试数据与沙箱支付;
- prod —— 生产环境,即已连接生产 ChatGPT App / Store 列表页的环境。
流程可以像这样:
flowchart TD A[Dev: feature/* 分支] --> B[PR → main] B --> C[CI: unit + contract + lint] C --> D[Deploy to Staging] D --> E[Smoke/E2E + 手动验证] E --> F[Deploy to Prod] F --> G[通过指标和日志进行观察]
每一步都要加“保险丝”。在 dev 环境中开发者可以自由尝试,但每次合并到 main 都会触发 CI,运行 unit/contract 测试。如果一切 OK——自动部署到 staging。在 staging 运行简短的 E2E/冒烟用例:例如一次完整的礼物推荐流程和在测试支付中的“假”结账。
只有在这之后才按下 prod 部署按钮。最好——带上版本标注与指向 CHANGELOG 的链接。进入生产后,继续观察 p95、错误率以及业务指标(从推荐到 checkout 的转化)。如果发布后指标下滑——你需要一套清晰的回滚计划,下面会谈。
4. 发布说明与变更记录:到底记录什么
如果没有发布说明,一个月后你会盯着图表:“为什么三周前我们的 checkout p95 翻倍?”然后回答自己“我也不知道,但当时我们发了个大版本”。这不是好策略。
通常至少有两类记录。
内部 changelog——偏技术。面向开发者、SRE 以及所有要读代码的人。记录新增了哪些特性、修复了哪些缺陷、是否有破坏性变更。格式可以遵循 Keep a Changelog:分为 Added、Changed、Fixed、Removed。
对外发布说明(release notes)——面向用户与 Store 的可读说明。没必要写“migrate MCP SDK 0.4 → 0.5”;更好写成“新增感恩节(Thanksgiving)推荐”、“修复了个别礼物无法加入购物车的罕见问题”等。
GiftGenius 的 CHANGELOG.md 片段示例:
## [1.4.0] - 2025-11-20
### Added
- 新增 tool `suggest_gifts_v2`,支持兴趣标签。
- 新 system-prompt 的 A/B 测试(flag: GG_PROMPT_V3)。
### Changed
- 改进了 ACP checkout 的错误处理(更友好的提示)。
### Fixed
- 修复了一个导致无图片商品被从推荐中隐藏的 bug。
把 prompt 的版本也放在旁边很有用:哪怕只是 system‑prompt 文件的提交哈希,日后你就能回溯:“啊哈,在 prompt b3f9c2d 之后,用户更少点击‘购买’了。”
5. SDK 与规范迁移:如何在快速演进的生态里生存
围绕 Apps SDK、MCP 与 Agents 的生态变化很快:新的 SDK 版本、MCP 协议变化、ACP 更新等。这是好事(能力增强),但也痛苦:也可能很快“被搞崩”。
固定版本与“不要在发版当天升级”
第一条简单建议:固定依赖版本。不要用 ^0.4.0,而是用 0.4.0。对那些经常改 API 的 SDK(MCP/Agents/Apps SDK)尤其重要。我看过一些研究,它们特别提到“SDK fatigue(SDK 疲劳)”——频繁不兼容升级带来的疲惫,尤其是那些保留了飘逸版本(比如 ^0.4.0)的人,某天 npm install 后突然遇到一堆错误。
小例子:
// package.json(节选)
{
"dependencies": {
"@modelcontextprotocol/sdk": "0.4.2",
"@openai/applications-sdk": "0.3.1"
}
}
第二条建议:不要把业务功能与大型 SDK 迁移绑在同一次发布里。如果需要把 MCP SDK 从 0.3.x 升到 0.4.x,最好做成单独的技术发布:先升级 SDK、跑测试并稳定,再上新的结账流程。
SDK 迁移策略
通常合理的计划是:
在 dev 环境创建单独分支 upgrade/mcp-sdk-0.4。升级依赖,修改代码,跑完所有 unit/contract 测试,并在本地通过 Dev Mode 跑通 GiftGenius 的主流程。
然后把该分支部署到单独的 staging URL,跑 E2E/冒烟测试。甚至可以做个小型压测:连续调用 suggest_gifts 50–100 次。
如果一切正常——合入 main,部署到主 staging,再跑一次冒烟,然后才进生产。
如果不行——你有显而易见的回滚:要么不合并该分支,要么回退它。这正是把 SDK 迁移与产品发布分离的意义。
tools 与 API 模式迁移
最让人头疼的是接口变更:模型不会立刻知道它们。相关研究特别强调一个重要原则:“只扩展,不破坏”(additive‑only)。如果你需要在 suggest_gifts 中新增参数 interests: string[],请把它做成可选,而非必填;旧场景将继续工作。
Zod 输入模式的演进示例:
// v1
const suggestGiftsInputV1 = z.object({
recipientName: z.string(),
budget: z.number()
});
// v1.1(添加了可选字段)
const suggestGiftsInputV1_1 = suggestGiftsInputV1.extend({
interests: z.array(z.string()).optional(),
occasion: z.string().optional()
});
请注意:我们不动现有字段,只做扩展。
如果确实需要破坏契约(例如用 minBudget + maxBudget 替代 budget),最好新增一个工具 suggest_gifts_v2,并在描述中说明这是改进版本。旧 API 可以标为 deprecated,待确认模型与用户都迁移后再逐步关闭。
数据迁移:product feed 与 ACP
我们已经谈了 SDK 与 tools 模式的迁移。Product feed 也是契约。如果你更改了 SKU、货币、在地化的格式,就需要协同推进:同时更新 feed 的模式、MCP/ACP 中的处理,以及任何预处理器。ChatGPT App 的 commerce 文档中指出,feed 中的错误(损坏字段、重复、离谱价格)即便在代码完美时也会严重伤害推荐质量。
典型做法:
- 先把新字段作为可选添加到 feed 模式中,并让 GiftGenius 在存在这些字段时学会使用它们。
- 再更新构建 feed 的流水线,让它开始填充这些字段。
- 运行 feed 校验(针对数据的 contract 测试),仅在通过后,才在逻辑中开始依赖这些字段。
6. Feature flags:将部署与发布解耦
功能开关(feature flags)是 ChatGPT App 世界中生存的关键工具之一。核心思想很简单:你可以先部署代码,但不一定立刻打开新功能。最开始它“躲在开关后面”——只对开发者可见,或对1%用户可见,或者完全关闭。
这在以下场景尤为重要:
- 上线新推荐算法;
- 更换 system‑prompt(这可能显著改变模型行为);
- 接入昂贵或缓慢的工具;
- 试验新的结账流程。
用环境变量实现的简单开关
最简实现可以用环境变量:
// lib/features.ts
export const isNewRecoEnabled =
process.env.NEXT_PUBLIC_GG_NEW_RECO === "1";
然后在 MCP 工具的代码里这样用:
if (isNewRecoEnabled) {
return runNewRecoAlgorithm(input);
}
return runOldRecoAlgorithm(input);
这种做法的优点是简单。缺点是运行时切换较麻烦:需要部署新环境,或至少重新初始化。
带上下文的集中式 helper
更成熟的做法是提供一个集中式 helper,它不仅考虑全局开关,还会考虑用户上下文(租户、分群、A/B 组)。
// lib/featureFlags.ts
type Feature = "new-reco" | "checkout-v2";
type Context = { userId: string; tenantId?: string };
export function isFeatureEnabled(
feature: Feature,
ctx: Context
): boolean {
// 这里可以接入逻辑:env、数据库、外部开关服务
if (feature === "new-reco") {
return process.env.GG_NEW_RECO === "1";
}
return false;
}
在 MCP handler 中:
const enabled = isFeatureEnabled("new-reco", { userId });
const result = enabled
? await runNewReco(input)
: await runOldReco(input);
如果将来接入外部开关服务(LaunchDarkly、Statsig 等),只需要更改 isFeatureEnabled 的实现,而不是改整套业务代码。
GiftGenius 的几个场景
A/B 测试 system‑prompt。 你创建 PROMPT_V2,只对10%、且 tenantId 在某名单内的用户开启。MCP 工具与小部件不变,你对比转化指标。
昂贵工具的一键关闭(kill‑switch)。 比如你做了一个会调用外部复杂服务的工具(例如某个昂贵的推荐 ML 模型)。如果外部服务开始大量失败或突然涨价,你希望一秒钟内把它关掉,而不需要停掉整个 GiftGenius。功能开关是最简单的方式。
新的结账流程的渐进式发布。 你先只对公司员工和少量信任客户启用 checkout v2。如果指标正常——逐步扩大覆盖,直到对所有人启用。
7. 回滚:当一切“起火”时如何快速退回
即便测试与开关都做得很好,仍然会出问题。关键是你要有清晰策略:在发现错误激增或指标下滑后的前5–15分钟内该怎么做。
快速回滚代码
如果问题是纯技术性 bug(NPE、变量不对、外部服务 URL 错误),通常回滚到上一个部署即可。例如在 Vercel 上可以瞬间回滚到上一次 deployment。
你的任务是始终知道哪个 deployment 对应哪个版本,以及如何回退。理想状态是在面向值班人员(on‑call)的 README 里说明:“如果 1.4.0 发布后全线告警,回滚到 deployment X。”
第二个快手段是功能开关。如果只有新功能(如 checkout-v2)出问题,先用开关关闭它通常比回滚整个发布更快。
高风险变更:清单与模式
清单与模式更棘手。如果你向 Store 提交了新的、但模式不正确的清单,并且已经被 OpenAI 批准,回滚可能要几天。原因很简单:每次清单修改也要经过 OpenAI 审核。各类 Store 的分析都明确指出,清单与模式的变更属于“高风险”发布,需要格外谨慎地准备与测试。
因此最好区分:
- 安全发布:MCP/Next.js 代码变更、提示词修改、被开关隐藏的新功能;
- 高风险发布:tools 列表、input/output 模式、ACP 契约的变更。
“高风险”发布应单独推进,加做额外检查,并且可能先只通过 Dev Mode 与 staging App 验证(不立即发布到 Store)。
数据与 feed 的回滚
数据更麻烦(也更痛)。如果你把 product feed 迁移到新模式时删除了旧字段,想完全回到过去并非总是容易。因此数据迁移应当是幂等且可逆的:要么保留旧副本,要么分两步迁移(先双写/双存,再切换读取)。
简单策略——并行保留新旧字段一段时间,并通过功能开关在它们之间切换。如果出问题——直接切回旧读路径。
8. GiftGenius 的发布流程小方案
把前文(版本、SDK 迁移、功能开关、回滚)整合成一个可操作的方案。
假设你要发 1.4.0,其中:
- 新增工具 suggest_gifts_v2,扩展了输入;
- 对部分用户启用新的 system‑prompt;
- 把 MCP SDK 从 0.3 升到 0.4;
- 修改 product feed 的格式(新增字段 tags)。
一个合理的计划如下。
先做一个单独的技术发布 1.3.1:升级 MCP SDK + 最小必要的代码调整,不改模式、不上功能。跑 CI、staging、冒烟测试。如果一切稳定——先运行几天。
然后开分支 feature/reco-v2。在里面新增 suggest_gifts_v2 工具(保留旧的 suggest_gifts)。它的输入模式仅向“添加可选字段”的方向扩展。准备新的 system‑prompt,但用开关 GG_PROMPT_V3 包裹。ACP/feed 中新增 tags 字段为可选,读取逻辑在缺失时也能工作。
在 CI 中补充若干 contract 测试:suggest_gifts_v2 同时接受旧与新 payload;带 tags 的 feed 合法;缺少 tags 的旧记录也不会弄崩服务端。
合入 main 后:
- CI 跑 unit/contract;
- staging 上通过新 tool 跑 1–2 个 E2E 用例;
- 仅对测试租户通过功能开关开启新 prompt 和新 tool。
观察指标:工具的 p95、错误率、checkout 转化。如果一切 OK——逐步扩大开关覆盖,直到全量。只有在确认稳定后,才(如有需要)为 Store 更新清单,并更新应用的推广描述。
如果过程中某处崩了——你知道如何回退:要么手动关开关,要么回滚部署,要么在极端情况下回退清单版本(但最好不要走到这一步)。
9. ChatGPT App 发布流程中的常见错误
错误 1:只有一个笼统的“App 版本”,没有版本矩阵。
当你只有“GiftGenius v1.4”,却没有记录 tools 模式、提示词与 product feed 的版本时,你就无法回答:“到底是哪一次变更导致了 checkout 下滑?”请按层拆分版本,并在结构化日志中记录它们。
错误 2:tools 做了破坏性变更却没有新名称/新版本。
最痛的坑:在 input 模式里重命名或删除了字段,却不改工具名。在旧聊天里模型仍发旧 payload,后端返回 400,GPT 开始“幻觉”,用户一头雾水。任何破坏性变更都应通过新工具(foo_v2)或新 API 版本来做,并在过渡期保留旧接口。
错误 3:在做功能的路上顺手升级 SDK。
经典场景:你在做一个新业务功能,同时把 @modelcontextprotocol/sdk 从 0.3 升到 0.5,“图个新鲜”。结果出问题时,不知道是新代码、 新模式,还是新 SDK 的锅。SDK 迁移最好做成独立技术发布,配清晰测试计划与回滚路径。
错误 4:没有功能开关和即刻的 kill‑switch。
把新推荐算法直接推给100%用户很刺激,直到它开始产出奇怪结果或压垮外部服务。没有功能开关时,你唯一的杠杆就是回滚整个发布,这可能牵连十来个无辜改进。至少用环境变量或小型配置做个简单开关。
错误 5:寄希望于“瞬时”的清单更新。
常见误解——认为只要你改了 tools 或 openapi.yaml,模型就会立刻了解新模式。实际上,清单和 tools 描述会被缓存,而且在已打开的对话中会长期存在。忽视这一点会导致隐蔽 bug:新对话一切正常,旧对话报错。请在考虑这种行为的前提下规划模式变更,并在发布到 Store 前通过 Dev Mode 与 staging 充分测试。
错误 6:没有清晰的回滚计划与发布文档。
如果团队中没人能回答“如何在5分钟内回滚?”、“我们要回到哪个版本?”、“如何回到旧的 feed 模式读取?”——那就等于没有回滚。值班人员应有简短但具体的剧本:按哪些按钮、改哪些变量、到哪里确认回滚生效。
错误 7:“无声”的发布,没有发布说明,也没有与指标关联。
不写任何 changelog 就发布,几个月后你就会靠猜。p95 突然升高、转化骤降时,你会问:“那会儿究竟改了啥?”养成至少写最小发布说明,并把它与部署日期和版本关联的习惯,这不仅方便质量审计,也能让整个团队的工作更顺畅。
GO TO FULL VERSION