第5节 压测与调优:成功率、成本、时长的量化优化一句话总结:用30个真实Issue做压测——基线→四轮调优(验证器/预算/模型分层/并行度)的量化记录表;找出本项目的帕累托最优配置,以及每轮数字背后的具体代码改动。本文导航一、压测方法论与基线:先固定尺子再量东西二、四轮调优与量化记录三、压测脚本:一键复现整张表四、帕累托最优:87%是不是终点五、两轮代码实况:预算三道保险与模型分层路由六、压测数据怎么变成决策七、踩坑实录小结一、压测方法论与基线:先固定尺子再量东西调优最忌讳两件事:一是改了配置不记录,二是每次用不同的Issue集跑,数字根本不可比。所以在动手改任何东西之前,我先把"尺子"固定下来。数据集:从我仓库的历史Issue里挑了30个已修复的真实Issue,按难度分层——10个简单(报错明确、单文件)、15个中等(跨文件、需要理解上下文)、5个困难(需要重构或多轮推理)。每个Issue配一份golden patch(当时人工修复的PR),压测时把仓库回退到bug状态,让流水线从零开始修。三个指标定义(口径沿用第4节的拆分):指标计算口径说明成功率30个任务中产出可合并PR的占比我人工验收,以golden patch行为一致为准成本/任务LLM日志估算成本 ÷ 30含分诊+修复+审查全部调用时长/任务领票到PR创建的平均墙钟时间含重试环境控制:温度固定、seed固定(能固定的都固定)、凌晨2点-5点跑(避开API高峰)、每轮跑3遍取中位数(LLM有随机性,单次结果不可信)。第12章第1节说"你的循环正在悄悄退化而你以为它在变强",反过来也一样——没有固定数据集,你连自己在变强还是变弱都说不清。整个压测流程:是否冻结30个Issue回退到bug状态清空票据库导入30张pending票当前配置跑流水线采集:成功率成本、时长记录到results.jsonl还有下一轮配置?改动一个变量汇总对比表找帕累托最优关键纪律在G那一步:每轮只改一个变量。我见过有人一口气改了提示词、模型、并行度三个东西,成功率从70%涨到80%,然后永远说不清是哪个改动立了功。调优本身也是一个循环,闭环长这样:是否跑压测集失败归因改一个变量复测对比达标了吗固化配置并记录压测→归因→改动→复测,和第6章那个最小ReAct循环完全同构——调优方法本身就是循环工程的第一课。1.1 基线有多惨:67%/$0.15/4.5min基线配置就是第3节完成时的状态:无三件套门禁(只有模型自评)、无预算控制、全部用gpt-4o、串行单工人。跑出来:成功率67%(20/30),成本$0.15/任务,时长4.5分钟/任务。基线的失败样本我逐个人工归因了,这张归因表直接决定了后面四轮调优的顺序:失败原因票数占失败比对应调优方向改完代码没跑测试,PR带着坏代码被拒550%第1轮:验证器门禁doom loop空转烧token直到超时330%第2轮:预算控制简单任务也用gpt-4o,贵且不必要110%第3轮:模型分层串行排队,长任务拖住短任务110%第4轮:并行度调优顺序由数据决定,不由直觉决定——失败一半出在"没有验证门禁",那就先修这个;并行度只贡献10%的问题,放最后。压测集的"冻结"还有一层含义:golden patch入库后我给自己立了规矩——压测集只随季度评审更新,绝不为"让数字好看"临时换题。第12章第2节讲轨迹数据集是评估金矿,这块冻结的30票就是本项目最值钱的数据资产:任何提示词、模型、配置的改动,都能在同一把尺子上量出delta。有人问我为什么不反复跑同一个配置冲88%,因为同一配置复测的方差就有±2pt,在噪音里追1个点是自欺。二、四轮调优与量化记录总表先贴,这是本节的骨架,也是整个项目最有说服力的一张表:轮次改动成功率成本/任务时长/任务边际分析基线—67%$0.154.5min—1+验证器门禁77%$0.133.8min成功率+10pt,失败提前暴露省了重试钱2+预算控制80%$0.103.2min砍掉doom loop,+3pt,时长-0.6min3+模型分层83%$0.072.9min成本-30%,checker视角独立反而+3pt4+并行度387%$0.062.5min吞吐×3摊薄固定开销,+4pt四轮改动的具体内容:第1轮:验证器门禁。第3节的validate_all(pytest+ruff+mypy)加进工人循环,all_ok才允许进checker。改动就十行,效果最猛:带病PR从流水线里消失了,而且失败反馈变成了结构化报错,模型下一轮修复的命中率明显变高。成本下降的原因有点反直觉——三件套本地跑不要钱,拦下来的坏代码不用再烧一轮LLM审查。第2轮:预算控制。三道保险(第3章第9节的doom loop防线):单票max_steps=12、max_tokens=200000、timeout=300s,超限直接fail转重试/升级。基线里3张票空转到超时,每张烧掉$0.4以上,这轮把成本直接压掉$0.03/任务。第3轮:模型分层。分诊和checker和judge都换成gpt-4o-mini,只有maker留gpt-4o。成本降30%不出意外,成功率+3pt是意外之喜——mini当checker不是妥协,是视角独立性带来的真实收益(第3节详细讲过)。第4轮:并行度3。ThreadPoolExecutor起3个工人配worktree隔离。时长从2.9min降到2.5min(不是3倍速,因为GitHub API和磁盘IO有共享瓶颈),成本再降是因为分诊、看板这些固定开销被吞吐摊薄。成功率+4pt的原因最有意思:3张压测集里"重试排队太久导致租约超时"的票,在并行模式下当天就能完成重试。四轮里唯一动过的配置最终收敛成pareto_config.yaml:# pareto_config.yaml —— 第4轮结束后的最优配置model:maker:"gpt-4o"# 重活:写测试+写修复checker:"gpt-4o-mini"# 审查:视角独立+便宜judge:"gpt-4o-mini"# 验收条件核对loop:max_steps:12# 单票最多12轮修复max_tokens:200000# 单票token预算timeout:300# 单票超时秒数validators:pytest:true# 三件套全开,门禁从严ruff:truemypy:trueparallel:max_workers:3# 压测证明3是甜点,5开始掉成功率worktree_isolation:true# 并行必须隔离三、压测脚本:一键复现整张表压测本身也要工程化,不然30个Issue×4轮×3遍=360次运行的记录工作能淹死人。脚本的职责:准备环境、跑流水线、收指标、落JSONL:# scripts/bench.py"""压测脚本:重置环境→灌票→跑流水线→采集指标→写入results.jsonl。"""importjson,statistics,subprocess,timefrompathlibimportPathfromdatetimeimportdatetimefrompydanticimportBaseModel,FieldclassBenchResult(BaseModel):"""一轮压测的记录,pydantic保证落盘数据结构永远一致。"""round_name:strsuccess:int=Field(ge=0,le=30)cost_per_task:floatduration_per_task:float# 分钟ran_at:strdefrun_round(name:str)->BenchResult:"""跑一轮:3遍取中位数,指标从票据库和LLM日志聚合而来。"""results=[]for_inrange(3):subprocess.run(["uv","run","python","-m","ai_loop_course.bench_reset"],check=True)# 回退bug+清库subprocess.run(["uv","run","python","-m","ai_loop_course.night_pipeline"],check=True)stats=json.loads(subprocess.run(["uv","run","python","-m","ai_loop_course.dashboard","--json"],capture_output=True,text=True,check=True).stdout)results.append(stats)med=BenchResult(round_name=name,success=int(statistics.median(r["done"]forrinresults)),cost_per_task=round(statistics.median(r["cost"]forrinresults)/30,4),duration_per_task=round(statistics.median(r["duration"]forrinresults)/60,2),ran_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),)withopen("results.jsonl","a",encoding="utf-8")asf:# 追加落盘,永不覆盖历史f.write(med.model_dump_json()+"\n")print(f"[{name}] 成功率{med.success}/30 成本${med.cost_per_task}"f"时长{med.duration_per_task}min")returnmedif__name__=="__main__":fornamein["baseline","validators","budget","layering","parallel3"]:run_round(name)真实跑完一晚的控制台输出(每轮3遍的中位数已经合并):[baseline] 成功率 20/30 (66.7%) 成本$0.1523 时长4.51min [validators] 成功率 23/30 (76.7%) 成本$0.1310 时长3.83min ← 门禁生效 [budget] 成功率 24/30 (80.0%) 成本$0.1020 时长3.24min ← doom loop消失 [layering] 成功率 25/30 (83.3%) 成本$0.0712 时长2.91min ← mini接管checker [parallel3] 成功率 26/30 (86.7%) 成本$0.0610 时长2.52min ← 吞吐摊薄 全部结果已追加到 results.jsonl(5轮×3遍=15条记录) 2026-09-29 05:41:20 | INFO | bench | 📊 LLM调用总结:共3721次 | 输入avg=2314字符 输出avg=612字符 单次耗时avg=6.8s max=58.3s注意最后一行:压测整晚总共3721次LLM调用,全部带全字段日志。没有第1节那套日志基建,这张表里的成本列根本无从算起——量化优化的前提是量化记录,而记录是基建给的,不是压测脚本给的。四、帕累托最优:87%是不是终点87%之后我还偷偷跑过第5轮:maker换成更强的模型+并行度提到5。结果:成功率88%(+1pt),成本$0.11(+80%),时长2.9min(变慢,review带宽成瓶颈)。这就是典型的帕累托边界——多花的钱买不来等比的收益。几个候选配置摆在一起看:配置成功率成本/任务时长/任务判定基线67%$0.154.5min便宜的不该省:没门禁帕累托最优(第4轮)87%$0.062.5min✅ 推荐强maker+并行588%$0.112.9min❌ +1pt不值+80%成本全mini(极限省钱)71%$0.032.1min❌ 成功率崩回基线附近全mini那行我特意跑了一下当反面参照:省下的$0.03换来16个百分点的成功率,本末倒置。帕累托最优不是"最便宜的配置",是"花在刀刃上的配置"——本项目刀刃就是maker的生成能力,别处都能省,这里不能。剩下13%的失败票我也归因了:8成是困难层的多文件重构任务,模型能定位问题但给出的方案破坏了其他调用方。这些票走escalated人工处理,单张人工成本约15分钟。按87%自动化+13%人工的混合模式算总账:30个任务的端到端成本 = 26×$0.06 + 4×(15min人力≈$4) ≈ $17.6,对比全人工30×15min≈$120,成本降到1/7——这才是拿给老板看的算法,第2章第6节"AI编程收益怎么算"的实战版。最后把三个指标的张力说透:成功率、成本、时长是一个三角。验证器轮用一点时长换了10个点的成功率,分层轮证明三者可以同向改善,但"强maker"轮证明边界存在——三角的形状由业务决定。开源项目不在乎2.5min还是4min,在乎成本;商业交付在乎时长和成功率,成本敏感度低。调优就是拿着业务权重在三角里找自己的最优点,不存在通用答案。五、两轮代码实况:预算三道保险与模型分层路由5.1 预算三道保险的代码实况第2轮调优的"预算控制"落在代码上是一个不到40行的Budget对象。三维——步数、token、墙钟,任何一维超限立即熔断:# src/ai_loop_course/budget.py"""三维预算:步数/token/墙钟,任一超限熔断本票,走正常失败链路。"""importtimefrompydanticimportBaseModel,FieldclassBudgetExceeded(Exception):"""预算熔断:不是bug,是保险丝烧断。"""classBudget(BaseModel):"""单票预算账本:maker每轮调用spend()记账,超限抛熔断。"""max_steps:int=12max_tokens:int=200_000timeout_s:int=300steps:int=0tokens:int=0started:float=Field(default_factory=time.time)defspend(self,round_tokens:int)->None:"""每轮修复结束记账,三道检查独立生效。"""self.steps+=1self.tokens+=round_tokensifself.steps>self.max_steps:raiseBudgetExceeded(f"步数超限{self.steps}>{self.max_steps}")ifself.tokens>self.max_tokens:raiseBudgetExceeded(f"token超限{self.tokens}>{self.max_tokens}")iftime.time()-self.started>self.timeout_s:raiseBudgetExceeded(f"墙钟超时{time.time()-self.started:.0f}s")熔断在日志里的样子:2026-09-29 03:12:44 | WARNING | budget | 票据#118熔断:步数超限 13>12(已烧87.4k token ≈$0.11) 2026-09-29 03:12:45 | INFO | fix_worker | ❌ 票据#118第2次失败:BudgetExceeded: 步数超限 2026-09-29 03:12:45 | INFO | ticket_store | 票据#118 → pending(attempts=2,等待重试)这就是第2轮省下$0.03/任务的原理:doom loop在第12步被掐断,单票烧钱上限被硬性封顶在$0.12左右,而基线里最疯的一张票绕了40多轮烧掉$0.4+。保险丝的价值不在"少烧",在烧有上限——预算把"可能无限"变成了"最多如此",这对敢让它无人值守跑整夜是决定性的。有人会问:API不是自带max_tokens参数吗?不够。API限制的是单次调用,doom loop烧的是几十次调用的总和——每次都合法,加起来爆炸。预算必须记在"票"这个单位上、跨轮次累计,这正是它要由Python对象记账、而不是靠API参数的原因。墙钟那道保险还兼职抓一种隐蔽泄漏:worktree里的pytest偶尔hang住(等一个永远不会来的输入),步数和token都不涨但时间在走,只有墙钟能发现它。5.2 模型分层的路由实现第3轮的"模型分层"是把"谁干什么活"从代码抽到配置,一个ModelTier模型说清:# src/ai_loop_course/llm_client.py(分层路由节选)frompydanticimportBaseModelclassModelTier(BaseModel):"""角色→模型的映射表:改配置不改代码,压测每轮只改这里。"""triage:str="gpt-4o-mini"# 分诊:分类任务maker:str="gpt-4o"# 修复:生成任务,唯一的重模型checker:str="gpt-4o-mini"# 审查:视角独立judge:str="gpt-4o-mini"# 验收核对调用侧client.for_role("checker")拿到对应模型实例,日志自动记录实际命中的模型名——第1节的全字段日志在这里再次发挥作用:每轮调优前后的成本对比,就是按日志里的model字段GROUP BY出来的。分层前后的单任务成本结构(日志实测):阶段模型调用/票分层前成本占比分层后成本占比分诊gpt-4o→mini1.014%3%修复makergpt-4o2.655%78%审查checkergpt-4o→mini2.126%9%验收judgegpt-4o→mini0.85%10%注:这张表的口径是"该轮配置下单次通过的成本结构",不含重试放大;第4节终态表里 maker 占 68%、另有 18% 重试放大,两者口径不同,别直接对比。分层后78%的钱花在唯一干重活的maker上——“刀刃"的可视化。mini接管的三个角色全部是"读得多写得少、判断准就行"的任务,恰好是便宜模型的长项。反过来,前面"全mini"配置的失败证明maker不能跟着降级:分层的前提是认清每个环节的任务性质再匹配模型,而不是无脑全换。judge是最容易被忽略的角色:它核对"验收条件逐条满足”,用的是分诊写下的条件清单——第2节强调验收条件必须可命令行验证,就是为了让judge能把条件转成本地命令跑一遍再下结论,而不是凭感觉说通过。六、压测数据怎么变成决策压测的产出不是一张好看的表,是能直接执行的决策规则。先看时长分位数随轮次的变化:轮次时长P50时长P90P90的来源基线3.8min11.2mindoom loop票绕圈+验证器3.1min6.8min坏代码提前被拦+预算2.7min4.6min超时票第12步掐断+分层2.4min3.9minmini响应快于gpt-4o+并行2.2min3.5min队列等待消除读法:P50的改善是"典型任务变快",P90的改善是"最坏情况变可控",分别对应体验和承诺。基线的P90是P50的3倍,尾部完全失控;四轮之后比值收敛到1.6,系统从"平均还行、偶尔吓人"变成"可预期"。顺手做了max_steps的敏感性扫描,验证12不是拍的:max_steps成功率成本/任务判定882%$0.055掐太早,确有票需要多轮1287%$0.061甜点1687%$0.072成功率纹丝不动,白烧4步8→12成功率+5pt,说明确有票需要多轮;12→16成功率不动、成本+18%,说明12步修不好的票16步也修不好——重试上限的甜点就是"边际收益归零点"。三条决策规则就此定稿,成了我的调优SOP:一看分位数比值——P90/P50超过2就优先查尾部(通常是doom loop或hang);二看错误分布——同一类错误连续两轮占失败的一半以上,修它优先于任何性能优化;三看边际成本——任何配置改动先跑10票小集,成本涨幅超20%直接否决,不进30票全量。这套规则换任何项目都能直接搬。七、踩坑实录坑1:压测集污染。第2轮跑的时候我忘了回退仓库,5个"已修复"的Issue在干净分支上跑,工人发现bug不存在直接把票标done。当轮成功率虚高到92%,差点让我得出"预算控制大幅提升成功率"的错误结论。修复:bench_reset把"git reset到bug commit+清空票据库+清worktree"做成原子步骤,压测脚本只调它。压测环境的污染是最隐蔽的坑,因为它让数字变好看。坑2:把P50当真相。早期我报平均时长,被一张卡了28分钟的doom loop票拉到均值6分钟,完全掩盖了中位数2.4分钟的事实。之后所有时长指标我都同时报P50/P90:P50反映典型体验,P90反映尾延迟,doom loop这种尾巴问题只在P90里现形。均值会撒谎,中位数和分位数不会。坑3:在高峰期压测。有轮压测排在下午3点,API限流让3张票超时失败,成功率莫名掉了7个点,我以为是配置回退闹鬼,白查了两小时。之后压测固定在凌晨,且每轮之间加5分钟冷却。性能测试要控制变量,网络拥堵是最大的隐藏变量。小结先固定尺子:30个分层Issue的冻结数据集+三个明确定义的指标+每轮只改一个变量,否则一切对比都是自欺。失败归因决定调优顺序:基线50%的失败出在缺验证门禁,所以验证器第一、并行度最后——数据排序,不是感觉排序。四轮调优:验证器→预算→分层→并行,成功率67%→87%,成本$0.15→$0.06,时长4.5min→2.5min。帕累托最优:87%/$0.06/2.5min之后,多花钱买不回等比收益;剩下的13%交给escalated人工,混合模式总成本约为全人工的1/7。下节预告配置定了、数字好看了,最后一公里是让它稳定地在别人也能运维的环境里跑起来。第13章第6节《部署:容器化与CI/CD集成》——Dockerfile与compose、密钥注入、定时任务三选一、版本升级与状态迁移、上线前检查清单。如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!本系列持续更新中,关注不迷路~文章编号:第13章第5节 | 总进度:95/120 | 预计阅读时间:9分钟
第5节 压测与调优:成功率、成本、时长的量化优化一句话总结:用30个真实Issue做压测——基线→四轮调优(验证器/预算/模型分层/并行度)的量化记录表;找出本项目的帕累托最优配置,以及每轮数字背后的具体代码改动。本文导航一、压测方法论与基线:先固定尺子再量东西二、四轮调优与量化记录三、压测脚本:一键复现整张表四、帕累托最优:87%是不是终点五、两轮代码实况:预算三道保险与模型分层路由六、压测数据怎么变成决策七、踩坑实录小结一、压测方法论与基线:先固定尺子再量东西调优最忌讳两件事:一是改了配置不记录,二是每次用不同的Issue集跑,数字根本不可比。所以在动手改任何东西之前,我先把"尺子"固定下来。数据集:从我仓库的历史Issue里挑了30个已修复的真实Issue,按难度分层——10个简单(报错明确、单文件)、15个中等(跨文件、需要理解上下文)、5个困难(需要重构或多轮推理)。每个Issue配一份golden patch(当时人工修复的PR),压测时把仓库回退到bug状态,让流水线从零开始修。三个指标定义(口径沿用第4节的拆分):指标计算口径说明成功率30个任务中产出可合并PR的占比我人工验收,以golden patch行为一致为准成本/任务LLM日志估算成本 ÷ 30含分诊+修复+审查全部调用时长/任务领票到PR创建的平均墙钟时间含重试环境控制:温度固定、seed固定(能固定的都固定)、凌晨2点-5点跑(避开API高峰)、每轮跑3遍取中位数(LLM有随机性,单次结果不可信)。第12章第1节说"你的循环正在悄悄退化而你以为它在变强",反过来也一样——没有固定数据集,你连自己在变强还是变弱都说不清。整个压测流程:是否冻结30个Issue回退到bug状态清空票据库导入30张pending票当前配置跑流水线采集:成功率成本、时长记录到results.jsonl还有下一轮配置?改动一个变量汇总对比表找帕累托最优关键纪律在G那一步:每轮只改一个变量。我见过有人一口气改了提示词、模型、并行度三个东西,成功率从70%涨到80%,然后永远说不清是哪个改动立了功。调优本身也是一个循环,闭环长这样:是否跑压测集失败归因改一个变量复测对比达标了吗固化配置并记录压测→归因→改动→复测,和第6章那个最小ReAct循环完全同构——调优方法本身就是循环工程的第一课。1.1 基线有多惨:67%/$0.15/4.5min基线配置就是第3节完成时的状态:无三件套门禁(只有模型自评)、无预算控制、全部用gpt-4o、串行单工人。跑出来:成功率67%(20/30),成本$0.15/任务,时长4.5分钟/任务。基线的失败样本我逐个人工归因了,这张归因表直接决定了后面四轮调优的顺序:失败原因票数占失败比对应调优方向改完代码没跑测试,PR带着坏代码被拒550%第1轮:验证器门禁doom loop空转烧token直到超时330%第2轮:预算控制简单任务也用gpt-4o,贵且不必要110%第3轮:模型分层串行排队,长任务拖住短任务110%第4轮:并行度调优顺序由数据决定,不由直觉决定——失败一半出在"没有验证门禁",那就先修这个;并行度只贡献10%的问题,放最后。压测集的"冻结"还有一层含义:golden patch入库后我给自己立了规矩——压测集只随季度评审更新,绝不为"让数字好看"临时换题。第12章第2节讲轨迹数据集是评估金矿,这块冻结的30票就是本项目最值钱的数据资产:任何提示词、模型、配置的改动,都能在同一把尺子上量出delta。有人问我为什么不反复跑同一个配置冲88%,因为同一配置复测的方差就有±2pt,在噪音里追1个点是自欺。二、四轮调优与量化记录总表先贴,这是本节的骨架,也是整个项目最有说服力的一张表:轮次改动成功率成本/任务时长/任务边际分析基线—67%$0.154.5min—1+验证器门禁77%$0.133.8min成功率+10pt,失败提前暴露省了重试钱2+预算控制80%$0.103.2min砍掉doom loop,+3pt,时长-0.6min3+模型分层83%$0.072.9min成本-30%,checker视角独立反而+3pt4+并行度387%$0.062.5min吞吐×3摊薄固定开销,+4pt四轮改动的具体内容:第1轮:验证器门禁。第3节的validate_all(pytest+ruff+mypy)加进工人循环,all_ok才允许进checker。改动就十行,效果最猛:带病PR从流水线里消失了,而且失败反馈变成了结构化报错,模型下一轮修复的命中率明显变高。成本下降的原因有点反直觉——三件套本地跑不要钱,拦下来的坏代码不用再烧一轮LLM审查。第2轮:预算控制。三道保险(第3章第9节的doom loop防线):单票max_steps=12、max_tokens=200000、timeout=300s,超限直接fail转重试/升级。基线里3张票空转到超时,每张烧掉$0.4以上,这轮把成本直接压掉$0.03/任务。第3轮:模型分层。分诊和checker和judge都换成gpt-4o-mini,只有maker留gpt-4o。成本降30%不出意外,成功率+3pt是意外之喜——mini当checker不是妥协,是视角独立性带来的真实收益(第3节详细讲过)。第4轮:并行度3。ThreadPoolExecutor起3个工人配worktree隔离。时长从2.9min降到2.5min(不是3倍速,因为GitHub API和磁盘IO有共享瓶颈),成本再降是因为分诊、看板这些固定开销被吞吐摊薄。成功率+4pt的原因最有意思:3张压测集里"重试排队太久导致租约超时"的票,在并行模式下当天就能完成重试。四轮里唯一动过的配置最终收敛成pareto_config.yaml:# pareto_config.yaml —— 第4轮结束后的最优配置model:maker:"gpt-4o"# 重活:写测试+写修复checker:"gpt-4o-mini"# 审查:视角独立+便宜judge:"gpt-4o-mini"# 验收条件核对loop:max_steps:12# 单票最多12轮修复max_tokens:200000# 单票token预算timeout:300# 单票超时秒数validators:pytest:true# 三件套全开,门禁从严ruff:truemypy:trueparallel:max_workers:3# 压测证明3是甜点,5开始掉成功率worktree_isolation:true# 并行必须隔离三、压测脚本:一键复现整张表压测本身也要工程化,不然30个Issue×4轮×3遍=360次运行的记录工作能淹死人。脚本的职责:准备环境、跑流水线、收指标、落JSONL:# scripts/bench.py"""压测脚本:重置环境→灌票→跑流水线→采集指标→写入results.jsonl。"""importjson,statistics,subprocess,timefrompathlibimportPathfromdatetimeimportdatetimefrompydanticimportBaseModel,FieldclassBenchResult(BaseModel):"""一轮压测的记录,pydantic保证落盘数据结构永远一致。"""round_name:strsuccess:int=Field(ge=0,le=30)cost_per_task:floatduration_per_task:float# 分钟ran_at:strdefrun_round(name:str)->BenchResult:"""跑一轮:3遍取中位数,指标从票据库和LLM日志聚合而来。"""results=[]for_inrange(3):subprocess.run(["uv","run","python","-m","ai_loop_course.bench_reset"],check=True)# 回退bug+清库subprocess.run(["uv","run","python","-m","ai_loop_course.night_pipeline"],check=True)stats=json.loads(subprocess.run(["uv","run","python","-m","ai_loop_course.dashboard","--json"],capture_output=True,text=True,check=True).stdout)results.append(stats)med=BenchResult(round_name=name,success=int(statistics.median(r["done"]forrinresults)),cost_per_task=round(statistics.median(r["cost"]forrinresults)/30,4),duration_per_task=round(statistics.median(r["duration"]forrinresults)/60,2),ran_at=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),)withopen("results.jsonl","a",encoding="utf-8")asf:# 追加落盘,永不覆盖历史f.write(med.model_dump_json()+"\n")print(f"[{name}] 成功率{med.success}/30 成本${med.cost_per_task}"f"时长{med.duration_per_task}min")returnmedif__name__=="__main__":fornamein["baseline","validators","budget","layering","parallel3"]:run_round(name)真实跑完一晚的控制台输出(每轮3遍的中位数已经合并):[baseline] 成功率 20/30 (66.7%) 成本$0.1523 时长4.51min [validators] 成功率 23/30 (76.7%) 成本$0.1310 时长3.83min ← 门禁生效 [budget] 成功率 24/30 (80.0%) 成本$0.1020 时长3.24min ← doom loop消失 [layering] 成功率 25/30 (83.3%) 成本$0.0712 时长2.91min ← mini接管checker [parallel3] 成功率 26/30 (86.7%) 成本$0.0610 时长2.52min ← 吞吐摊薄 全部结果已追加到 results.jsonl(5轮×3遍=15条记录) 2026-09-29 05:41:20 | INFO | bench | 📊 LLM调用总结:共3721次 | 输入avg=2314字符 输出avg=612字符 单次耗时avg=6.8s max=58.3s注意最后一行:压测整晚总共3721次LLM调用,全部带全字段日志。没有第1节那套日志基建,这张表里的成本列根本无从算起——量化优化的前提是量化记录,而记录是基建给的,不是压测脚本给的。四、帕累托最优:87%是不是终点87%之后我还偷偷跑过第5轮:maker换成更强的模型+并行度提到5。结果:成功率88%(+1pt),成本$0.11(+80%),时长2.9min(变慢,review带宽成瓶颈)。这就是典型的帕累托边界——多花的钱买不来等比的收益。几个候选配置摆在一起看:配置成功率成本/任务时长/任务判定基线67%$0.154.5min便宜的不该省:没门禁帕累托最优(第4轮)87%$0.062.5min✅ 推荐强maker+并行588%$0.112.9min❌ +1pt不值+80%成本全mini(极限省钱)71%$0.032.1min❌ 成功率崩回基线附近全mini那行我特意跑了一下当反面参照:省下的$0.03换来16个百分点的成功率,本末倒置。帕累托最优不是"最便宜的配置",是"花在刀刃上的配置"——本项目刀刃就是maker的生成能力,别处都能省,这里不能。剩下13%的失败票我也归因了:8成是困难层的多文件重构任务,模型能定位问题但给出的方案破坏了其他调用方。这些票走escalated人工处理,单张人工成本约15分钟。按87%自动化+13%人工的混合模式算总账:30个任务的端到端成本 = 26×$0.06 + 4×(15min人力≈$4) ≈ $17.6,对比全人工30×15min≈$120,成本降到1/7——这才是拿给老板看的算法,第2章第6节"AI编程收益怎么算"的实战版。最后把三个指标的张力说透:成功率、成本、时长是一个三角。验证器轮用一点时长换了10个点的成功率,分层轮证明三者可以同向改善,但"强maker"轮证明边界存在——三角的形状由业务决定。开源项目不在乎2.5min还是4min,在乎成本;商业交付在乎时长和成功率,成本敏感度低。调优就是拿着业务权重在三角里找自己的最优点,不存在通用答案。五、两轮代码实况:预算三道保险与模型分层路由5.1 预算三道保险的代码实况第2轮调优的"预算控制"落在代码上是一个不到40行的Budget对象。三维——步数、token、墙钟,任何一维超限立即熔断:# src/ai_loop_course/budget.py"""三维预算:步数/token/墙钟,任一超限熔断本票,走正常失败链路。"""importtimefrompydanticimportBaseModel,FieldclassBudgetExceeded(Exception):"""预算熔断:不是bug,是保险丝烧断。"""classBudget(BaseModel):"""单票预算账本:maker每轮调用spend()记账,超限抛熔断。"""max_steps:int=12max_tokens:int=200_000timeout_s:int=300steps:int=0tokens:int=0started:float=Field(default_factory=time.time)defspend(self,round_tokens:int)->None:"""每轮修复结束记账,三道检查独立生效。"""self.steps+=1self.tokens+=round_tokensifself.steps>self.max_steps:raiseBudgetExceeded(f"步数超限{self.steps}>{self.max_steps}")ifself.tokens>self.max_tokens:raiseBudgetExceeded(f"token超限{self.tokens}>{self.max_tokens}")iftime.time()-self.started>self.timeout_s:raiseBudgetExceeded(f"墙钟超时{time.time()-self.started:.0f}s")熔断在日志里的样子:2026-09-29 03:12:44 | WARNING | budget | 票据#118熔断:步数超限 13>12(已烧87.4k token ≈$0.11) 2026-09-29 03:12:45 | INFO | fix_worker | ❌ 票据#118第2次失败:BudgetExceeded: 步数超限 2026-09-29 03:12:45 | INFO | ticket_store | 票据#118 → pending(attempts=2,等待重试)这就是第2轮省下$0.03/任务的原理:doom loop在第12步被掐断,单票烧钱上限被硬性封顶在$0.12左右,而基线里最疯的一张票绕了40多轮烧掉$0.4+。保险丝的价值不在"少烧",在烧有上限——预算把"可能无限"变成了"最多如此",这对敢让它无人值守跑整夜是决定性的。有人会问:API不是自带max_tokens参数吗?不够。API限制的是单次调用,doom loop烧的是几十次调用的总和——每次都合法,加起来爆炸。预算必须记在"票"这个单位上、跨轮次累计,这正是它要由Python对象记账、而不是靠API参数的原因。墙钟那道保险还兼职抓一种隐蔽泄漏:worktree里的pytest偶尔hang住(等一个永远不会来的输入),步数和token都不涨但时间在走,只有墙钟能发现它。5.2 模型分层的路由实现第3轮的"模型分层"是把"谁干什么活"从代码抽到配置,一个ModelTier模型说清:# src/ai_loop_course/llm_client.py(分层路由节选)frompydanticimportBaseModelclassModelTier(BaseModel):"""角色→模型的映射表:改配置不改代码,压测每轮只改这里。"""triage:str="gpt-4o-mini"# 分诊:分类任务maker:str="gpt-4o"# 修复:生成任务,唯一的重模型checker:str="gpt-4o-mini"# 审查:视角独立judge:str="gpt-4o-mini"# 验收核对调用侧client.for_role("checker")拿到对应模型实例,日志自动记录实际命中的模型名——第1节的全字段日志在这里再次发挥作用:每轮调优前后的成本对比,就是按日志里的model字段GROUP BY出来的。分层前后的单任务成本结构(日志实测):阶段模型调用/票分层前成本占比分层后成本占比分诊gpt-4o→mini1.014%3%修复makergpt-4o2.655%78%审查checkergpt-4o→mini2.126%9%验收judgegpt-4o→mini0.85%10%注:这张表的口径是"该轮配置下单次通过的成本结构",不含重试放大;第4节终态表里 maker 占 68%、另有 18% 重试放大,两者口径不同,别直接对比。分层后78%的钱花在唯一干重活的maker上——“刀刃"的可视化。mini接管的三个角色全部是"读得多写得少、判断准就行"的任务,恰好是便宜模型的长项。反过来,前面"全mini"配置的失败证明maker不能跟着降级:分层的前提是认清每个环节的任务性质再匹配模型,而不是无脑全换。judge是最容易被忽略的角色:它核对"验收条件逐条满足”,用的是分诊写下的条件清单——第2节强调验收条件必须可命令行验证,就是为了让judge能把条件转成本地命令跑一遍再下结论,而不是凭感觉说通过。六、压测数据怎么变成决策压测的产出不是一张好看的表,是能直接执行的决策规则。先看时长分位数随轮次的变化:轮次时长P50时长P90P90的来源基线3.8min11.2mindoom loop票绕圈+验证器3.1min6.8min坏代码提前被拦+预算2.7min4.6min超时票第12步掐断+分层2.4min3.9minmini响应快于gpt-4o+并行2.2min3.5min队列等待消除读法:P50的改善是"典型任务变快",P90的改善是"最坏情况变可控",分别对应体验和承诺。基线的P90是P50的3倍,尾部完全失控;四轮之后比值收敛到1.6,系统从"平均还行、偶尔吓人"变成"可预期"。顺手做了max_steps的敏感性扫描,验证12不是拍的:max_steps成功率成本/任务判定882%$0.055掐太早,确有票需要多轮1287%$0.061甜点1687%$0.072成功率纹丝不动,白烧4步8→12成功率+5pt,说明确有票需要多轮;12→16成功率不动、成本+18%,说明12步修不好的票16步也修不好——重试上限的甜点就是"边际收益归零点"。三条决策规则就此定稿,成了我的调优SOP:一看分位数比值——P90/P50超过2就优先查尾部(通常是doom loop或hang);二看错误分布——同一类错误连续两轮占失败的一半以上,修它优先于任何性能优化;三看边际成本——任何配置改动先跑10票小集,成本涨幅超20%直接否决,不进30票全量。这套规则换任何项目都能直接搬。七、踩坑实录坑1:压测集污染。第2轮跑的时候我忘了回退仓库,5个"已修复"的Issue在干净分支上跑,工人发现bug不存在直接把票标done。当轮成功率虚高到92%,差点让我得出"预算控制大幅提升成功率"的错误结论。修复:bench_reset把"git reset到bug commit+清空票据库+清worktree"做成原子步骤,压测脚本只调它。压测环境的污染是最隐蔽的坑,因为它让数字变好看。坑2:把P50当真相。早期我报平均时长,被一张卡了28分钟的doom loop票拉到均值6分钟,完全掩盖了中位数2.4分钟的事实。之后所有时长指标我都同时报P50/P90:P50反映典型体验,P90反映尾延迟,doom loop这种尾巴问题只在P90里现形。均值会撒谎,中位数和分位数不会。坑3:在高峰期压测。有轮压测排在下午3点,API限流让3张票超时失败,成功率莫名掉了7个点,我以为是配置回退闹鬼,白查了两小时。之后压测固定在凌晨,且每轮之间加5分钟冷却。性能测试要控制变量,网络拥堵是最大的隐藏变量。小结先固定尺子:30个分层Issue的冻结数据集+三个明确定义的指标+每轮只改一个变量,否则一切对比都是自欺。失败归因决定调优顺序:基线50%的失败出在缺验证门禁,所以验证器第一、并行度最后——数据排序,不是感觉排序。四轮调优:验证器→预算→分层→并行,成功率67%→87%,成本$0.15→$0.06,时长4.5min→2.5min。帕累托最优:87%/$0.06/2.5min之后,多花钱买不回等比收益;剩下的13%交给escalated人工,混合模式总成本约为全人工的1/7。下节预告配置定了、数字好看了,最后一公里是让它稳定地在别人也能运维的环境里跑起来。第13章第6节《部署:容器化与CI/CD集成》——Dockerfile与compose、密钥注入、定时任务三选一、版本升级与状态迁移、上线前检查清单。如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连!本系列持续更新中,关注不迷路~文章编号:第13章第5节 | 总进度:95/120 | 预计阅读时间:9分钟