CodeGym /课程 /ChatGPT Apps /发布流程——版本、变更记录、SDK/spec 迁移、feature flags、回滚

发布流程——版本、变更记录、SDK/spec 迁移、feature flags、回滚

ChatGPT Apps
第 17 级 , 课程 3
可用

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 代码与构建
giftgenius-app 1.4.2
package.json,Git 标签
MCP / API 接口 tools 集合及其模式
tools-schema v1.7
代码中的常量、注解
System / prompts System‑prompt、辅助提示、示例
prompt v3.1
独立文件 + 元数据
Commerce / ACP / feed product feed 与 ACP 合同的格式
feed v2, acp v1.3
数据仓库中的 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:分为 AddedChangedFixedRemoved

对外发布说明(release notes)——面向用户与 Store 的可读说明。没必要写“migrate MCP SDK 0.40.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 50100 次。

如果一切正常——合入 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 中的错误(损坏字段、重复、离谱价格)即便在代码完美时也会严重伤害推荐质量。

典型做法:

  1. 先把新字段作为可选添加到 feed 模式中,并让 GiftGenius 在存在这些字段时学会使用它们。
  2. 再更新构建 feed 的流水线,让它开始填充这些字段。
  3. 运行 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. 回滚:当一切“起火”时如何快速退回

即便测试与开关都做得很好,仍然会出问题。关键是你要有清晰策略:在发现错误激增或指标下滑后的前515分钟内该怎么做。

快速回滚代码

如果问题是纯技术性 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 跑 12 个 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/sdk0.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 突然升高、转化骤降时,你会问:“那会儿究竟改了啥?”养成至少写最小发布说明,并把它与部署日期和版本关联的习惯,这不仅方便质量审计,也能让整个团队的工作更顺畅。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION