模型字符串是代码库中最危险的常量。团队往往将其视为一个简单的标签,但它却决定了哪个检查点(checkpoint)进行响应、调用成本是多少,以及请求是否能够成功。因此,AI 模型的弃用和版本控制应纳入发布流程中,而不是作为一次性的启动决策。本指南使用 TokenLab 的公共模型 API(观察于 2026-10-03),展示你可以自动检查的内容、必须在其他地方验证的内容,以及当 ID 停止工作时该怎么做。
关键要点
- 生命周期数据存在于模型对象中。请从 Get a Model(观察于 2026-10-03)读取
tokenlab.lifecycle,而不是从博客文章中复制日期。 - 我们在 2026-10-03 检查的所有五个模型均为
active状态,且没有弃用或停用日期。我们的证据中不包含实际已弃用的模型,因此下文中的失败路径是记录在案的行为,而非我们观察到的实际事件。 - 缺失的模型会返回
model_not_found,但不同端点的状态码有所不同。请根据code进行分支判断,切勿仅根据状态码判断。 - 固定精确的 ID 并将其保留在一个配置层中。TokenLab 表示它不会静默替换你发送的模型。
- 回退(fallback)方案必须是经过测试且预先批准的选择。不同模型之间的能力、请求格式、限制和价格各不相同。
AI 模型弃用与版本控制在 API 中的体现
Get a Model 参考文档(观察于 2026-10-03)指出 lifecycle 字段涵盖了可用性、发布日期、弃用日期和替换模型。List Models 参考文档(观察于 2026-10-03)为每个列表项增加了 tokenlab.lifecycle、tokenlab.aliases、tokenlab.commercial 和 tokenlab.badges。同一页面还列出了 tokenlab.accepted_request_formats 和 token 限制,这对任何模型切换都至关重要。
以下是我们于 2026-10-03 从 claude-sonnet-5 的实时模型 API 读取的内容。这是观察值的精简呈现。我们的证据记录的是数值,而非原始嵌套键名,因此请根据实时响应确认确切的键。
GET https://api.tokenlab.sh/v1/models/claude-sonnet-5
category: chat
lifecycle stage: active
released: 2026-06-30
replacement model: None
deprecated_at: None
retired_at: None
max input tokens: 1000000
max output tokens: 128000
accepted formats: anthropic_messages, openai_chat_completions
将五个生命周期概念映射到这些字段,如下所示:
| 概念 | 观察到的响应字段 | claude-sonnet-5 的值 |
|---|---|---|
| 阶段 | lifecycle stage | active |
| 发布 | released | 2026-06-30 |
| 弃用 | deprecated_at |
None |
| 停用 | retired_at |
None |
| 替换 | replacement model | None |
空值并不总是意味着有问题。对于 glm-5.2 和 gpt-5.5,实时 API(观察于 2026-10-03)显示 released 为 None,而阶段为 active。检查逻辑应在 deprecated_at 已设置、retired_at 已设置或阶段非 active 时失败。不应因缺失发布日期而失败。
我们还读取了 deepseek-v4-pro 和 deepseek-v4-flash(均于 2026-04-24 发布,均为 active,无替换模型)。这使得五个模型都没有弃用信号。已弃用模型的替换字段是我们无法观察到其被填充的唯一值。
当依赖项失效时触发失败的脚本
下面的检查仅使用文档化的 GET /v1/models/{model} 端点,由于目录是公开的,因此无需 API 密钥。如果 ID 缺失、设置了 deprecated_at 或 retired_at、阶段非 active,或者模型不再接受请求格式,它将失败。
#!/usr/bin/env python3
"""当依赖的模型 ID 缺失、弃用或停用时触发失败。"""
import json
import sys
import urllib.error
import urllib.request
BASE = "https://api.tokenlab.sh/v1/models/"
# 模型 ID -> 我们代码实际发送的请求格式
DEPENDENCIES = {
"gpt-5.5": "openai_chat_completions",
"claude-sonnet-5": "anthropic_messages",
"deepseek-v4-pro": "openai_chat_completions",
}
def fetch(model_id):
req = urllib.request.Request(BASE + model_id, headers={"Accept": "application/json"})
try:
with urllib.request.urlopen(req, timeout=15) as resp:
return resp.status, json.load(resp)
except urllib.error.HTTPError as exc:
try:
body = json.load(exc)
except ValueError:
body = {}
return exc.code, body
def check(model_id, fmt):
try:
status, body = fetch(model_id)
except urllib.error.URLError as exc:
return [f"could not verify: {exc.reason}"], {}, []
if status in (400, 404):
return [f"missing: HTTP {status}"], {}, []
if status != 200:
return [f"could not verify: HTTP {status}"], {}, []
info = body.get("tokenlab", {})
life = info.get("lifecycle") or {}
problems = []
if not life:
problems.append("no lifecycle block returned")
stage = life.get("stage") # 根据实时响应确认确切键名
if stage not in (None, "active"):
problems.append(f"stage={stage}")
for key in ("deprecated_at", "retired_at"):
if life.get(key):
problems.append(f"{key}={life[key]}")
if fmt not in info.get("accepted_request_formats", []):
problems.append(f"format {fmt} not accepted")
return problems, life, info.get("aliases", [])
def main():
failed = False
for model_id, fmt in DEPENDENCIES.items():
problems, life, aliases = check(model_id, fmt)
if problems:
failed = True
print(f"FAIL {model_id}: {'; '.join(problems)}")
if life:
print(f" lifecycle: {json.dumps(life)}")
else:
print(f"ok {model_id} aliases={aliases}")
sys.exit(1 if failed else 0)
if __name__ == "__main__":
main()
请在 CI 和夜间计划任务中运行此脚本,因为模型状态可能会在无需你提交代码的情况下发生改变。想象一下,如果你的最后一次部署是在一周前,而夜间作业在周二退出并返回 1。这就是重点:失败应出现在流水线中,而不是客户的聊天窗口中。脚本在失败时会打印整个生命周期块,以便人工查看替换值,而无需猜测键名。
停用或未知模型返回的内容
证据记录的是未知和不可用模型,而不是单独标记为“已停用”的响应。因此,在我们验证之前,我们将停用视为同一类错误。以下是文档内容,均观察于 2026-10-03:
| 位置 | 响应 | 来源 |
|---|---|---|
GET /v1/models/{model} |
404 model_not_found |
Get a Model |
| 使用拼写错误或不可用模型的聊天请求 | 400 model_not_found,可选包含 did_you_mean, suggestions, hint |
Errors agents can act on |
| 所选交付层(Delivery tier)无供应 | 503 all_channels_failed 或 503 delivery_tier_unavailable, retryable: false |
Errors agents can act on |
| 密钥缺乏模型权限 | 403 model_not_allowed |
Handle API errors |
不同端点的状态码不一致,因此一个修复方案很重要:我们源文章的回退代码将 404 和 410 视为“标识符已消失”。这对于聊天请求是错误的。文档化的聊天响应是 400,错误指南建议通过 HTTP 状态码和 code 来处理错误。请基于 code == "model_not_found" 来设计你的逻辑。
例如,文档显示拼写错误的 ID 会返回 did_you_mean: "gpt-5.6-terra"。你的客户端应按以下顺序响应:
- 不要重复发送未更改的请求。错误指南将
400、403和404标记为不可重试。 - 将
did_you_mean视为建议。仅当你的产品已获得更改模型的权限时才进行切换。 - 如果 503 响应体显示
retryable: false,请停止重试。文档指出相似的名称并不证明可用性。 - 将响应头中的 Request ID 与模型及你自己的作业 ID 一起保存。
- 提醒人工。固定的 ID 消失是一个发布事件,而不是瞬时错误。
固定精确 ID 或遵循别名
最佳实践指南(观察于 2026-10-03)建议在每个请求中显式指定模型 ID,不要静默切换模型。它警告称,替换模型可能会改变质量、价格、上下文长度、工具或媒体支持。错误指南补充说,TokenLab 不会静默替换你发送的模型。
| 策略 | 行为 | 主要风险 |
|---|---|---|
固定精确 ID(如 gpt-5.5) |
你获得该模型,或收到错误 | 如果 ID 被移除,会导致硬故障 |
| 遵循别名 | 名称随时间推移可能指向新位置 | 行为和成本可能在无需代码更改的情况下发生变化 |
| 配置中的内部路由名称 | 代码请求 primary_chat;配置将其映射到精确 ID |
你需要自行拥有并测试映射关系 |
关于别名,我们发现了一个缺口。List Models 参考文档称每个项目都有 tokenlab.aliases。我们拥有的五个实时摘录(claude-sonnet-5, deepseek-v4-pro, deepseek-v4-flash, glm-5.2, gpt-5.5)中没有一个显示别名值。我们无法告诉你这些模型今天有哪些别名。上面的脚本会为每个依赖项打印 aliases,请运行它并阅读输出。
另外两个目录事实很有帮助。2026-10-02 的目录将版本列为单独的精确 ID,例如 claude-sonnet-4-6, claude-sonnet-5 和 claude-sonnet-5-5。并列显示并不意味着一个是另一个的继承者。replacement 值是该关系唯一应来源的地方。一个 ID grok-voice-latest 的名称中带有“latest”。我们没有证据表明它继承自什么,因此在信任它之前请检查其模型页面。市场风格的前缀 ID 不是 TokenLab 别名。迁移指南称 TokenLab ID 不携带 OpenRouter 提供商前缀,并建议将你自己的 LiteLLM 别名与 TokenLab ID 分开。
针对消失的固定模型的回退模式
将回退视为你预先测试过的第二个固定模型。按调用点进行选择,记录哪个模型服务了每个答案,并使用相同的夜间脚本进行验证。在配对之前比较这两个模型:
| 模型 (实时 API, 观察于 2026-10-03) | 每百万 token 输入/输出价格 | 最大输入/输出 token | 请求格式 | 列出的能力 |
|---|---|---|---|---|
gpt-5.5 |
$1.50 / $9.00 | 1,000,000 / 128,000 | openai_chat_completions, openai_responses |
coding, json-mode, prompt-cache, reasoning, tool-use, vision |
claude-sonnet-5 |
$0.90 / $4.50 | 1,000,000 / 128,000 | anthropic_messages, openai_chat_completions |
json-mode, prompt-cache, reasoning, tool-use, vision |
deepseek-v4-pro |
非高峰 $0.66 / $1.98;高峰 $1.32 / $3.96 | 1,000,000 / 384,000 | anthropic_messages, openai_chat_completions, openai_responses |
json-mode, prompt-cache, tool-use |
这三者的定价均于 2026-10-02T16:53:30.068Z 更新。差异很重要。claude-sonnet-5 不接受 openai_responses,因此基于 Responses 的调用者无法回退到它。deepseek-v4-pro 未列出视觉或推理能力,因此包含图像的请求在那里会中断。DeepSeek 的高峰时段为北京时间 09:00-12:00 和 14:00-18:00。
以下是成本估算及计算过程。假设每日负载为 10M 输入和 2M 输出 token,这是一个说明性数字,而非测量值:
gpt-5.5: 10 × $1.50 + 2 × $9.00 = $15 + $18 = 每天 $33。claude-sonnet-5: 10 × $0.90 + 2 × $4.50 = $9 + $9 = 每天 $18。deepseek-v4-pro非高峰: 10 × $0.66 + 2 × $1.98 = $6.60 + $3.96 = 每天 $10.56。deepseek-v4-pro高峰: 10 × $1.32 + 2 × $3.96 = $13.20 + $7.92 = 每天 $21.12。
这些只是估算值,缓存输入的定价方式不同。下面的代码仅在重试无法解决的错误时进行回退。429 或可重试的 5xx 错误应遵循正常的重试规则。
import logging
import os
import openai
log = logging.getLogger("model-routing")
client = openai.OpenAI(
api_key=os.environ["TOKENLAB_API_KEY"],
base_url="https://api.tokenlab.sh/v1",
timeout=30.0,
max_retries=0, # 我们决定什么适合重复
)
ROUTES = {
"primary_chat": {"model": "gpt-5.5", "fallback": "claude-sonnet-5"},
"coding_agent": {"model": "claude-sonnet-5", "fallback": "deepseek-v4-pro"},
}
def _error(exc):
body = exc.body if isinstance(exc.body, dict) else {}
err = body.get("error", body)
return err if isinstance(err, dict) else {}
def _chat(model_id, text):
resp = client.chat.completions.create(
model=model_id,
messages=[{"role": "user", "content": text}],
)
return resp.choices[0].message.content
def call_model(route, text):
"""返回 (回答的模型, 文本)。记录结果对应的模型。"""
cfg = ROUTES[route]
try:
return cfg["model"], _chat(cfg["model"], text)
except openai.APIStatusError as exc:
err = _error(exc)
code = err.get("code")
gone = code == "model_not_found"
no_supply = (
code in ("all_channels_failed", "delivery_tier_unavailable")
and err.get("retryable") is False
)
if not (gone or no_supply):
raise # 401/402/403/429 等需要不同的修复方式
log.warning("pinned model %s unavailable (%s); using %s",
cfg["model"], code, cfg["fallback"])
return cfg["fallback"], _chat(cfg["fallback"], text)
回退仅跳转一次,不会链式触发。如果回退也失败,则抛出错误并通知相关人员。静默的替换链是另一种形式的漂移问题。
AI 模型弃用与版本控制发布检查清单
在更改生产环境模型 ID 之前运行此清单。它反映了最佳实践指南(观察于 2026-10-03)并增加了生命周期检查。
- 列出生产环境中的每个模型 ID,并命名使用每个 ID 的调用点。一个配置层应能在几秒钟内回答此问题。
- 针对当前 ID 和候选 ID 运行生命周期脚本。读取
deprecated_at,retired_at, 阶段和替换模型。 - 使用 Get a Model 中的
accepted_request_formats,确认候选模型接受你的代码发送的请求格式。 - 对比最大输入和最大输出 token 与你最长的实际请求及输出上限。
- 比较能力。在你的产品依赖视觉、工具使用、json-mode 和推理时,确认候选模型是否支持。
- 根据当前价格重新计算成本,并展示计算过程。对于 DeepSeek,需区分高峰和非高峰。
- 使用你将要发布的精确候选 ID 和格式重放代表性请求。包含一个失败案例,例如未知 ID。
- 验证错误路径。你的客户端应基于
code分支判断,保留 Request ID,且不要重试400,403或404。 - 将更改作为配置编辑发布,并将旧 ID 保留为文档化的回滚值。
- 检查每个响应记录是否存储了实际服务该请求的模型。
- 发布后重新运行夜间脚本。确认回退 ID 也通过检查。
在文档未提及的地方,检查清单就是保障。它们没有弃用提前通知期,也没有通知渠道。请单独验证提供商公告,并检查模型页面上的任何生命周期说明。
常见问题解答
如何判断 TokenLab 模型是已弃用还是已停用?
调用 GET /v1/models/{model} 并读取 tokenlab.lifecycle。检查阶段、deprecated_at、retired_at 以及替换模型。截至 2026-10-03,我们读取的五个模型均为 active,且未设置日期。请再次轮询,因为该值仅反映你询问那一刻的状态。
未知模型 ID 返回什么 HTTP 状态码?
这取决于端点。Get a Model 返回 404 model_not_found,而使用错误模型的聊天请求返回 400 model_not_found。在所选交付层中无供应的模型返回 503。请基于 code 字段进行分支判断。
我应该重试 model_not_found 错误吗?
不应该。重复相同的请求不会改变结果。如果存在 did_you_mean 和 suggestions,请检查它们,但仅在你的产品已获得权限时才切换模型。否则,请使用预先批准的回退方案并提醒你的团队。
如果我的模型消失了,TokenLab 会将我的请求切换到另一个模型吗?
文档称它不会静默替换你发送的模型。请发送显式 ID 并自行处理错误。你的回退方案应是一个经过测试的配置值,而不是对平台的假设。
在进行下一次模型更改之前,请在模型目录中比较当前的 ID、限制和价格。
来源
价格更新于 2026-10-03
- TokenLab Docs: List Models资料更新于 2026-10-03
- TokenLab Docs: Get a Model资料更新于 2026-10-03
- TokenLab Docs: Build a reliable TokenLab integration资料更新于 2026-10-03
- TokenLab Docs: Handle API errors资料更新于 2026-10-03
- TokenLab Docs: Errors agents can act on资料更新于 2026-10-03
- TokenLab Docs: Migration Guides资料更新于 2026-10-03
- TokenLab live model API: claude-sonnet-5资料更新于 2026-10-03
- TokenLab live model API: deepseek-v4-pro资料更新于 2026-10-03
