质量达到要求之后,服务还必须在费用、配额和时间约束内运行。本文以完整任务为计量单位,从成本账走到配额瓶颈与平均在途量,再把重试放进总期限。模型内部 KV 与算子开销见推理资源篇,这里关注服务调度与失败处理。
成本按完整执行链计算
Token 是常见计费量,但不是唯一成本来源。模型请求可能区分普通输入、缓存写入、缓存读取和输出;工具、检索、存储、执行容器、网络及自托管算力也会产生费用。不同供应商与产品有不同口径,不能把“输出固定比输入贵五倍”作为通用规则。
任务成本 = 所有模型请求费用之和 + 工具与运行费用 + 相关存储/检索等费用
假设某服务的教学价格为输入每百万 token 2 个货币单位、输出 10 个单位。一次请求输入 20,000、输出 2000,模型费用是 0.04+0.02=0.06;再加工具费用 0.01,总量为 0.07。一个任务需要三次同样规模的调用,才是 0.21;额外失败尝试也要按实际产生的费用加入。这些数字仅用于演算,不是当前产品报价。
| 优化手段 | 可能节省什么 | 需要一同观察 |
| 删除无用输入 | 输入费用和部分处理时间 | 是否丢失必要证据 |
| 前缀缓存 | 重复前缀的计算或费用 | 命中条件、写入开销和有效期 |
| 调整模型与 effort | 单次计算投入 | 完整任务成功率与重试 |
| 控制最终篇幅 | 可见输出量 | 是否覆盖任务要求 |
| 非实时批处理 | 取决于服务的批量价格 | 完成时限、逐项失败与重复提交 |
缓存收益可以用不含真实定价的模型核算:前缀长度 L,普通输入单价 u,写入单价 w,读取单价 r,复用 N 次且其余请求都命中,比较 N×L×u 与 L×w+(N−1)×L×r。TTL 内调用太少或前缀经常变化时,写入溢价可能收不回来;机制与条件见 上下文工程。
换便宜模型也要算整个任务。若单次费用减半,但失败导致三次额外尝试,未必更省。报告每项成功分摊的总成本,并记录未成功任务耗费,而不是只比较一次请求的报价。
拆解延迟与吞吐
端到端时间包含排队、网络、输入处理、推理/生成、工具和应用验证等阶段。串行依赖相加,并行分支按关键路径计算;“延迟主要由输出长度决定”只适合部分工作负载。
| 时间指标 | 从哪里量到哪里 | 用来发现什么 |
| 排队时间 | 进入服务到开始处理 | 准入或后端容量不足 |
| 首个可见文本时间 | 请求开始到有用文本出现 | 输入、思考、网络与展示等待 |
| 生成阶段时间 | 生成开始到本次输出完成 | 解码、输出规模或停顿 |
| 工具阶段时间 | 实际分派到结果确认 | 外部服务和串行依赖 |
| 任务完成时间 | 用户提交到验收完成 | 用户实际体验 |
流式让结果逐步交付,通常改善等待感知,并可配合连接保活;它不会自动提高模型生成速度、解除输出上限或保证所有代理与网络都不超时。需要分别设置连接、读取空闲、单次调用和任务总期限;接收部分文本后断流,也不能把半截结构化结果标为完成。
缓存、缩短输入、批量、并行和更少探索都可能改善某些阶段,但要根据 trace 找瓶颈。若工具查询花 20 秒,削掉 100 个输出 token 对整体影响可能很小;若排队占大头,单独降低前端超时只会制造更多失败重试。
配额、准入与有界排队
配额可能同时限制请求数、输入 token、输出 token和加速增长速度;具体维度按服务文档与账户配置确认。Claude Rate limits 只限制请求并发,不能保证 token 配额不超;只限制平均 RPM,也不能消除瞬间突发。
假设允许 100 请求/分钟、200,000 输入 token/分钟、50,000 输出 token/分钟,每个请求平均输入 10,000、输出 1000,则三个维度给出的平均上限分别为 100、20、50 请求/分钟,最紧约束是 20 请求/分钟。
若平均服务时间为 30 秒,在稳定、无积压的简化条件下,20 请求/分钟对应平均在途请求数约 10。这个 Little 定律算例是平均容量账,不是生产并发设为 10 就一定安全:长度波动、尾延迟、突发和外部共享配额还需余量与动态控制。
from fractions import Fraction
rpm = 100
input_tpm, output_tpm = 200_000, 50_000
input_per_request, output_per_request = 10_000, 1_000
rate = min(Fraction(rpm), Fraction(input_tpm, input_per_request),
Fraction(output_tpm, output_per_request))
mean_inflight = rate / 60 * 30
assert rate == 20
assert mean_inflight == 10
assert 3 * 3 == 9 print("平均请求/分钟:", rate, "平均在途:", mean_inflight)
队列需要长度与等待期限,超出后按业务延期、降级或拒绝。在线交互与可延后的离线任务可分开调度,避免长批处理耗尽交互资源。过载时持续接收并让所有请求更慢,常比早期拒绝更难恢复;容量管理应依据真实资源而非单一请求计数。Google SRE:Handling Overload
重试需要期限与幂等边界
错误类别比数字口诀更有用
| 类别 | 处理原则 |
| 无效参数或不支持能力 | 修正请求,原样重试通常无意义 |
| 身份或权限问题 | 按认证流程恢复或报告;不扩大权限绕过 |
| 限流或短时过载 | 尊重服务提示,有限退避并减少施压 |
| 连接、超时与部分服务错误 | 先判断操作是否可安全重放 |
| 写动作结果未知 | 查询状态或使用已支持的幂等协议 |
| 配额耗尽、长期不可用 | 等待条件变化或使用明确降级,不无限重试 |
不能把它压成“只有 429/5xx 才能重试”或“所有 5xx 都应重试”。408、409 等是否适合重试取决于协议和操作;供应商错误与 SDK 默认策略也应分别核查。Claude Errors
将退避放进同一个任务期限
常见退避上界为 min(cap, base×2^attempt),在区间内引入 jitter,避免多个客户端同时醒来。服务返回 Retry-After 时按它的语义安排,并受任务剩余期限约束;如果等完后已经没有完成一次尝试的时间,就不应继续启动。
例如任务剩余 8 秒,服务要求至少等待 6 秒,而下次操作需要预留 5 秒,这轮重试不符合预算。把“每次请求 timeout=10 秒”设置好,也不代表任务只会等 10 秒;重试、退避、排队和工具阶段都会占用总时长。
调用方重试三次、SDK 内部每次又最多尝试三次,最坏就是九次底层请求。要明确由哪一层管理重试,统计真实尝试数,必要时设置整个服务的重试预算;过载时重试放大会让本已不足的容量更紧张。Google SRE:Addressing Cascading Failures
人工确认不替代幂等
用户确认过“创建一条订单”,只授权了一次业务意图,不保证网络重试不会创建两条。幂等键必须对应同一个业务操作,并由实际服务保存和核验;对同一键发送不同参数也应按服务语义处理,不能只在模型 prompt 里写“不要重复”。
请求超时不能证明服务没执行。若动作已完成但响应未到,应先查询或重放已有结果,取消等待也不自动撤销动作。这条边界在 Agent 循环 中用完整时序展开。
先耗尽的是请求数还是 token?
先把不同单位的配额都换成请求速率,再取最小值,才能找到当前瓶颈。Little 定律中的 L 是平均在途请求数;它不是推荐的硬并发上限,也不能单靠这条公式保证排队尾延迟。
r≤min(R,IˉImax,OˉOmax),L=60rWˉ
正在呈现知识画面
先耗尽的是请求数还是 token?可持续吞吐不超过所有配额换算后的最小值;平均在途数还取决于每请求占用时间。
{"id":"ai-capacity-limit","title":"先耗尽的是请求数还是 token?","summary":"可持续吞吐不超过所有配额换算后的最小值;平均在途数还取决于每请求占用时间。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"input\" data-i18n=\"input\"></label><output for=\"input\" id=\"input-value\"></output><input id=\"input\" type=\"range\" min=\"1000\" max=\"10000\" value=\"4000\" step=\"500\"></div><div class=\"control\"><label for=\"output\" data-i18n=\"output\"></label><output for=\"output\" id=\"output-value\"></output><input id=\"output\" type=\"range\" min=\"100\" max=\"2000\" value=\"500\" step=\"100\"></div><div class=\"control\"><label for=\"seconds\" data-i18n=\"seconds\"></label><output for=\"seconds\" id=\"seconds-value\"></output><input id=\"seconds\" type=\"range\" min=\"1\" max=\"60\" value=\"12\" step=\"1\"></div><div id=\"limits\"></div><div class=\"metrics\"><div><span data-i18n=\"rate\"></span><strong id=\"rate\"></strong></div><div><span data-i18n=\"inflight\"></span><strong id=\"inflight\"></strong></div></div><p class=\"formula\" id=\"math\"></p><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control output{color:var(--accent);text-align:right;font-variant-numeric:tabular-nums;min-width:4em}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;min-height:44px}.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:16px 0}.actions button{min-width:0;min-height:44px;font-size:13px;overflow-wrap:anywhere}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0;overflow-wrap:anywhere}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0}.metrics>div{border-left:2px solid var(--accent);padding:0 6px 0 10px;min-width:0}.metrics span{display:block;min-height:3.6em;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metrics strong{font-size:21px;display:block;min-height:2em;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.check{display:flex;align-items:center;gap:9px;font-size:13px;margin:14px 0}.check input{width:18px;height:18px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:14px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:44px}.panel{padding:14px;border:1px solid var(--rule);border-radius:9px;margin:16px 0;min-width:0}.panel>header{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--muted)}.panel>div{overflow-wrap:anywhere}.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:56px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:6px 3px;font-size:12px;overflow-wrap:anywhere}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.table{display:grid;gap:5px;font-size:12px}.tr{display:grid;gap:5px}.tr>div{background:var(--surface);border-radius:4px;padding:9px 6px;min-width:0;min-height:5em;overflow-wrap:anywhere;display:flex;align-items:center}.tr>.gold{background:var(--second-soft)}.tr>.on{background:var(--accent-soft)}.formula,.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:16px 0}.formula{border-top:1px solid var(--rule);padding-top:12px;min-height:7.2em}.source{min-height:8em;background:var(--surface);padding:12px;border-radius:8px}.intervals{margin:16px 0}.interval{position:relative;height:38px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/38px ui-monospace,monospace;padding-left:7px}.bars{display:grid;gap:10px;margin:16px 0}.bar-row{display:grid;grid-template-columns:44px minmax(0,1fr) 62px;gap:8px;align-items:center;font:12px ui-monospace,monospace}.bar-track{height:24px;background:var(--surface);border-radius:4px;overflow:hidden}.bar-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.bar-track i.gold{background:var(--second)}.bar-track i.empty{opacity:.2}.bar-row output{text-align:right}.diagram{display:block;width:100%;height:auto;margin:18px 0}.diagram text{font:13px ui-monospace,monospace;fill:var(--ink)}.node{fill:var(--surface);stroke:var(--rule)}.node.on{fill:var(--accent-soft);stroke:var(--accent)}.node.gold{fill:var(--second-soft);stroke:var(--second)}.edge{stroke:var(--rule);stroke-width:2;fill:none}.edge.on{stroke:var(--accent)}.matrix{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:16px 0}.matrix button{min-width:0;min-height:44px;font:13px ui-monospace,monospace}.matrix button[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.matrix button.masked{opacity:.35}.legend{font-size:12px;color:var(--muted);min-height:3.6em;overflow-wrap:anywhere}.numeric{font-variant-numeric:tabular-nums}.scope p{line-break:strict}@media(max-width:480px){.presets button,.actions button{font-size:12px}.panel{padding:12px}.metrics{gap:10px}.metrics strong{font-size:19px}.bar-row{grid-template-columns:34px minmax(0,1fr) 58px;gap:6px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.panel>h3{font-size:13px;font-weight:600;margin:0 0 12px;color:var(--muted)}\n\n.bar-row{grid-template-columns:44px minmax(0,1fr) 82px}.bar-row output{white-space:nowrap}@media(max-width:480px){.bar-row{grid-template-columns:34px minmax(0,1fr) 76px}}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function bars(id,values,max=1){$('#'+id).className='bars';$('#'+id).innerHTML=values.map(v=>'<div class=\"bar-row\"><span>'+esc(v.label)+'</span><div class=\"bar-track\"><i class=\"'+(v.cls||'')+'\" style=\"width:'+Math.max(0,Math.min(100,v.value/max*100))+'%\"></i></div><output>'+esc(v.text??(v.value*100).toFixed(1)+'%')+'</output></div>').join('')}function softmax(z,T=1){let max=Math.max(...z),w=z.map(x=>Math.exp((x-max)/T)),sum=w.reduce((a,b)=>a+b,0);return w.map(x=>x/sum)}function draw(){let I=n('input'),O=n('output'),s=n('seconds'),rs=[100,200000/I,50000/O],r=Math.min(...rs),L=r*s/60;for(const id of['input','output','seconds'])set(id+'-value',n(id));bars('limits',rs.map((v,i)=>({label:['RPM','IPM','OPM'][i],value:v,text:v.toFixed(1),cls:v===r?'gold':''})),500);set('rate',r.toFixed(1));set('inflight',L.toFixed(2));set('math','r ≤ min(100, 200000/'+I+', 50000/'+O+')\\nL = ('+r.toFixed(2)+'/60) × '+s+' = '+L.toFixed(2));set('explanation',t('explain'));document.body.dataset.rate=r;document.body.dataset.inflight=L;}inputs(draw);","audio":false,"strings":{"explain":"默认输入限制为 50 请求/分,低于请求数限制 100 与输出限制 100。处理时间变长不会提高配额,却会增加维持同一吞吐所需的平均在途数。实际运行还要留余量。","heading":"先耗尽的是请求数还是 token?","inflight":"此吞吐下平均在途数","input":"单请求输入 token","intro":"改变单请求输入、输出和处理时间,比较三个容量上限。","output":"单请求输出 token","rate":"配额允许的请求 / 分钟","reset":"重置","scope":"假设每分钟 100 请求、200000 输入 token、50000 输出 token,分别计费限流;忽略突发、缓存规则与其他瓶颈。","scopeLabel":"模型范围与约定","seconds":"平均处理秒数"}}
降级、批量与运行验证
降级仍需满足任务的最低要求
切换模型前核验上下文长度、结构化输出、工具格式、输入模态与权限边界。不能把只支持文本的后备模型静默接到图像任务,也不能因为备用服务支持范围不同,就删除关键约束后伪装成同等结果。
可选择返回已有可信部分、延后离线完成、使用兼容后备或明确报告暂不可用。缓存旧结果时标注适用时间与版本,并检查用户访问范围。恢复服务后,应防止积压任务同时重放形成新一轮突发。
批量提交按条目管理生命周期
非实时任务可以使用批量接口,但价格折扣、完成时限、大小限制和缓存交互是服务特定规则。批次结束不等于所有条目成功;按每项 ID 区分成功、失败、过期与取消,并只重试应重试的条目。Claude Batch processing
批量/并行结果可能乱序,保存请求映射,按 ID 对应输出。重新提交失败项时保留原任务与新尝试的关联,避免重复写入已成功结果;模型批处理本身也不保证下游业务操作恰好执行一次。
验证目标是可控的完整服务
建立包含短请求、长输入、长工具等待、限流、断流、结果未知和后备切换的运行样例。记录成功率、p50/p95 延迟、队列长度、实际尝试数、配额使用和每项成功成本。
每层只重试两次,会放大多少?
“重试两次”容易被误读成总共两次调用,这里统一使用包含首次调用的尝试上限。底层 SDK 与外层工作流都重试时,要按乘积核算最坏请求量,并共享截止时间和重试预算。
Nbackend≤AouterAinner,twait+tattempt≤tremaining
正在呈现知识画面
每层只重试两次,会放大多少?嵌套重试的最坏调用量相乘;退避等待与下一次执行都必须计入同一个截止时间。
{"id":"ai-retry-budget","title":"每层只重试两次,会放大多少?","summary":"嵌套重试的最坏调用量相乘;退避等待与下一次执行都必须计入同一个截止时间。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"outer\" data-i18n=\"outer\"></label><output for=\"outer\" id=\"outer-value\"></output><input id=\"outer\" type=\"range\" min=\"1\" max=\"3\" value=\"3\" step=\"1\"></div><div class=\"control\"><label for=\"inner\" data-i18n=\"inner\"></label><output for=\"inner\" id=\"inner-value\"></output><input id=\"inner\" type=\"range\" min=\"1\" max=\"3\" value=\"3\" step=\"1\"></div><div class=\"control\"><label for=\"remaining\" data-i18n=\"remaining\"></label><output for=\"remaining\" id=\"remaining-value\"></output><input id=\"remaining\" type=\"range\" min=\"1\" max=\"20\" value=\"8\" step=\"1\"></div><div class=\"track-row\"><span data-i18n=\"calls\"></span><div id=\"attempts\" class=\"track\"></div></div><section class=\"panel\"><h3 data-i18n=\"fits\"></h3><div id=\"timeline\"></div></section><div class=\"metrics\"><div><span data-i18n=\"calls\"></span><strong id=\"calls\"></strong></div><div><span data-i18n=\"fits\"></span><strong id=\"fits\"></strong></div></div><div class=\"copy\"><p id=\"explanation\" role=\"status\"></p><p class=\"reserve\" aria-hidden=\"true\" data-i18n=\"explain\"></p></div><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control output{color:var(--accent);text-align:right;font-variant-numeric:tabular-nums;min-width:4em}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1;min-width:0;overflow-wrap:anywhere;font-size:13px;min-height:44px}.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin:16px 0}.actions button{min-width:0;min-height:44px;font-size:13px;overflow-wrap:anywhere}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0;overflow-wrap:anywhere}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:14px;margin:18px 0}.metrics>div{border-left:2px solid var(--accent);padding:0 6px 0 10px;min-width:0}.metrics span{display:block;min-height:3.6em;color:var(--muted);font-size:12px;overflow-wrap:anywhere}.metrics strong{font-size:21px;display:block;min-height:2em;font-weight:550;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.check{display:flex;align-items:center;gap:9px;font-size:13px;margin:14px 0}.check input{width:18px;height:18px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:14px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:44px}.panel{padding:14px;border:1px solid var(--rule);border-radius:9px;margin:16px 0;min-width:0}.panel>header{font-size:13px;font-weight:600;margin-bottom:12px;color:var(--muted)}.panel>div{overflow-wrap:anywhere}.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:8px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:56px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:6px 3px;font-size:12px;overflow-wrap:anywhere}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.table{display:grid;gap:5px;font-size:12px}.tr{display:grid;gap:5px}.tr>div{background:var(--surface);border-radius:4px;padding:9px 6px;min-width:0;min-height:5em;overflow-wrap:anywhere;display:flex;align-items:center}.tr>.gold{background:var(--second-soft)}.tr>.on{background:var(--accent-soft)}.formula,.source{font:13px/1.8 ui-monospace,monospace;white-space:pre-wrap;overflow-wrap:anywhere;margin:16px 0}.formula{border-top:1px solid var(--rule);padding-top:12px;min-height:7.2em}.source{min-height:8em;background:var(--surface);padding:12px;border-radius:8px}.intervals{margin:16px 0}.interval{position:relative;height:38px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/38px ui-monospace,monospace;padding-left:7px}.bars{display:grid;gap:10px;margin:16px 0}.bar-row{display:grid;grid-template-columns:44px minmax(0,1fr) 62px;gap:8px;align-items:center;font:12px ui-monospace,monospace}.bar-track{height:24px;background:var(--surface);border-radius:4px;overflow:hidden}.bar-track i{display:block;height:100%;background:var(--accent);transition:width .2s}.bar-track i.gold{background:var(--second)}.bar-track i.empty{opacity:.2}.bar-row output{text-align:right}.diagram{display:block;width:100%;height:auto;margin:18px 0}.diagram text{font:13px ui-monospace,monospace;fill:var(--ink)}.node{fill:var(--surface);stroke:var(--rule)}.node.on{fill:var(--accent-soft);stroke:var(--accent)}.node.gold{fill:var(--second-soft);stroke:var(--second)}.edge{stroke:var(--rule);stroke-width:2;fill:none}.edge.on{stroke:var(--accent)}.matrix{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin:16px 0}.matrix button{min-width:0;min-height:44px;font:13px ui-monospace,monospace}.matrix button[aria-pressed=true]{background:var(--accent-soft);border-color:var(--accent)}.matrix button.masked{opacity:.35}.legend{font-size:12px;color:var(--muted);min-height:3.6em;overflow-wrap:anywhere}.numeric{font-variant-numeric:tabular-nums}.scope p{line-break:strict}@media(max-width:480px){.presets button,.actions button{font-size:12px}.panel{padding:12px}.metrics{gap:10px}.metrics strong{font-size:19px}.bar-row{grid-template-columns:34px minmax(0,1fr) 58px;gap:6px}}@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n\n.panel>h3{font-size:13px;font-weight:600;margin:0 0 12px;color:var(--muted)}\n\n.bar-row{grid-template-columns:44px minmax(0,1fr) 82px}.bar-row output{white-space:nowrap}@media(max-width:480px){.bar-row{grid-template-columns:34px minmax(0,1fr) 76px}}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';row.style.gridTemplateColumns='repeat('+r.length+',minmax(0,1fr))';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}function plot(id,series,xr,yr,opts={}){const e=$('#'+id),X=x=>42+(x-xr[0])/(xr[1]-xr[0])*298,Y=y=>175-(y-yr[0])/(yr[1]-yr[0])*148;let out='<defs><clipPath id=\"clip-'+id+'\"><rect x=\"42\" y=\"27\" width=\"298\" height=\"148\"/></clipPath></defs>';for(let i=0;i<3;i++){let x=xr[0]+i*(xr[1]-xr[0])/2,y=yr[0]+i*(yr[1]-yr[0])/2;out+='<path class=\"gridline\" d=\"M '+X(x)+' 27V175M42 '+Y(y)+'H340\"/><text x=\"'+X(x)+'\" y=\"194\" text-anchor=\"middle\">'+esc(opts.xfmt?opts.xfmt(x):Number(x.toFixed(2)))+'</text><text x=\"36\" y=\"'+(Y(y)+4)+'\" text-anchor=\"end\">'+esc(opts.yfmt?opts.yfmt(y):Number(y.toFixed(2)))+'</text>';}out+='<g clip-path=\"url(#clip-'+id+')\">';for(const s of series){out+='<path fill=\"none\" stroke=\"'+(s.color||'var(--accent)')+'\" stroke-width=\"2.5\" '+(s.dash?'stroke-dasharray=\"5 4\"':'')+' d=\"'+s.pts.map((p,i)=>(i?'L':'M')+X(p[0]).toFixed(2)+','+Y(p[1]).toFixed(2)).join(' ')+'\"/>';}for(const p of opts.points||[])out+='<circle cx=\"'+X(p[0])+'\" cy=\"'+Y(p[1])+'\" r=\"4\" fill=\"var(--second)\" stroke=\"var(--paper)\" stroke-width=\"1.5\"/>';if(opts.cursor!==undefined)out+='<path d=\"M'+X(opts.cursor)+' 27V175\" stroke=\"var(--muted)\" stroke-dasharray=\"3 3\"/>';e.innerHTML=out+'</g>';e.dataset.curves=JSON.stringify(series.map(s=>s.pts));}const curve=(fn,lo,hi,n=120)=>Array.from({length:n+1},(_,i)=>{const x=lo+(hi-lo)*i/n;return[x,fn(x)]});function inputs(draw){document.querySelectorAll('input,select').forEach(e=>{e.addEventListener('input',draw);e.addEventListener('change',draw)});draw()}const n=id=>+$('#'+id).value;const show=(id,v,d=3)=>set(id,Number(v.toFixed(d)));function bars(id,values,max=1){$('#'+id).className='bars';$('#'+id).innerHTML=values.map(v=>'<div class=\"bar-row\"><span>'+esc(v.label)+'</span><div class=\"bar-track\"><i class=\"'+(v.cls||'')+'\" style=\"width:'+Math.max(0,Math.min(100,v.value/max*100))+'%\"></i></div><output>'+esc(v.text??(v.value*100).toFixed(1)+'%')+'</output></div>').join('')}function softmax(z,T=1){let max=Math.max(...z),w=z.map(x=>Math.exp((x-max)/T)),sum=w.reduce((a,b)=>a+b,0);return w.map(x=>x/sum)}function draw(){let a=n('outer'),b=n('inner'),d=n('remaining');for(const id of['outer','inner','remaining'])set(id+'-value',n(id));cells('attempts',Array.from({length:9},(_,i)=>({text:i<a*b?String(i+1):'—',cls:i<a*b?'on':'empty'})));intervals('timeline',[{label:'wait 0→6',start:0,end:6},{label:'run 6→11',start:6,end:11},{label:'deadline '+d,start:0,end:d,cls:'gold'}],20);set('calls',a*b);set('fits',d>=11?'✓':'×');set('explanation',t('explain'));document.body.dataset.calls=a*b;document.body.dataset.fits=d>=11;}inputs(draw);","audio":false,"strings":{"calls":"最坏底层调用次数","explain":"外层 3 次、内层 3 次,最坏会产生 9 次底层调用。默认剩余 8 秒却需要 11 秒,应停止这一重试计划;增加期限只是满足时间条件,不是自动允许重试。","fits":"等待与执行能否放下","heading":"每层只重试两次,会放大多少?","inner":"内层最多尝试","intro":"改变内外层尝试上限与剩余期限,检查一次重试是否放得下。","outer":"外层最多尝试","remaining":"剩余期限(秒)","reset":"重置","scope":"尝试上限包含首次调用;重试需要等待 6 秒,再执行最多 5 秒。只判定时间预算,不替代幂等性与错误分类。","scopeLabel":"模型范围与约定"}}
继续阅读:发布验收、灰度与恢复。