发布的是代码、模型、提示、工具、权限和索引共同构成的版本。将前面的检查组织成对应这个版本的证据,再决定灰度、扩大流量或停止。本文负责整合验收流程,具体公式和执行边界回到各专题查阅。
固定候选版本与任务边界
先写清系统完成什么,以及怎样确认完成。查询型助手可以要求答案有可核验来源;执行型助手还需要检查实际业务状态。模型返回结束、HTTP 200 或工具没有抛异常,都不能独自证明任务成功。人工处理也是一个明确的结果分支,应统计其比例和成本,不能静默算作自动成功。
候选版本至少需要能够定位应用代码、模型标识与参数、提示模板、工具 schema 和权限策略。使用检索时还要记录文档快照、分块与嵌入配置、索引版本;使用持久状态时要记录状态格式与迁移规则。不要只给 prompt 一个版本号,却让其余依赖在验证后悄悄变化。对于只能使用浮动模型别名的服务,记录实际可获得的版本信息与验证时间,并承认供应方变化会降低复现能力。
相关解释:Agent 循环、多 Agent 编排。
发布前检查表
每一项记录负责人、候选版本、验证时间、证据位置和结论。未执行与失败分开记录,但两者都不能冒充通过;不适用项写明原因,不能仅删除勾选框。
质量与模型配置
| 核对项 | 应留下的证据 | 不能替代它的信号 |
| [ ] 定义任务成功与失败 | 输出要求、业务后置条件、拒绝/转人工规则 | 模型自述“已完成” |
| [ ] 覆盖代表性任务与边界 | 固定评估集,按难度、语言、工具类型等分组结果 | 单条演示或总体平均分 |
| [ ] 保留独立验收集 | 调参集与验收集的划分、重复试验策略 | 在同一批题上反复调到满分 |
| [ ] 校准评分方法 | 程序检查覆盖范围、人工复核、裁判误判样例 | “用了程序评分所以一定客观” |
| [ ] 核对模型能力与参数 | 所选端点实际支持的参数、停止/拒绝/截断分支验证 | 给所有模型统一设置 high effort |
| [ ] 验证结构与业务约束 | schema 校验及数量、状态、资源归属等负例 | JSON 能解析 |
评估的“观察什么”与“谁来评分”是两个维度:业务结果既可由程序检查,也可由人工或模型裁判评估,不能把 outcome 排在程序评分后面当成低级方法。一次成功也不能说明重复执行稳定,应说明每个任务的试验次数与聚合口径。Anthropic:Agent 评估
采样、推理预算和输出上限作用不同。不要把 temperature 机械翻译成 effort,也不要假定所有新模型都取消采样参数。相关原理见 Token 与采样、推理与 thinking、评估与可观测、结构化输出。
上下文、知识与状态
| 核对项 | 应留下的证据 | 典型失败分支 |
| [ ] 给完整请求留预算 | 工具定义、检索材料、历史与输出预留的总预算 | 单篇文档能放下,完整请求超限 |
| [ ] 验证检索与答案两层 | 候选召回、排序、引用对应及无证据时行为 | 答案通顺但引用不支持结论 |
| [ ] 检查版本与权限变化 | 文档更新、删除、撤权后的索引及缓存测试 | 原文已撤权,旧片段仍返回 |
| [ ] 验证会话恢复 | 已完成步骤、待确认结果与外部对象 ID 的恢复记录 | 恢复后把未知结果当未执行 |
| [ ] 验证状态兼容性 | 旧状态读取、新状态写入与回退兼容范围 | 回退应用无法读新 checkpoint |
详见 上下文工程、RAG、记忆与状态。模型选型中的权重、KV 与硬件预算见 模型架构。
执行权限、可靠性与观测
| 核对项 | 应留下的证据 | 典型失败分支 |
| [ ] 执行器独立核验授权 | 跨用户/项目、伪造主体与多余参数的拒绝测试 | 模型传入 approved 就放行 |
| [ ] 限制通用工具能力 | 文件边界、命令参数、网络出站等隔离验证 | 命令名合法却能任意写文件 |
| [ ] 检查注入与敏感数据流 | 合成注入用例、日志脱敏、输出端处理 | 工具资料被提升为可信指令 |
| [ ] 区分超时与业务失败 | 未知写结果的查询、去重与恢复记录 | 超时后重新执行已经成功的动作 |
| [ ] 约束重试和排队 | 总 deadline、最大尝试、有界队列及过载策略 | SDK 与外层重试叠加放大 |
| [ ] 观察完整任务 | 关联 ID、关键步骤耗时、任务结果与完整费用 | 只统计模型响应时间和单次 token |
| [ ] 验证故障分支 | 限流、工具异常、断流、截断、部分批次失败 | 流式请求建立成功就算完成 |
已有任务授权应由系统沿操作链传递,需要额外确认的动作再按具体策略处理;逐次人工点击并不能替代资源权限和幂等机制。详见 安全与防护、成本、性能与可靠性。接入协议与工具发现的边界见 MCP 与 Skills。
证据状态与版本匹配
可以使用四种状态:pass(达到约定条件)、fail(未达到)、not_run(未执行)、not_applicable(有理由且经负责流程确认不适用)。报告存在不代表通过,报告通过也不代表属于当前候选。对必要检查,缺失、失败、未执行或版本不匹配都应阻止自动放行。
下面的本地教学例子仅演示这一规则,不会部署任何东西。把多个实际依赖版本的组合抽象成 candidate;真实系统还要验证证据来源、内容、时效和适用范围,不能信任任意调用者自报的 pass。
from copy import deepcopy
REQUIRED = {"quality", "authorization", "recovery"}
def blockers(candidate, evidence):
problems = []
for name in sorted(REQUIRED):
item = evidence.get(name)
if not isinstance(item, dict):
problems.append((name, "missing"))
elif item.get("candidate") != candidate:
problems.append((name, "stale"))
elif item.get("status") != "pass":
problems.append((name, "not_passed"))
elif not isinstance(item.get("report"), str) or not item["report"].strip():
problems.append((name, "no_report"))
return problems
reports = {name: {"candidate": "release-17", "status": "pass",
"report": f"reports/{name}-17.json"} for name in REQUIRED}
assert blockers("release-17", reports) == []
assert len(blockers("release-18", reports)) == 3
for state in ["fail", "not_run", "not_applicable"]:
changed = deepcopy(reports)
changed["quality"]["status"] = state
assert blockers("release-17", changed) == [("quality", "not_passed")]
changed = deepcopy(reports)
del changed["authorization"]
assert blockers("release-17", changed) == [("authorization", "missing")]
changed = deepcopy(reports)
changed["recovery"]["report"] = ""
assert blockers("release-17", changed) == [("recovery", "no_report")]
print("匹配版本放行;旧版本、未通过、缺项和缺报告均阻止放行")
这里的必要项不允许用“不适用”跳过。条件项则可以由发布规则在明确场景下判定不适用,例如完全无业务写入的查询服务不需要退款去重检查,但仍需要自身的超时恢复检查。不能让候选版本自行缩减必要项列表。
绿色检查,属于哪个候选版本?
清单的勾选状态需要绑定候选版本、输入集合和检查环境,而不是永久存在的布尔值。这个模型只展开版本维度,让“曾经通过”与“当前可用的验收证据”显式分开。
正在呈现知识画面
绿色检查,属于哪个候选版本?发布门检查的是当前候选的有效证据集合;之前版本的绿色结果不能自动继承。
{"id":"ai-release-evidence","title":"绿色检查,属于哪个候选版本?","summary":"发布门检查的是当前候选的有效证据集合;之前版本的绿色结果不能自动继承。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"change\" data-i18n=\"change\"></button><button id=\"quality\" data-i18n=\"quality\"></button><button id=\"authority\" data-i18n=\"authority\"></button><button id=\"recovery\" data-i18n=\"recovery\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"state\"></h3><div id=\"state\"></div></section><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)}let v=17,reports={quality:17,authority:17,recovery:17};function draw(){let ok=Object.values(reports).every(r=>r===v);table('state',[[t('candidate'),'r'+v],...Object.entries(reports).map(([k,r])=>[t(k),'r'+r+' ✓',r===v?'✓':'≠']),[t('gate'),ok?'✓':'×']]);set('explanation',t('explain'));document.body.dataset.accepted=ok;document.body.dataset.version=v;}$('#change').onclick=()=>{v=v===17?18:17;draw()};for(const key of Object.keys(reports))$('#'+key).onclick=()=>{reports[key]=v;draw()};$('#reset').onclick=()=>{v=17;reports={quality:17,authority:17,recovery:17};draw()};draw();","audio":false,"strings":{"authority":"验证权限","candidate":"候选版本","change":"切换候选 r17 / r18","explain":"三项报告仍然显示通过,但如果报告针对 r17,而候选是 r18,发布门应关闭。恢复旧候选时,只有对应版本且仍满足有效期等条件的证据才可复用;本例只检查版本。","gate":"此例发布门","heading":"绿色检查,属于哪个候选版本?","intro":"更新候选版本,再逐项生成当前版本的验收证据。","quality":"验证质量","recovery":"验证恢复","reset":"重置","scope":"三个本地示例检查:质量、权限、恢复。验证按钮代表成功报告,实际发布还需完整风险范围的检查,不执行部署。","scopeLabel":"模型范围与约定","state":"当前候选与证据"}}
灰度与恢复
离线验收通过后,用有限范围的真实任务观察新版本,并与可比较的旧版本流量对照。灰度需要预先定义指标、观察条件和停止规则;只把流量比例设为 1% 并不会自动得到足够证据。低流量业务可能很久都遇不到重要失败场景。Google SRE:Canarying Releases
| 阶段 | 重点确认 | 继续或停止的依据 |
| 影子验证(适用时) | 同一输入下的输出与资源消耗 | 写工具应隔离或模拟,不能重复真实副作用 |
| 小范围灰度 | 质量分组、人工接管、任务费用、尾延迟 | 达到预设观察条件且没有触发停止规则 |
| 逐步扩大 | 配额、排队、缓存与下游容量 | 规模增长后仍在预算与服务目标内 |
| 退回稳定版本 | 新任务路由、在途任务和状态兼容 | 确认恢复结果,并单独处理已发生副作用 |
例如,100 个任务中 95 个成功,并不意味着真实成功率已经被证明至少为 95%。在独立、同分布的简化假设下,95/100 的双侧 95% Wilson 区间下界约为 88.8%;真实任务还有分布变化和相关性。是否放量应结合样本量、关键分组与业务容忍度,不能用一个漂亮的点估计代替判断。
回退计划需要回答三个不同问题:新请求怎样回到旧配置,在途任务怎样停止或完成,已经发生的退款/发信等效果怎样核对。恢复旧 prompt 不会撤销退款;强行给进行中的任务换模型或工具版本也可能破坏其状态契约。优先让任务绑定候选版本,并明确升级、排空和恢复策略。
95% 成功率,证据有多强?
发布证据除了版本正确,还要有足够的样本信息。固定观测比例时,增大样本量会收窄区间;但更多重复的相似任务并不自动满足独立性,也不能替代分组覆盖。这里展示不确定性,不把一个置信下界直接当成通用上线标准。 NIST: Wilson interval
1+nz2p^+2nz2 ± znp^(1−p^)+4n2z2
正在呈现知识画面
95% 成功率,证据有多强?同样的观测成功率,小样本区间更宽;零失败也不证明失败概率为零。
{"id":"ai-wilson-interval","title":"95% 成功率,证据有多强?","summary":"同样的观测成功率,小样本区间更宽;零失败也不证明失败概率为零。","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"rate\" data-i18n=\"rate\"></label><output for=\"rate\" id=\"rate-value\"></output><input id=\"rate\" type=\"range\" min=\"0\" max=\"100\" value=\"95\" step=\"5\"></div><div class=\"presets\" role=\"group\" data-i18n-label=\"size\"><button data-mode=\"small\" data-i18n=\"small\"></button><button data-mode=\"medium\" data-i18n=\"medium\"></button><button data-mode=\"large\" data-i18n=\"large\"></button></div><section class=\"panel\"><h3 data-i18n=\"interval\"></h3><div id=\"band\"></div></section><div class=\"metrics\"><div><span data-i18n=\"interval\"></span><strong id=\"interval\"></strong></div><div><span data-i18n=\"count\"></span><strong id=\"count\"></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.metrics strong{font-size:16px;min-height:4em}","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)}let mode='medium';function draw(){pressed(mode);let N={small:20,medium:100,large:1000}[mode],k=Math.round(n('rate')/100*N),p=k/N,z=1.96,d=1+z*z/N,c=(p+z*z/(2*N))/d,h=z*Math.sqrt(p*(1-p)/N+z*z/(4*N*N))/d,lo=Math.max(0,c-h),hi=Math.min(1,c+h);set('rate-value',n('rate')+'%');intervals('band',[{label:'0% → 100%',start:0,end:1},{label:(lo*100).toFixed(1)+'% — '+(hi*100).toFixed(1)+'%',start:lo,end:hi,cls:'gold'}],1);set('interval',(lo*100).toFixed(1)+'–'+(hi*100).toFixed(1)+'%');set('count',k+' / '+N);set('math','p̂ = '+p.toFixed(2)+'; n = '+N+'\\ncenter = '+c.toFixed(4)+'\\nhalf-width = '+h.toFixed(4));set('explanation',t('explain'));document.body.dataset.interval=JSON.stringify([lo,hi]);}document.querySelectorAll('[data-mode]').forEach(e=>e.onclick=()=>{mode=e.dataset.mode;draw()});inputs(draw);","audio":false,"strings":{"count":"成功 / 总试次","explain":"默认 95/100 的区间约为 88.8%–97.8%,并不是“真实成功率有 95% 概率落在这个已计算区间”。95% 描述的是重复抽样下构造方法的覆盖率目标。","heading":"95% 成功率,证据有多强?","interval":"95% Wilson 区间","intro":"保持成功比例,改变样本数,查看 Wilson 区间。","large":"1000","medium":"100","rate":"观测成功率(%)","reset":"重置","scope":"独立、同分布的二项试次,双侧近似 95% Wilson 区间,z=1.96。相关任务、选择偏差和重复试验需另行处理。","scopeLabel":"模型范围与约定","size":"样本量","small":"20"}}
发布后的闭环
事故进入回归前,先保存足以解释失败的版本与必要轨迹,并对个人信息和敏感数据做适当处理。将真实数据直接复制到长期评估集可能扩大暴露范围;可以构造保留同一失败条件的合成案例。
一条有用的回归不只是“再次问同一个问题”,还要记录预期结果和失败位置。例如跨租户工单事件可以拆成执行器拒绝测试与端到端注入测试:前者验证权限边界,后者观察模型是否继续正确完成本来任务。修改后跑直接相关测试,再根据依赖影响补充回归,发布前重新确认必要证据属于当前组合。
检查表会随系统变化调整。删除失效、重复的检查并保留理由,新增能覆盖真实缺口的检查;维护目标是让证据更能预测生产行为,而不是让勾选项越来越多。