版权与内容来源声明
本文为原创整理。文中涉及官方文档、开源仓库、论文与公开报道的内容,均在附表 A 中标注来源;引用官方原文保持原样,不作改写。文中命令、版本号与界面截图以本文成文时的实测/核验结果为准,标注「待验证」的部分请以你本地环境实际输出为判断依据。本文不推荐任何不合规的软件获取方式,也不对任何收益结果作承诺。转载请注明出处。
把重试次数从 3 改成 8,是很多人撞上 429 之后的第一反应。我在本机把这笔账按时间线求和跑了一遍:单次请求上限 2 秒、退避基数 1 秒、退避封顶 20 秒(每次失败后等待按倍数拉长,最长不超过 20 秒),8 次尝试要跑满 87 秒 —— 而配置里那个「总超时 30 秒」早在第 5 次尝试结束时就到期了。后面 3 次不是重试,是排队等超时。
这篇不算「该重试几次」,只算账:一次重试循环花掉多少时间、四个参数怎么互相约束、撞到 429 时该看哪个数。
一、先分清三块开销:重试次数不是答案
一次重试循环的总耗时是三块的加和 —— 每次尝试自身占用的时间、退避等待的累计、以及被重试放大的后端负载。只去调「重试次数」这一个旋钮,另外两块一定会把它顶回来。
三块开销分别是哪些账
第一块是尝试开销,等于「尝试次数 × 单次请求上限」。这里有个高频率的混淆:把单次超时当成了整个操作的上限。OpenAI 的官方限流文档里有一句写得很直白:A timeout for each attempt isn’t necessarily a deadline for the entire operation.(单次尝试的超时并不必然是整个操作的截止时间)。也就是说,「每次最多等 2 秒」和「整个调用最多花 30 秒」是两个独立的数,很多人只配了前者。
第二块是退避等待的累计。指数退避(每次等待按倍数拉长)的等待序列是几何级数,哪怕加了封顶,封顶之后每一步仍在持续累加 —— 封顶不是停止,只是变成线性增长。
第三块不落在你的耗时账上,落在对方的配额账上。OpenAI 文档里明确写了:Note that unsuccessful requests contribute to your per-minute limit, so continuously resending a request won’t work.(失败的请求同样计入你的每分钟限额,所以不停地重发是没有用的)。换句话说,重试不只是「再试一次」,它是在消耗配额的同时制造失败。AWS Builders’ Library 把重试直接定性为一句话:Retries are “selfish.”—— 它的意思是,客户端用服务器更多的时间,去换自己更高的成功概率。
| 开销 | 由什么决定 | 只调重试次数会怎样 | 该盯哪个数 |
|---|---|---|---|
| 尝试开销 | 尝试次数 × 单次请求上限 | 次数翻倍,尝试开销同步翻倍 | 单次请求上限 |
| 退避等待累计 | 退避基数、增长倍率、封顶值 | 次数一加,等待按几何级数爆掉 | 累计等待时间线 |
| 后端负载放大 | 单位时间内的重试并发数 | 次数越多,二次峰值越高 | 每个时间片的重试请求数 |
为什么次数不能手写
重试次数必须是算出来的输出,不能是手写进去的输入 —— 它的上游是单次上限、总预算、退避基数、封顶值。
二、每次重试要等多久:退避公式怎么估
闭式公式能给你一个起点,但它建立在两个通常不成立的假设上:退避序列不被封顶,且每次尝试本身不耗时。所以它只能当首轮估算,必须用时间线求和回头验算。
闭式给的是上界,不是答案
先看闭式。退避基数记作 W、倍率记作 r,n 次重试的等待之和是 W(r^n − 1)/(r − 1)。让它不超过总预算 B,解出:可重试次数 n ≈ floor( log_r( 1 - B × (1 - r) / W ) )。代入 B = 30 秒、W = 1 秒、r = 2,得 n ≈ 4,这就是那类「30 秒预算够重试 4 次」的估算来源。
但完整的账要把每次尝试自身的开销也放进去:n × 单次请求上限 + W(r^n − 1)/(r − 1) ≤ B,且每一项等待还要被封顶值截断。
时间线求和怎么验算
这两个式子必须联立解,单独满足任何一个都会失效。我用时间线求和验算了一遍,差距是真实存在的:
🧪 实测环境:Python 3.13.12 / macOS / 仅用标准库 random、math / 脚本 work/exp1014/exp02a.py(下列可独立运行)
importmath,random T_REQ,BUDGET,MAX_ATTEMPTS,BASE,CAP,SEED=2.0,30.0,8,1.0,20.0,20261014defwaits_fixed(n):return[BASE]*ndefwaits_exp(n):return[min(CAP,BASE*(2**i))foriinrange(n)]defwaits_jitter(n,rng):return[rng.uniform(0,min(CAP,BASE*(2**i)))foriinrange(n)]deftimeline(waits):# 第 n 次尝试结束时刻 = n*T_req + 前 n-1 次等待之和ends=[round((i+2)*T_REQ+sum(waits[:i+1]),3)foriinrange(len(waits))]returnendsor[round(T_REQ,3)],(endsor[T_REQ])[-1]defattempts_within(waits):elapsed,done=T_REQ,1ifT_REQ<=BUDGETelse0forwinwaits:elapsed+=w+T_REQifelapsed<=BUDGET:done+=1else:breakreturndonedefclosed_form(b,w0,r):# 只算退避等待,不含单次开销inner=1.0-b*(1.0-r)/w0returnint(math.floor(math.log(inner,r)))ifinner>0elseNonedefsummed(b,t=T_REQ,w0=BASE,cap=CAP,r=2.0):# 时间线求和口径elapsed,n=t,0whileTrue:w=min(cap,w0*(r**n))ifelapsed+w+t>b:breakelapsed+=w+t;n+=1returnn,round(elapsed,3)rng,n_retry=random.Random(SEED),MAX_ATTEMPTS-1forname,waitsin[("固定间隔 1s",waits_fixed(n_retry)),("指数退避 基数1s(截断20s)",waits_exp(n_retry)),("指数+full jitter(截断20s)",waits_jitter(n_retry,rng))]:ends,total=timeline(waits)print(f"---{name}---")print(" 等待序列(s) : "+", ".join(f"{w:.3f}"forwinwaits))print(" 尝试结束时刻: "+", ".join(f"{e:.3f}"foreinends))print(f" 累计总耗时 :{total:.3f}s")print(f" 预算内实际完成尝试次数:{attempts_within(waits)}/{MAX_ATTEMPTS}")print()print(f"闭式(只算退避等待, 不含单次开销) 可重试次数 ≈{closed_form(BUDGET,BASE,2.0)}")print(f"时间线求和(含单次{T_REQ}s 开销) 指数退避实际完成尝试次数 ={attempts_within(waits_exp(n_retry))}")print()print(f"{'总预算B(s)':>10}|{'闭式可重试次数':>14}|{'求和可重试次数':>14}|{'含单次开销的耗时(s)':>18}")print("-"*66)forbin(10.0,20.0,30.0,60.0,120.0):est,(real,used)=closed_form(b,BASE,2.0),summed(b)print(f"{b:>10.1f}|{str(est):>14}|{real:>14}|{used:>18.3f}"+(""ifest==realelse" <-- 两个口径分叉"))原样输出(同一组参数):
--- 固定间隔 1s --- 等待序列(s) : 1.000, 1.000, 1.000, 1.000, 1.000, 1.000, 1.000 尝试结束时刻: 5.000, 8.000, 11.000, 14.000, 17.000, 20.000, 23.000 累计总耗时 : 23.000s 预算内实际完成尝试次数: 8 / 8 --- 指数退避 基数1s(截断20s) --- 等待序列(s) : 1.000, 2.000, 4.000, 8.000, 16.000, 20.000, 20.000 尝试结束时刻: 5.000, 9.000, 15.000, 25.000, 43.000, 65.000, 87.000 累计总耗时 : 87.000s 预算内实际完成尝试次数: 5 / 8 --- 指数+full jitter(截断20s) --- 等待序列(s) : 0.728, 0.350, 3.073, 6.459, 9.457, 6.615, 7.159 尝试结束时刻: 4.728, 7.077, 12.150, 20.609, 32.065, 40.680, 49.839 累计总耗时 : 49.839s 预算内实际完成尝试次数: 5 / 8 闭式(只算退避等待, 不含单次开销) 可重试次数 ≈ 4 时间线求和(含单次 2.0s 开销) 指数退避实际完成尝试次数 = 5 总预算B(s) | 闭式可重试次数 | 求和可重试次数 | 含单次开销的耗时(s) ------------------------------------------------------------------ 10.0 | 3 | 2 | 9.000 <-- 两个口径分叉 20.0 | 4 | 3 | 15.000 <-- 两个口径分叉 30.0 | 4 | 4 | 25.000 60.0 | 5 | 5 | 43.000 120.0 | 6 | 8 | 109.000 <-- 两个口径分叉三个能直接拿走的读数。闭式给出的 4 次重试对应 5 次尝试,和时间线求和正好对得上;但预算挪到 10 秒、20 秒、120 秒,三个档位全部分叉,方向还不一致 —— 前两档闭式偏乐观,120 秒那档偏悲观(封顶拉长了线性段,闭式假设的是不封顶的几何级数)。指数退避在 30 秒预算内只跑得完 5 次尝试,配置里写 8 次,有 3 次白等。加抖动让同样的 8 次尝试从 87 秒缩到 49.8 秒。
三、三档方案:固定间隔 / 指数退避 / 指数加抖动
只有「指数 + 抖动」能同时压住总耗时和二次峰值;固定间隔在限流持续时,会把自己的重试打成一条稳定的高负载;纯指数退避则会把所有客户端对齐到同一时刻。
固定间隔与纯指数退避各缺什么
| 策略 | 前 4 次等待序列 | 30 秒预算内完成 | 跑满 8 次耗时 | 主要代价 |
|---|---|---|---|---|
| 固定间隔 1s | 1, 1, 1, 1 | 8 次 | 23s | 限流持续时,重试变成稳定负载 |
| 指数退避(基数 1s,封顶 20s) | 1, 2, 4, 8 | 5 次 | 87s | 客户端同步重试,形成二次峰值 |
| 指数 + full jitter(同参数) | 0.73, 0.35, 3.07, 6.46 | 5 次 | 49.8s | 需要随机源与可复现种子 |
三档里最容易被低估的是纯指数退避。AWS Builders’ Library 指出了它的两个坑:一是封顶之后Now all of the clients are retrying constantly at the capped rate.(所有客户端都以封顶速率不停重试);二是没有抖动时If all the failed calls back off to the same time, they cause contention or overload again when they are retried.(失败的调用都退避到同一时刻,重试时会再次造成竞争或过载)。这就是「惊群」(大量客户端退避到同一时刻、一起重试)的工程后果 —— 抖动不是调优项,是防二次峰值的必需项。
抖动把峰值摊平
我把这个峰值量化跑了一遍:
🧪 实测环境:Python 3.13.12 / macOS / 仅用标准库 random / 固定种子 20261014 / 脚本 work/exp1014/exp02b.py(下列可独立运行)
importrandom N_CLIENTS,MAX_RETRY,BASE,CAP,BUCKET,WINDOW,SEED=200,6,1.0,60.0,0.1,70.0,20261014defretry_times(jitter,rng):# 200 个客户端各自的重试时刻(t=0 是首次被限流时刻)per_client=[]for_inrange(N_CLIENTS):t,times=0.0,[]foriinrange(MAX_RETRY):upper=min(CAP,BASE*(2**i))t+=rng.uniform(0.0,upper)ifjitterelseupper# full jitter:U(0, upper)times.append(t)per_client.append(times)returnper_clientdefbucketize(per_client):# 按 0.1 秒时间片统计重试请求数n=int(WINDOW/BUCKET)+1counts=[0]*nfortimesinper_client:fortintimes:idx=int(t/BUCKET)ifidx<n:counts[idx]+=1returncountsdefrender(name,counts):peak=max(counts)peak_at=[round(i*BUCKET,1)fori,cinenumerate(counts)ifc==peak]nonzero=[cforcincountsifc>0]mean=sum(counts)/len(counts)print(f"---{name}---")print(f" 重试请求总数 :{sum(counts)}")print(f" 单时间片峰值 :{peak}(出现在 t ={peak_at}s)")print(f" 峰值/均值比 :{peak/mean:.1f}x (均值{mean:.2f}次/片)")print(f" 空时间片占比 :{1-len(nonzero)/len(counts):.1%}"f"(有请求的时间片{len(nonzero)}/{len(counts)})")returnpeak ca=bucketize(retry_times(False,random.Random(SEED)))peak_a=render("无抖动(纯指数退避)",ca)print()cb=bucketize(retry_times(True,random.Random(SEED)))peak_b=render("有抖动(full jitter)",cb)print()print(f"无抖动峰值单时间片请求数 :{peak_a}")print(f"有抖动峰值单时间片请求数 :{peak_b}")print(f"峰值下降倍数 :{peak_a/peak_b:.2f}x")print(f"总请求数(两种策略相同) :{sum(ca)}vs{sum(cb)}")原样输出:
--- 无抖动(纯指数退避) --- 重试请求总数 : 1200 单时间片峰值 : 200 (出现在 t = [1.0, 3.0, 7.0, 15.0, 31.0, 63.0] s) 峰值/均值比 : 116.8x (均值 1.71 次/片) 空时间片占比 : 99.1% (有请求的时间片 6/701) --- 有抖动(full jitter) --- 重试请求总数 : 1200 单时间片峰值 : 43 (出现在 t = [0.9] s) 峰值/均值比 : 25.1x (均值 1.71 次/片) 空时间片占比 : 52.8% (有请求的时间片 331/701) 无抖动峰值单时间片请求数 : 200 有抖动峰值单时间片请求数 : 43 峰值下降倍数 : 4.65x 总请求数(两种策略相同) : 1200 vs 1200200 个客户端在 t=0 同时被限流,两种策略的总重试请求数完全一样(1200 次),差别只在落点:无抖动时全部砸在 t=1.0、3.0、7.0、15.0、31.0、63.0 这六个瞬时点上,单时间片峰值 200;加抖动后同样的 1200 次请求被摊到 331 个时间片上,峰值 43 次,空时间片占比从 99.1% 降到 52.8%。峰值差 4.65 倍,代价是零 —— 只是把固定等待换成U(0, 上界)。
AWS 架构博客用 100 个竞争客户端做过同类模拟,结论一致:In the case with 100 contending clients, we’ve reduced our call count by more than half.(在 100 个竞争客户端的情况下,我们把调用次数减少了一半以上)。
四、把总时间预算跑成一段代码
预算不是「最坏情况下愿意等多久」,它必须反过来约束尝试次数。把「单次请求上限、总时间预算、最大重试次数、退避基数(含封顶与抖动)」放进同一个函数,让尝试次数成为输出,账才算闭环。
四个参数怎么联立
🧪 实测环境:Python 3.13.12 / macOS / 仅用标准库 random / 脚本 work/exp1014/exp02c.py
defplan(budget,t_req=2.0,base=1.0,cap=20.0,r=2.0,max_attempts=8):used,done,n=t_req,1,0# 首次尝试也占一个 t_reqwhilen<max_attempts:w=min(cap,base*r**n)# 不抖动的上界 = 最坏情况ifused+w+t_req>budget:breakused+=w+t_req;n+=1;done+=1returndone,round(used,3)defbudget_needed(n_attempts,t_req=2.0,base=1.0,cap=20.0,r=2.0):waits=[min(cap,base*r**i)foriinrange(n_attempts-1)]# 首次之前无等待returnround(n_attempts*t_req+sum(waits),3)forbin(10.0,20.0,30.0,60.0,90.0):print("预算",b,"→ 实际可用",plan(b)[0],"次尝试")print("要跑满 8 次尝试,需要预算",budget_needed(8),"s")同一个脚本的原样输出(数字未改动):预算 10 秒 → 实际可用 3 次尝试;20 秒 → 4 次;30 秒 → 5 次;60 秒 → 6 次;90 秒 → 8 次;要跑满 8 次尝试,需要预算 87.0 秒。
注意末行:要 8 次尝试就得准备 87 秒预算,而常见配置里的操作总超时是 30 秒级别 —— 差了三倍。要么把次数降到预算装得下的档位,要么把预算提上去,不能两个都不动。
参数取值与联立约束
| 参数 | 含义 | 取值建议 | 依据 | 配置错方向会怎样 |
|---|---|---|---|---|
| 单次请求上限 | 一次尝试最多占多久 | 取下游 p99.9 延迟,按可接受的误超时比例反推 | AWS 是先从下游延迟分位数起手 | 设太小,慢请求被判失败并重试,反而放大负载 |
| 总时间预算 | 从首次发起到放弃 | 不超过调用方给你的 deadline,留出收尾余量 | OpenAI:单次超时不是整个操作的上限 | 设太大失败感知慢;设太小重试没机会 |
| 最大重试次数 | 含首次的尝试上限 | 取联立式解出的 n | 本文 exp02a / exp02c 实测 | 手写 8 次,30 秒预算下 3 次白等 |
| 退避基数 | 首次等待时长 | 0.5 到 1.0 秒,封顶在 20 秒量级 | urllib3 源码文档的生产建议:setting a non-zero factor (e.g. 0.5 to 1.0) | 基数太大,前两次就把预算烧完 |
urllib3 的默认策略可以直接当参照系:它的重试睡眠是{backoff factor} * (2 ** ({number of previous retries})),默认封顶值是 120 秒,默认退避因子是 0 —— 也就是说默认不重试,要用得自己显式打开。
这份资料里有什么:四个参数怎么联立、以及怎么把重试账算成一段能跑的代码,涉及的超时与退避基础概念比较多。相关的概念梳理和排障清单放在资料包里,扫码即可获取:
五、按现象排障:撞上限流时该看哪个数
撞上 429 之后的第一反应不该是调大重试次数,而是先分清是哪一类现象,每类现象只对应一个要看的数。
四类现象各对应一个参数
| 现象 | 最可能的原因 | 该看的数 | 处置 |
|---|---|---|---|
| 一直失败,重试也没成功 | 总预算太小,重试被砍在半路 | 预算内实际可完成的尝试次数 | 先把预算提到能容纳目标次数 |
| 失败得更快,比不加重试还差 | 抖动缺失,多客户端同步重试形成二次峰值;失败请求也在吃配额 | 每个时间片的重试请求数 | 给退避加 full jitter;失败请求按配额定速,而不是无条件重发 |
| 偶尔成功,整体不稳 | 退避基数太大,前两次就把预算烧完 | 首次等待与累计等待时间线 | 基数降到 0.5 到 1.0 秒,或把重试整体转成排队 |
| 延迟尾部长 | 并发重试路数过高,尾请求被自己的重试叠加拉长 | 并发重试路数、单次上限 | 限制并发重试路数;尾延迟按整个操作统计 |
第二行值得单说。失败的请求同样计入每分钟限额,所以不停重发不会更好 —— 重试本身就在消耗你被允许的额度,抖动缺失时这层消耗会以峰值形式集中释放。
第三行的「偶尔成功」最容易被误判成「重试有效」。它其实是基数太大叠加预算太小:预算只够跑两三次,第一次成功靠运气,后面全在等超时。
可以直接落地的两条纪律
第一手建议里有两条可以直接当配置纪律。一条来自 OpenAI 官方文档:Treat this value as a minimum: wait at least that long and add a small random delay so multiple clients don’t retry at the same time.(把 Retry-After 当作最小值:至少等这么久,再加一个小随机延迟,避免多个客户端同时重试)。另一条来自 urllib3 源码里的幂等方法白名单(同一请求发多次、服务端效果与发一次相同):DEFAULT_ALLOWED_METHODS = frozenset(["HEAD", "GET", "PUT", "DELETE", "OPTIONS", "TRACE"]),注释写明By default, we only retry on methods which are considered to be idempotent。注意这个列表里没有 POST。
六、什么时候该停下重试、交给队列
有三种情况必须停止自动重试:操作不幂等、退避已打满封顶且依赖没恢复迹象、重试预算会吃穿你自己的 deadline。
非幂等是硬约束
第一种是硬约束,不是调优建议。RFC 9110 第 9.2.2 节给幂等下的定义是:A request method is considered "idempotent" if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request. Of the request methods defined by this specification, PUT, DELETE, and safe request methods are idempotent.(幂等的含义是:对同一方法发出的多个相同请求,服务端产生的预期效果与单个请求相同;本规范定义的方法里,PUT、DELETE 与安全方法幂等)。同一节还写了两条规范性要求:A client SHOULD NOT automatically retry a request with a non-idempotent method unless ...(非幂等方法的请求不应自动重试,除非能确认语义实际幂等,或能检测到原请求从未生效),以及A proxy MUST NOT automatically retry non-idempotent requests. A client SHOULD NOT automatically retry a failed automatic retry.(代理不得自动重试非幂等请求;客户端不应自动重试一次已失败的自动重试)。
末了那半句很容易被忽略:重试失败后的重试,也不行。很多重试库默认允许这种嵌套重试,叠起来就是指数级的请求量。
AWS 的口径是把重试当成需要观察才能维持的动作:We avoid this amplification by retrying only when we observe that the dependency is healthy. We stop retrying when the retries are not helping to improve availability.(只在下游健康时重试;重试不再提升可用性时就停止)。它给出的停手判据是「看它有没有变好」,而不是「看我还剩几次」。
什么时候转队列
落到可执行的阈值上,我给三个:
- 非幂等的写操作(典型是 POST 创建类接口)不做自动重试,改成先查后写或带幂等键。urllib3 的白名单可以直接照抄。
- 预算内可完成尝试次数 ≤ 2时不要配重试。按本文实测,30 秒预算配 2 秒单次上限只够 5 次尝试,预算压到 10 秒只剩 3 次 —— 这种量级没有统计意义。
- 并发重试路数超过 8就改走队列:请求先落队列,由固定速率的消费端慢慢取,失败进死信。阈值取 8 的理由是,超过这个数,重试产生的并发本身就会把下游从「部分失败」推向「完全不可用」。
配置的落点就是四个数加一个开关:max_attempts: 4(含首次,由预算反解得出,不手写)、single_timeout_s: 2.0、total_budget_s: 30.0、退避的base_s: 1.0与cap_s: 20.0、jitter: full,再加一个「并发重试路数超过 8 就转队列」的兜底开关。
这四条合起来只说明一件事:重试次数是算出来的,不是配出来的。先定单次上限和总预算,再解出次数,用抖动把峰值摊平;任何一步单独调,账都会从另一头破掉。
这份资料里有什么:文中用到的重试时间线脚本、官方超时与退避建议的对照汇总都在这一份里,涉及的概念比正文更细。放在资料包里,扫码即可获取:
附表 A:本文引用事实与出处对照表
| # | 事实 | 出处 | 本文位置 |
|---|---|---|---|
| 1 | 「重试是自私的」「封顶后所有客户端以封顶速率不停重试」「失败的调用退避到同一时刻会再次造成竞争或过载」「只在下游健康时重试」 | AWS Builders’ Library《Timeouts, retries, and backoff with jitter》,aws.amazon.com/builders-library/timeouts-retries-and-backoff-with-jitter/(取数日期 2026-10-14) | 第一章、第三章、第六章 |
| 2 | 「100 个竞争客户端下调用次数减少一半以上」;抖动把尖刺摊成近似恒定速率 | AWS Architecture Blog《Exponential Backoff And Jitter》,aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/(取数日期 2026-10-14) | 第三章 |
| 3 | 幂等方法定义;客户端不应自动重试非幂等请求;代理不得自动重试非幂等请求;不应自动重试已失败的自动重试 | RFC 9110《HTTP Semantics》第 9.2.2 节 Idempotent Methods,rfc-editor.org/rfc/rfc9110.txt(取数日期 2026-10-14) | 第六章 |
| 4 | 默认只对幂等方法重试;退避睡眠公式与默认封顶 120 秒;默认退避因子 0;生产环境建议 0.5 到 1.0 并配合 backoff_jitter | urllib3 源码src/urllib3/util/retry.py(main 分支,取数日期 2026-10-14),urllib3.readthedocs.io 参考页 | 第四章、第五章 |
| 5 | 把 Retry-After 视为最小值并加随机延迟;失败请求计入每分钟限额;单次超时不等于整个操作的截止时间;退避缺失时回落到带抖动的指数退避 | OpenAI《Rate limits》官方文档,developers.openai.com/api/docs/guides/rate-limits(取数日期 2026-10-14) | 第一章、第五章 |
| 6 | 三种退避策略在「单次上限 2 秒 / 总预算 30 秒 / 最大 8 次尝试」下的时间线与总耗时;闭式与求和的口径分叉 | 本文实测,脚本 work/exp1014/exp02a.py,输出 exp02a_out.txt(2026-10-14 运行) | 第二章 |
| 7 | 200 客户端同步重试时,无抖动与有抖动的单时间片峰值对比(200 对 43) | 本文实测,脚本 work/exp1014/exp02b.py,输出 exp02b_out.txt(2026-10-14 运行,种子 20261014) | 第三章 |
| 8 | 四个参数联立后反解的实际可用尝试次数;跑满 8 次尝试所需预算为 87 秒 | 本文实测,脚本 work/exp1014/exp02c.py,输出 exp02c_out.txt(2026-10-14 运行) | 第四章 |
写在最后:这篇用到的资料
写这篇文章时,把相关的官方文档和源码又翻了一遍,顺手也整理了几份配套的东西:
- 大模型学习路线图:从零基础到能自己动手做 Agent,按阶段说明每一步该学什么、哪些可以先跳过
- 《LangChain + LangGraph + MCP 智能体开发实战》视频课:7 个模块,从私有化部署、Embedding+RAG 到 MCP+Agent 全流程
- AI 大模型知识库(在线可查):Agent Skills 从入门到落地、Claude Skills 完全指南等专题,按目录浏览即可
- 640 套 AI 大模型行业报告 + 经典 PDF 书籍:看行业落地案例和别人怎么做的时候用得上
- 大模型零基础到精通教学视频:跟着敲一遍,比只读文档快得多
资料是我自己整理的,放在下面这个码上,扫码即可获取:
添加时备注「AI」,优先通过。
资料按「先路线、再动手、最后查漏」的顺序整理好了,建议先看学习路线那一份,照着它挑一条适合自己当前基础的路径再往下看。