Grok 模型 ID 写法与版本陷阱
把一个「能跑」的请求交给同事接手,最常见的交接方式是「照抄我那段代码,模型名别改」。抄的人照做了,请求也通了。三个月后,同一份代码开始给出不一样的账单,回答风格也变了——代码一行没动,唯一变的是模型字符串背后指向的东西。
这一节只解决一个决策:模型名到底该写死到什么程度。
一、能跑通和可复现是两件事
官方模型页把模型写法分成三档,这不是语法糖,而是三种不同的「未来行为」:
| 写法 | 官方给出的行为 | 适合谁 | 代价 |
|---|---|---|---|
<modelname> |
指向该系列当前稳定版 | 个人项目、试用 | 行为与单价随发布漂移 |
<modelname>-latest |
指向该系列当前版本 | 愿意跟进并自测的团队 | 漂移更快,回归压力在你这 |
<modelname>-<date> |
钉住某一次发布 | 线上系统、需要复现结论的场景 | 新版本能力要手动升 |
这里有一个极容易读漏的前提:官方原文写的是「部分模型有别名」(Some models have aliases),并且只对 -<date> 这一档承诺「不再更新」。也就是说,三档里只有带日期的写法能兑现可复现;<modelname> 与 -latest 都会随发布移动。
多数人踩的坑不是「用了别名」,而是不知道自己用了别名。教程里写 grok-4,读者抄过来,抄的那一刻它指向某一版;官方发布新版本之后,同一行代码指向另一版。而价格表是按模型分档的,模型换了,单价与上下文窗口会一起换——你以为什么都没改,其实换了个模型在跑。
更需要注意的是:grok-4.7 这种「官方列表里的 ID」并不自动等于钉住版本。它在形态上属于 <modelname> 那一档,该系列是否设了别名、会不会自动迁移,是逐系列的具体事实,不能从命名规则推出来。想拿到「不再更新」的承诺,要用带日期段的发布 ID,例如 grok-4.20-0309-reasoning。
还有一类更安静的变化。官方模型页写明:grok-4.20 及更新的模型不支持 logprobs 与 top_logprobs,设置了也会被静默忽略。如果你的功能依赖这两个字段,升级之后不会报错,只会拿到空数据。这类「能跑通但跑错」比直接失败更难查。
官方对多数用户的建议其实是用别名,好处是自动获得后续能力。本书的取向相反,是因为主线项目要对外承诺成本与行为一致性:一旦账单可复现比「省心」更重要,取舍就会反过来。两种取向都对,差别在约束条件。
二、先给模型字符串定性
下面这个分类器不联网,只做一件事:把项目里出现的模型字符串按官方规则归到五类,并给出该做的动作。
"""模型 ID 分类器:不联网,按官方别名规则判断一个字符串的「可复现性」。"""
import re
from dataclasses import dataclass
## 官方 models 页当前列出的文本模型(核验 2026-11-04);复核时只改这一处
KNOWN_CONTEXT = {
"grok-4.7": 500_000,
"grok-4.6": 500_000,
"grok-4.5": 500_000,
"grok-4.3": 1_000_000,
"grok-4.20-0309-reasoning": 1_000_000,
"grok-4.20-0309-non-reasoning": 1_000_000,
"grok-4.20-multi-agent-0309": 1_000_000,
"grok-build-0.1": 256_000,
}
## 官方只有 <modelname>-<date> 承诺「不再更新」;这里按四位日期段识别
DATE_SHAPE = re.compile(r"-\d{4}(?:-|$)")
## 形如 grok-4 的写法:官方定义了别名机制,但某个具体别名是否存在要另查
ALIAS_SHAPE = re.compile(r"^grok-\d+(\.\d+)?$")
@dataclass
class Verdict:
model_id: str
kind: str # pinned / series / float / alias_likely / unknown
context: int # 0 表示本次未核到
action: str
def classify(model_id: str) -> Verdict:
if model_id in KNOWN_CONTEXT:
if DATE_SHAPE.search(model_id):
# 带日期段:官方写明不再更新,是唯一能兑现可复现的一档
return Verdict(model_id, "pinned", KNOWN_CONTEXT[model_id],
"钉住某次发布,升级时显式换 ID")
# 在列表里但不含日期段:当前可用,是否随别名漂移要逐系列确认
return Verdict(model_id, "series", KNOWN_CONTEXT[model_id],
"当前可用;若该系列设了别名会漂移,须配回归")
if model_id.endswith("-latest"):
# 官方别名规则里的浮动档:行为随发布变化
return Verdict(model_id, "float", 0,
"换 pinned ID,或接受漂移并补回归用例")
if ALIAS_SHAPE.match(model_id):
# 只说明它「像」系列别名,不替官方断言该别名存在
return Verdict(model_id, "alias_likely", 0,
"到官方 models 页确认该别名,线上改 pinned ID")
return Verdict(model_id, "unknown", 0,
"对照官方 models 页当前列表,别按记忆改拼写")
DEMO = ["grok-4.7", "grok-4.20-0309-reasoning", "grok-4.7-latest",
"grok-4", "grok-4.5-turbo"]
if __name__ == "__main__":
print("| 模型字符串 | 类型 | 上下文 | 动作 |")
print("| --- | --- | --- | --- |")
for item in DEMO:
v = classify(item)
ctx = f"{v.context:,}" if v.context else "未核到"
print(f"| `{v.model_id}` | {v.kind} | {ctx} | {v.action} |")
KNOWN_CONTEXT 是从官方模型页抄录的当前列表,核验日期 2026-11-04。它有两用:判断这个 ID 现在还在不在,以及给后续容量估算提供上下文窗口。复核时只改这一处常量,别把模型名散落到业务代码里。
DATE_SHAPE 与 ALIAS_SHAPE 都只判「形状」。官方说明了别名机制的存在,但某个具体别名是否在售、某个系列有没有设别名,属于列表事实,不是命名规则能推出来的——所以分类器给出的是动作,不是结论。
三、五类字符串的处置口径
五类的处置逻辑不一样:
| 类型 | 含义 | 处置 |
|---|---|---|
pinned |
在列表中且带日期段 | 可复现;升级当变更事件处理 |
series |
在列表中、不含日期段 | 承认它可能浮动,配回归用例 |
float |
以 -latest 结尾 |
换 pinned ID,或接受漂移并加回归 |
alias_likely |
形如 grok-4,像系列别名 |
先确认存在性,再决定要不要留 |
unknown |
既不在列表也不像别名 | 按拼写错或已下架处理 |
三点值得展开:
- 别名的代价是双份的。它换来「自动跟进」,代价是行为与单价同时变。内部工具无所谓,它换来的正是省心;但一个要对外报价的系统就不能接受,因为你的成本函数会跟着官方的发布节奏抖动。这也是本书把
series单独列一类、而不是一律判「可用」的原因。 unknown不等于写错。模型可能刚发布还没进你的常量表,也可能已经下架。把它单独列一类,是为了逼你回去对表,而不是凭记忆改拼写——凭记忆改拼写最常见的产物是grok-4.5-turbo这种查不到的名字,或是把grok-4.20写成grok-4-20这类分隔符错误。- 形状判断不能替代对表。
grok-4.20-0309-reasoning带日期段,能拿到「不再更新」的承诺;grok-4.7形状上只是系列名。两者都在官方列表里,但可复现性不是一回事。
四、预期输出与异常分支
运行 __main__,五组样本应输出:
| 模型字符串 | 类型 | 上下文 | 动作 |
| --- | --- | --- | --- |
| `grok-4.7` | series | 500,000 | 当前可用;若该系列设了别名会漂移,须配回归 |
| `grok-4.20-0309-reasoning` | pinned | 1,000,000 | 钉住某次发布,升级时显式换 ID |
| `grok-4.7-latest` | float | 未核到 | 换 pinned ID,或接受漂移并补回归用例 |
| `grok-4` | alias_likely | 未核到 | 到官方 models 页确认该别名,线上改 pinned ID |
| `grok-4.5-turbo` | unknown | 未核到 | 对照官方 models 页当前列表,别按记忆改拼写 |
真实项目里的失败分支,通常不是分类器报错,而是分类器跑出了你没预期的类型:
| 现象 | 原因 | 处理 |
|---|---|---|
| 升级后回答风格变了 | 用了浮动别名或会漂移的系列 ID | 换 pinned ID,并留一条回归用例 |
| 账单比预估高一截 | 别名指向了另一个模型,单价随模型变 | 把模型名写进每条账目(见 5.1) |
logprobs 有字段但为空 |
该模型不支持,被静默忽略 | 升级前查模型页的限制说明 |
分类器一半是 unknown |
常量表没跟上官方列表 | 定 3 个月复核,或变更时触发 |
五、判据卡
| 判断项 | 该这么做 | 不该这么做 |
|---|---|---|
| 线上要可复现 | 用带日期段的发布 ID | 假定列表里的系列 ID 钉住了版本 |
| 用系列 ID | 承认可能漂移,配回归用例 | 抄一次模型名用一整年 |
| 升级模型 | 显式改 ID,跑一遍回归 | 等别名自动变好 |
| 见到陌生 ID | 对官方模型页当前列表 | 按记忆改拼写或猜后缀 |
依赖 logprobs |
升级前确认该模型是否支持 | 假定字段总会有值 |
| 复核节奏 | 官方页变动或每 3 个月对表 | 把核验日期当长期有效 |
这张卡约束的是升级动作,不是某个 SDK 的写法。换语言、换封装,几条都成立;它唯一不成立的地方是你在做一次性的离线实验——那里用别名反而更省事。
六、来源与核验
- 官方模型页:
https://docs.x.ai/developers/models(核验日期 2026-11-04)。三档别名规则、「部分模型才有别名」的前提、当前文本模型与上下文窗口、logprobs的静默忽略行为,均来自该页的 Model Aliases 与 Key Information 段。 - 官方发布说明:
https://docs.x.ai/developers/release-notes。模型更迭节奏以该页为准,本节的核验日期不代表长期有效。 - 旧标识符重定向、点号与连字符写法差异属于第三方说法,本次未在官方页核到,正文不作事实陈述;模型名一律以官方页当前列表为准,价格与可用性以官方定价页和开发者控制台当前显示为准。
七、动手试
- 把
KNOWN_CONTEXT换成你项目实际用到的模型,跑一遍分类器,统计有多少条落在series/float/alias_likely。若一条pinned都没有,说明这个系统目前没有可复现的模型基线。 - 给
classify()加一条retired分支:输入一个已知退役的 ID 时返回「不可用,需迁移」。想一想,「弃用」和「已退役」该不该共用一个分支?两者的动作差别在哪? - 如果团队坚持用
-latest换取自动跟进,你会用什么最小成本的手段,让「行为变了」这件事在 CI 里被发现?把答案写成一句不超过 40 字的判据。