把模型名从代码里抽出来:30 分钟做一次下线演练

2026-09-22 工程实践模型迁移运维可复制

同一天里我写了两条关停下线的情报:OpenAI 的 Videos API 和 Sora 全家 9 月 24 日停,DeepSeek 那边公告说 9 月 14 日下线 V4 Pro、结果今天定价页上它还在。两件事的方向相反,指向的却是同一个动作——你的代码必须能在不改业务文件的前提下换模型名。

做不到这一点的后果很具体:厂商一纸公告下来,你要么在几小时里满仓库改字符串,要么眼睁睁看着线上报错。

第一步:摸清家底

grep -rnE "(gpt-[0-9]|claude-|gemini-[0-9]|deepseek-|sora-|qwen-|veo-|kimi-)" \
  --include="*.ts" --include="*.tsx" --include="*.py" --include="*.json" --include="*.yaml" \
  . | grep -v node_modules

跑完你会吓一跳。除了真正的调用点,命中通常还来自三类地方:

  • 提示词里写死的模型能力描述(“你运行在 GPT-5 上”这种),换模型后这句话就成了谎言
  • 测试 fixture 里存着的模型名,它不会报错,只会让你以为测试覆盖了
  • 环境变量的默认值(DEFAULT_MODEL = "gpt-5.6-sol"),这是最阴的一种,因为它看起来已经”配置化”了

第二步:集中登记

建一个文件,全仓库只有它能出现模型名字符串:

// src/models.ts
const ALLOWED = new Set([
  'deepseek-flash',
  'deepseek-v4-pro',
  'veo-3.1-fast',
  'text-embedding-3-small',
]);

export function pick(envKey: string, fallback: string): string {
  const name = process.env[envKey] ?? fallback;
  if (!ALLOWED.has(name)) {
    throw new Error(
      `[models] ${envKey}="${name}" 不在登记表里。可用的:${[...ALLOWED].join(', ')}`
    );
  }
  return name;
}

export const MODELS = {
  chat: pick('MODEL_CHAT', 'deepseek-flash'),
  video: pick('MODEL_VIDEO', 'veo-3.1-fast'),
  embed: pick('MODEL_EMBED', 'text-embedding-3-small'),
};

关键是 MODELS 在模块加载时就求值。好处是:模型名错了,进程在启动阶段就死,而不是等到第一个用户请求才炸。

业务代码里全部改成 MODELS.chat,不要留任何裸字符串。

第三步:跑一次演练

MODEL_CHAT=__shutdown_drill__ npm run smoke

期望的结果只有一种:5 秒之内失败,错误信息第一行包含 __shutdown_drill__ 和 MODEL_CHAT 两个词。

如果它跑到了第一个请求才报错,说明 pick 没在入口被调用,白名单形同虚设。如果错误信息里没有配置项名字,说明你还得去翻日志才能知道该改哪儿——那也算没过。

再跑一次正向的:

MODEL_CHAT=deepseek-v4-pro npm run smoke

不用改任何文件,行为就变了。这一步能过,下次真的关停时你要做的就是改一个环境变量。

验收标准

  1. 上面那条 grep 命令跑完,只剩 src/models.ts 一个文件命中。
  2. MODEL_CHAT=__shutdown_drill__ npm run smoke 在 5 秒内失败,第一行报错同时包含 __shutdown_drill__ 和 MODEL_CHAT。
  3. 换模型只改环境变量,git status 是干净的。
  4. ALLOWED 里每一个名字旁边有一行注释,写明它用来干什么、官方定价页和官方弃用页的 URL。

第 4 条最容易被跳过,也最值钱。真到关停那天,你要回答的不是”换成哪个模型”,而是”这个替换在我们的 workload 上是贵了还是便宜了”,那时候没 URL 就得重新搜一遍。

维护成本

一个月 10 分钟。建议挂在每月第一个周一:跑一遍 grep,把新出现的模型名补进登记表,顺手打开官方弃用页看一眼有没有新日期。关停这种事从来不是突然发生的,厂商基本都提前几个月写在文档里,只是没人去看。

顺带一句:这套做法对 API 端点一样适用。今天停的是 /v1/videos,明天可能是别的。把 base URL 也放进 models.ts,改名 providers.ts 也行——名字不重要,重要的是全仓库只有一处知道你依赖了谁。