把模型名从代码里抽出来:30 分钟做一次下线演练
同一天里我写了两条关停下线的情报: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
不用改任何文件,行为就变了。这一步能过,下次真的关停时你要做的就是改一个环境变量。
验收标准
- 上面那条 grep 命令跑完,只剩
src/models.ts一个文件命中。 MODEL_CHAT=__shutdown_drill__ npm run smoke在 5 秒内失败,第一行报错同时包含__shutdown_drill__和MODEL_CHAT。- 换模型只改环境变量,
git status是干净的。 ALLOWED里每一个名字旁边有一行注释,写明它用来干什么、官方定价页和官方弃用页的 URL。
第 4 条最容易被跳过,也最值钱。真到关停那天,你要回答的不是”换成哪个模型”,而是”这个替换在我们的 workload 上是贵了还是便宜了”,那时候没 URL 就得重新搜一遍。
维护成本
一个月 10 分钟。建议挂在每月第一个周一:跑一遍 grep,把新出现的模型名补进登记表,顺手打开官方弃用页看一眼有没有新日期。关停这种事从来不是突然发生的,厂商基本都提前几个月写在文档里,只是没人去看。
顺带一句:这套做法对 API 端点一样适用。今天停的是 /v1/videos,明天可能是别的。把 base URL 也放进 models.ts,改名 providers.ts 也行——名字不重要,重要的是全仓库只有一处知道你依赖了谁。