版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。
一、结论先行:有没有日期,判不了它是不是锁死的
三十条用例全过,周报上写了四个字:模型没变。这句话能撑住的东西,比写的人以为的少得多。按 95% 置信算,它排除的只有「真实漂移超过 9.5%」这一种情况;真实漂移如果是 3%,三十条全过属于大概率事件,概率四成。也就是说,一次全绿给出的结论不是「没变」,而是「可能变了,最多变到一成,而且我有四成机会一条都没碰见」。
要打的常识在这里:很多人拿「名字里有没有日期」当锁定凭据。带日期的当作钉死,不带日期的当作浮动指针。这个直觉在几年以前大致能用,现在两头都不成立——一家把无日期名定义成快照,一家把无日期名定义成映射,方向正好相反。靠字符串形态去判断锁没锁,等于用一个已经失效的字段做断言。
Anthropic 的说法很直接。它在《Model IDs and versions》里写:A 4.6-generation ID such as claude-sonnet-4-6 is not an alias. It is the snapshot.(4.6 代这种 ID 不是别名,它就是快照。)同一页还专门纠正了一个流传很广的误解:A common misconception is that dateless model IDs such as claude-sonnet-4-6 behave as evergreen pointers that route to the latest or best-performing version. That is not the case. 也就是说,名字里没有日期,反而是锁死的。
同一页还有一条约束常被忽略:When you use a model ID in an API request, the underlying model remains constant for the lifetime of that ID. 但这个保证有明确的适用范围——This guarantee covers model IDs, not the convenience aliases that the Claude API accepts for some earlier models...。别名是另一回事,它的定义是 An alias such as claude-sonnet-4-5 is a convenience pointer that resolves to the most recent dated snapshot for that minor version.:名字不变,指向会变。至于「版本会不会在同一个 ID 下悄悄更新」,官方把话堵死了:Anthropic does not update the weights or configuration of an existing model ID. When an updated version is available, it ships under a new model ID.
OpenAI 那边的情况不一样,而这一点最容易被写错。它的《Deprecations》页尾有一栏 Plain-text aliases,把「无日期名 ↔ 具体快照」列成两列映射,例如 gpt-5.1 | gpt-5.1-2025-11-13。这一栏能核到的事实是:无日期名在文档里对应着某个具体快照。但它没有写「别名会自动指向后来发布的快照」,也没有「自动升级 / 自动重定向」这类表述。这个映射会不会随新快照发布而改,我在官方页面上没有核到明确句子,标待验证。截至 2026-10-28,两家公开文档在这一点上的表述密度差得很远,谁也不能替对方下结论。
把两家的定义并排放,问题就露出来了。
| 名字形态 | 里面有日期吗 | 官方怎么定义这个名字 | 能不能靠名字判断锁没锁 |
|---|---|---|---|
| Anthropic 4.6 代起的 ID | 没有 | 本身就是快照,不是别名 | 能,结论是「锁住了」 |
| Anthropic 旧版别名(claude-sonnet-4-5 这类) | 没有 | 指向该 minor version 最近的那个带日期快照 | 不能,名字稳定但指向会漂 |
| OpenAI 无日期名(gpt-5.1 这类) | 没有 | 退役页把它与具体快照列成映射;是否随新快照更新,页面未说明 | 不能,得查映射并把解析结果留档 |
| 两家带日期的名字 | 有 | 指向具体快照 | 能,但快照本身仍会进退役窗口 |
表里最扎眼的是前两行:同样是「没有日期」,一个是锁死的,一个是浮动的。判据一旦建立在「有没有日期」上,四行里有两行会判错,而且错的方向相反——把锁死的当成浮动,你会做多余的回归;把浮动的当成锁死,你会把一次静默换版当成零成本。后者才是要命的那种。
所以判据只能换一个落点:不问名字长什么样,问「如果它真的变了,我这批用例能不能看见」。这是一个可以被算出来的量,不是一种感觉。
二、评测口径:什么叫「检出漂移」,为什么 30 条不够
检出,指的是一次试验里至少有一条用例从通过翻成不通过,不是平均分掉了多少,也不是人看着「风格有点不一样」。把口径压到这么窄,是因为只有这样它才是个可算的量:一条用例在新旧两个快照上的结果,是一个二值判断,不掺主观分。
三个符号定下来:p 是单条用例翻面的概率(可以理解成「这条断言在新快照上失效的机会」),n 是用例条数,一次试验里至少命中一条的概率是 1-(1-p)^n。这个式子只依赖两个假设:各条独立、各条同分布。真实评测里这两条都只是近似——同一个模板派生出来的用例常常一起翻面,真出现成片失效时,实际检出比公式算出来的更低。所以后面所有数字都该当成上限读,不是保险额度。
| 口径项 | 本文怎么定 | 为什么这么定 | 它不覆盖什么 |
|---|---|---|---|
| 度量对象 | 一次试验里「至少一条用例翻面」 | 与告警真实的触发方式一致 | 翻面的是不是最关键那一条 |
| 漂移幅度 p | 单条用例翻面的概率,取 1%、3%、5%、10%、20% 五档 | 覆盖从「几乎察觉不到」到「一眼看出」 | 整段输出质量的缓慢下滑 |
| 用例数 n | 10、30、100、300、1000 五档 | 从常见的小回归到接近全量 | 用例之间的相关性 |
| 判定方式 | 同一条断言在新旧快照上的前后对比 | 不需要人工打分,可以逐字复现 | 绝对分变化、语气与格式漂移 |
| 置信口径 | 95%,上界用 1-0.05^(1/n) 精算 | 与常用的「3/n 规则」对照,能看出近似误差 | 多版本同时比较时的多重比较问题 |
表里第四行是这套口径最容易被换掉的地方。同一个漂移,只要把判据从「逐条比对」换成「看均分变化」,检出情况会掉一个数量级,第四章会把这件事算出来。第五行则是一个常被写错的细节:网上流传的「30 条全过就说明漂移小于 10%」用的是 3/n 这个近似式,它在 n 大于三十以后误差很小,在 n 等于十时能差出四个百分点。这一点第三章的实跑输出里有一列专门量它。
举个具体形状的例子,免得 p 停在抽象层:断言写成「输出里必须出现订单号」,新快照在 3% 的输入上把订单号吞了。这 3% 就是 p。三十条用例里能不能撞上一条,取决于运气,不取决于你有没有认真写断言。
这套口径边界在哪,得说清两件事。第一,它量的是「有没有翻面」,不量「翻得对不对」。一条断言从通过变不通过,可能是模型退化,也可能是你更新了断言本身;所以基线快照与候选快照必须跑在同一版用例上,任何一边改了断言,对比就作废。第二,它假设每条用例各管一段能力,三十条平分下来是「每类问题两三条」。如果你的三十条里有二十条在测同一件事(同一段提示词的几种问法),有效条数就只有十来条,第三章那张表要按十来条那一行读,检出会比你以为的低一档。相关性没法从公式里修,只能靠列用例的时候摊开。
三、同样的漂移幅度,样本量决定你能不能看见
同样是 3% 的漂移,十条用例看见 26.3%,三十条看见 59.9%,一百条看见 95.2%,三百条几乎是必然。差异不在模型,在你手里有多少条用例。
🧪 实测环境:Python 3.13.12 / macOS / 仅标准库 脚本:
work/exp1028/e02_power.py
"""Drift detection power: P(at least one case breaks), and the 95% upper bound on p
when all n cases pass. Standard library only. No clock, no randomness."""
import math
P = [0.01, 0.03, 0.05, 0.10, 0.20]
N = [10, 30, 100, 300, 1000]
def power(p, n):
return 1.0 - (1.0 - p) ** n
print("A. P(at least one case shows the drift) = 1-(1-p)^n")
head = "n\\p " + "".join(f"{p:>9.2f}" for p in P)
print(head)
print("-" * len(head))
for n in N:
print(f"{n:<7}" + "".join(f"{power(p, n) * 100:>8.1f}%" for p in P))
print()
print("B. All n cases pass -> 95% upper bound on the real drift p")
print("n 3/n rule exact 1-0.05^(1/n) gap")
for n in N:
rule = 3.0 / n
exact = 1.0 - 0.05 ** (1.0 / n)
print(f"{n:<8}{rule * 100:>7.1f}%{exact * 100:>19.1f}%{(rule - exact) * 100:>8.1f}pp")
print()
print("C. Cases needed to reach a given power at p = 0.03")
for target in (0.50, 0.80, 0.95):
n = math.ceil(math.log(1.0 - target) / math.log(1.0 - 0.03))
print(f"power >= {target * 100:.0f}% -> n = {n}")
A. P(at least one case shows the drift) = 1-(1-p)^n
n\p 0.01 0.03 0.05 0.10 0.20
----------------------------------------------------
10 9.6% 26.3% 40.1% 65.1% 89.3%
30 26.0% 59.9% 78.5% 95.8% 99.9%
100 63.4% 95.2% 99.4% 100.0% 100.0%
300 95.1% 100.0% 100.0% 100.0% 100.0%
1000 100.0% 100.0% 100.0% 100.0% 100.0%
B. All n cases pass -> 95% upper bound on the real drift p
n 3/n rule exact 1-0.05^(1/n) gap
10 30.0% 25.9% 4.1pp
30 10.0% 9.5% 0.5pp
100 3.0% 3.0% 0.0pp
300 1.0% 1.0% 0.0pp
1000 0.3% 0.3% 0.0pp
C. Cases needed to reach a given power at p = 0.03
power >= 50% -> n = 23
power >= 80% -> n = 53
power >= 95% -> n = 99
A 段那张表横着读是「同样条数,漂移越大越容易看见」,竖着读才是关键:n 从 30 加到 100,3% 那一列从 59.9% 跳到 95.2%。三十条不是「差不多够」,它连六成都不到,而团队往往把它当成覆盖了。同一批用例独立跑两次可以把检出抬到 83.9%(1-0.401²),代价是两次调用预算,这笔账在预算紧的时候值得算一遍。
C 段给的是反推:想在 3% 这个量级上拿到 95% 的把握,需要 99 条,取整就是一百条。B 段量的是另一个方向——当你什么都没有看见时,能下多硬的结论。三十条全过,精算上界 9.5%,3/n 近似给 10.0%,两者差半个百分点;到 n=10 时近似值虚高 4.1 个百分点,把「最多 26%」说成「最多 30%」。所以别在两位数样本上引用 3/n。
从这张表能直接抄出一条工程判据:用例条数取容忍幅度的倒数乘三。容忍 10% 的漂移,n 取 30;容忍 5%,n 取 60;容忍 3%,n 取 100;容忍 1%,n 取 300。四个数跟 C 段算出来的 95% 把握点几乎重合,因为 3/n 这个近似和 0.95 的分位数在 n 大于三十后基本贴在一起。
还有一处必须自己打折:公式假设各条独立,而真实漂移常常成片出现。同一个提示词模板派生出的二十条用例,遇到模板层面的改动会一起翻面,此时你损失的不是「一条都没命中」,而是「一命中就是二十条」——检出反而更稳。真正危险的是相反的情形:漂移只落在你压根没覆盖的那类输入上,p 对已有用例等于零,n 加到一千也还是零。所以扩条数的正确顺序是先补覆盖面、再按 3 除以容忍幅度算总数,反过来做只是把同一批断言复制了三百遍。
四、把「全过」当成「没变」,是这类事故最常见的误判
「全过」只是「这批用例没长眼睛」,不是「模型没动」。下面这组蒙特卡洛把这句话量成了百分比。
🧪 实测环境:Python 3.13.12 / macOS / 仅标准库 脚本:
work/exp1028/e02_sim.py
"""Monte Carlo: what "all green" really means, and two detection rules compared.
Fixed seed, no clock. Scores are on a 0-100 scale."""
import random
SEED = 20261028
TRIALS = 2000
P_DRIFT = 0.03
DROP = 35.0
NOISE = 2.0
ITEM_TOL = 15.0
MEAN_TOL = 5.0
MEAN_TOL_TIGHT = 2.0
def baseline(rng, n):
return [100.0 + rng.uniform(-NOISE, NOISE) for _ in range(n)]
def candidate(rng, n):
out = []
for _ in range(n):
s = 100.0 + rng.uniform(-NOISE, NOISE)
if rng.random() < P_DRIFT:
s -= DROP
out.append(s)
return out
def mean(xs):
return sum(xs) / len(xs)
print(f"seed={SEED} trials={TRIALS} p={P_DRIFT} drop={DROP} noise=+-{NOISE}")
print()
print("A. Share of trials with zero broken cases (i.e. read as 'nothing changed')")
print("n simulated theory (1-p)^n")
for n in (30, 100, 300):
miss = 0
rng = random.Random(SEED + n)
for _ in range(TRIALS):
hit = 0
for _ in range(n):
if rng.random() < P_DRIFT:
hit += 1
if hit == 0:
miss += 1
print(f"{n:<7}{miss / TRIALS * 100:>8.2f}%{(1 - P_DRIFT) ** n * 100:>14.2f}%")
print()
print("B. Two rules on the same paired runs (baseline vs candidate)")
print("n item diff mean drop(5) mean drop(2)")
for n in (30, 100, 300):
rng = random.Random(SEED + 1000 + n)
item = mean5 = mean2 = 0
for _ in range(TRIALS):
a = baseline(rng, n)
b = candidate(rng, n)
if any(abs(y - x) > ITEM_TOL for x, y in zip(a, b)):
item += 1
d = abs(mean(b) - mean(a))
if d > MEAN_TOL:
mean5 += 1
if d > MEAN_TOL_TIGHT:
mean2 += 1
print(f"{n:<6}{item / TRIALS * 100:>8.2f}%{mean5 / TRIALS * 100:>12.2f}%{mean2 / TRIALS * 100:>13.2f}%")
print()
print("C. One broken case out of n pulls the mean down by this much")
for n in (30, 100, 300):
print(f"n={n:<5} {DROP / n:.2f} points on a 0-100 scale")
seed=20261028 trials=2000 p=0.03 drop=35.0 noise=+-2.0
A. Share of trials with zero broken cases (i.e. read as 'nothing changed')
n simulated theory (1-p)^n
30 40.60% 40.10%
100 4.40% 4.76%
300 0.00% 0.01%
B. Two rules on the same paired runs (baseline vs candidate)
n item diff mean drop(5) mean drop(2)
30 61.70% 0.45% 22.10%
100 95.45% 0.00% 7.85%
300 100.00% 0.00% 1.00%
C. One broken case out of n pulls the mean down by this much
n=30 1.17 points on a 0-100 scale
n=100 0.35 points on a 0-100 scale
n=300 0.12 points on a 0-100 scale
A 段回答的是「报绿了意味着什么」。3% 的真实漂移、三十条用例,两千次试验里有 812 次一条都没翻面,占 40.60%,理论值 40.10%,两者差半个百分点以内,说明这批数是可信的。把条数抬到一百,误读的比例掉到 4.40%;抬到三百,两千次里一次都没有。第一章那句「四成机会一条都没碰见」,来源就是这一行。
B 段是同一批数据、同一个漂移,只换判据。逐条比对在三十条时检出 61.70%,一百条时 95.45%;换成「只看整体平均分变化」、阈值定 5 分,三十条时只有 0.45%,一百条和三百条都是零。把均分阈值一路收紧到 2 分,三十条也只能到 22.10%,一百条 7.85%,三百条 1.00%——阈值越紧,误报风险越高,检出却依然追不上逐条比对。
原因写在 C 段:一条用例掉 35 分,摊到三十条里均值只动 1.17 分,一百条是 0.35 分,三百条 0.12 分。而生产告警的均分阈值极少设在 5 分以下,因为每天的正常抖动就有一两分。这就形成了一个结构性盲区:漂移越稀疏,均分越看不见;用例越多,单条的影响被摊得越薄。均分阈值和逐条比对不是一个灵敏度量级的东西,把它们当成同一件事的两种写法,是这类事故最常见的误判。
这份资料里有一份模型版本核对的检查清单:把「别名 → 解析到的快照 → 退役日期」三列记在一张表里,和本章两种判据的差别直接对应。放在资料包里,扫码即可获取:
五、锁定版本要做三件事,不是改一个字符串
锁版本是三个动作连起来的一件事,改配置里的字符串只是第一步,而且是最不值钱的一步。字符串改完,你得到的只是「我以为我锁了」;后面两步才把它变成「我能证明它没动」。
第一件事:把解析结果记下来,别只记你填的那个名字。Anthropic 4.6 代起的 ID 本身就是快照,把响应里的 model 字段打进程日志就够了;旧版别名和 OpenAI 侧的无日期名不一样,它们要经过一次解析,所以每次构建都把「填的名字 → 实际解析到的快照名」落成一行记录,放进版本库。这样做的收益很具体:下次有人问「是不是换过」,你 diff 一行日志就有答案,不用重跑一整轮评测。判断标准也很硬——如果你的配置里只有别名、没有解析结果,那这条锁定就是没做完的。
第二件事:用例条数按容忍幅度反推,判据用单次逐条比对。第三章那条经验式(n 取 3 除以容忍幅度)可以直接抄:容忍 10% 用 30 条,容忍 3% 用 100 条,容忍 1% 用 300 条。同时把闸门从「均分掉了多少」改成「有没有任意一条断言翻面」,因为前者在稀疏漂移下几乎不动。第四章的数字是这次的依据:同样是三十条、同样 3% 漂移,逐条比对 61.70%,均分 5 分阈值 0.45%。
第三件事:把退役窗口提前放进排期,而不是等它发生。OpenAI 的退役页给了三档时间:Generally available models: At least 6 months.、Specialized variants of generally available models: At least 3 months.、Preview models, identified by preview in the model name, may be retired with much shorter notice, such as 2 weeks. 并且给了程序化的查法:To monitor model shutdowns programmatically, call the List models endpoint (GET /v1/models) and check each model's optional shutdown_date field. 这三档放在一起看,结论很清楚:preview 名字的模型不该出现在「锁死就没风险」的清单里,两周的窗口不够走一次完整回归。
| 要做的动作 | 落到哪里 | 可执行的判据 | 不做会怎样 |
|---|---|---|---|
| 记下解析结果 | 构建日志 + 版本库里的映射记录 | 每次构建都有一行「名字 → 快照」,可 diff | 说不清换没换,只能重跑评测 |
| 按容忍幅度定条数 | 回归用例集 | n ≈ 3 / 容忍幅度,且判据为逐条比对 | 全绿也说明不了问题,均分闸门形同虚设 |
| 前置退役窗口 | 排期表 + 定时任务 | GA 留 6 个月、专用变体留 3 个月、preview 按 2 周算,且读 shutdown_date | 换版和换代撞在一起,回归没跑完就得切换 |
三件事里最容易漏的是第二件的后半句。很多团队把用例补到了一百条,判据却还是「均分不低于上次」,于是补的条数全部白费——第四章 B 段那一列 0.00% 就是这种状态:一百条用例、3% 漂移、两千次试验,一次都没报警。
给一个停手条件,免得把这件事做成无底洞:如果你的容忍幅度是 5%,那么用例补到 60 条、判据换成逐条比对,就够用了,再往上加条数只是把检出从 95% 抬到 99%,边际收益很低;反过来,如果容忍幅度是 1%,又只想维护三十条用例,那么无论判据怎么改都看不见——这时候该动的不是代码,是把容忍幅度这件事重新跟业务方谈一遍。
这份资料里有一份回归用例的样本量对照表:从容忍幅度反推条数、逐条比对与均分阈值的差距,都按本章算出来的数排好了。放在资料包里,扫码即可获取:
附表 A:本文引用事实与出处对照表
| 序号 | 事实(含原文) | 出处 | 本文位置 |
|---|---|---|---|
| 1 | A 4.6-generation ID such as claude-sonnet-4-6 is not an alias. It is the snapshot.;且 Every Claude model ID is a pinned snapshot, including the dateless IDs used from the 4.6 generation on. | Anthropic《Model IDs and versions》与《Models overview》,platform.claude.com/docs/en/about-claude/models/ | 第一章 |
| 2 | An alias such as claude-sonnet-4-5 is a convenience pointer that resolves to the most recent dated snapshot for that minor version. | 同上《Model IDs and versions》 | 第一章 |
| 3 | This guarantee covers model IDs, not the convenience aliases that the Claude API accepts for some earlier models... | 同上 | 第一章 |
| 4 | A common misconception is that dateless model IDs such as claude-sonnet-4-6 behave as evergreen pointers that route to the latest or best-performing version. That is not the case. | 同上 | 第一章 |
| 5 | Anthropic does not update the weights or configuration of an existing model ID. When an updated version is available, it ships under a new model ID. | 同上 | 第一章 |
| 6 | 退役页尾 Plain-text aliases 一栏把「无日期名 ↔ 具体快照」列成映射(左列 gpt-5.1,右列 gpt-5.1-2025-11-13);该页未说明别名是否随新快照更新,此点标待验证 | OpenAI《Deprecations》,developers.openai.com/api/docs/deprecations | 第一、五章 |
| 7 | Generally available models: At least 6 months. / Specialized variants of generally available models: At least 3 months. / Preview models, identified by preview in the model name, may be retired with much shorter notice, such as 2 weeks. | 同上 | 第五章 |
| 8 | To monitor model shutdowns programmatically, call the List models endpoint (GET /v1/models) and check each model's optional shutdown_date field. | 同上 | 第五章 |
写在最后:这篇用到的资料
写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:
- 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
- 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
- AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
- 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
- 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「AI」,优先通过。
资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。
本文深入剖析大模型上线中「30条用例全过≠模型未变更」的认知误区,指出仅依赖别名字符串或均分阈值无法可靠检出模型漂移;强调需结合快照锁定机制、逐条断言比对、按容忍幅度反推用例量(如3%/100条)三要素构建回归测试防线,并揭示Anthropic与OpenAI在模型ID语义上的根本差异及其对上线风险的影响。







1万+

被折叠的 条评论
为什么被折叠?



