分层用模型老是翻车:粗筛、执行、终审各该用什么档位
数据截至 2026-07,各产品的额度与报错口径以官方最新说明为准。
多数人把「分层用模型翻车」归到小模型不行,我的判断是八成不是档位问题,而是任务边界没切干净:你把一个需要做判断、且没人复核的活儿,塞给了本来只该干搬运的那一档。 换成更强的模型后现象暂时消失,过两周任务一复杂又回来了,因为根因没动过。这篇讲的就是怎么按顺序把这件事查清楚。
先划清范围。站内已有两篇相邻的文章:按请求特征挑模型的规则怎么定,看 模型路由策略;某一档挂了怎么自动切到备用,看 多模型 fallback 设计。本篇不重复它们,只解决一件事——同一条链路内部按环节分档时,出了问题按什么顺序排查、什么时候该换档。路由是横向选,fallback 是失败后补救,分层是纵向切,三者会同时存在但故障模式不一样。
一、先弄清三档各自在干什么活
分档的分界线不是任务难度,是错误由谁来兜。这一条想明白,后面大半的争论就没了。
粗筛档干的是缩小范围。从几十个文件里挑出可能相关的五个,从一大段日志里圈出异常区间,从一堆候选方案里列出三个待选。它的特征是:输出可枚举、下游一定会复核、单次错误的代价接近于零(漏了就多读一个文件)。这一档不该出结论,只该出清单。
执行档干的是产生副作用的活。写代码、改配置、连续调工具、落盘。它的输出直接进工作区,错了要人回滚,代价是真实的时间。这一档最吃的不是「聪明」,是指令跟随的稳定性和工具调用格式的合法率——一次结构错乱就能让整条链路空转半小时。
终审档干的是放不放行。它看执行档产出的 diff、测试结果、影响到的模块范围,给出通过或打回加理由。它自己不落盘,但它决定别人落的盘算不算数。这一档最怕的是「看起来没问题」式的空转认可。
三档跑在同一条链路上时,还有一个常被忽略的角色:谁负责把上一环节的产物压缩成下一环节能吃的输入。这一步做砸了,后面换什么档位都救不回来。
二、按现象定位:一张判别表
出问题时别急着改模型配置,先对着下面这张表把现象归因。左边是你能直接观察到的,右边是先做什么。
| 现象 | 大概率成因 | 怎么验证 | 处置动作 |
|---|---|---|---|
| 粗筛给的候选里,正确文件根本没出现过 | 检索面没覆盖,或输入在进模型前就被截断 | 把粗筛的实际输入原样打印一份,肉眼确认目标路径在不在里面 | 修检索和切分,别升档;升档也变不出没给它的东西 |
| 候选里有正确文件但排得很靠后,下游没读到 | 粗筛判断力不够,或下游只取了前几条 | 让粗筛输出不排序的全量清单,执行档全部读一遍看结果是否好转 | 扩大候选条数并放弃排序;仍不行再考虑粗筛升一档 |
| 执行档写出来的代码风格不对,反复违反同一条项目约定 | 项目规则没进上下文 | 在同一会话里让它复述项目约定,看它答不答得出 | 补规则文件与引用方式,不是换更大的模型 |
| 多轮工具调用中越跑越偏,最后原地打转 | 上下文被中间产物稀释,目标被冲掉 | 看最近三轮动作是不是在重复同类操作 | 中断重开,把目标压成一句话加上已确认结论列表 |
| 输出结构经常不合法:JSON 截断、代码块没闭合 | 单次输出被长度上限截断,或结构约束太松 | 看返回的结束原因是不是长度截断 | 拆小任务、要求分块输出并逐块校验;仍不稳再升档 |
| 终审从来没打回过任何一次 | 终审拿不到 diff,或判据写得太笼统 | 故意塞一个已知缺陷进去,看它抓不抓得住 | 把 diff 和测试结果喂给它,并写死硬性打回条件 |
| 终审打回很频繁,但理由多是风格偏好 | 终审职责越界 | 统计最近打回理由的分类占比 | 收窄职责,只审正确性、边界和影响范围 |
| 同样的配置昨天好用今天明显变差 | 服务端默认指向变了,或别名指向了新版本 | 固定可追溯的模型标识,用同一批输入重跑对比 | 见 模型退化排查 |
| 高峰期整条链路间歇性失败,返回 429 | 触发了服务端的速率或并发限制 | 看失败是否集中在同一时间窗,并且原样重发能过 | 降并发、加退避重试;这是调度问题不是档位问题 |
| 报 401/403 或 ETIMEDOUT、ECONNRESET | 凭据、区域可达性或网络链路问题 | 用最小请求单独验证该档的连通性 | 先修可达性,把连不上的档从分层表里摘掉 |
表里最容易被误判的是第一行和第三行:它们的表象都像「模型不够聪明」,实际一个是输入没给全,一个是约定没给它。这两类占了我见过的分层故障的大头。
第三行的验证有两个分支,别只走一半。让它复述项目约定,答不出来,说明规则压根没进上下文,那就去补规则文件和引用方式;答得出来却照样违反,说明规则在场但约束力不够——这时候升档也未必稳,正确动作是把「靠它记住」换成「机器来卡」:能写成 lint 规则、格式化配置、测试用例的约定,就别留在自然语言里。自然语言约定的执行率永远是概率,检查脚本的执行率是 100%。
还要区分两种「重试」,它们方向相反、别混着用。一种是传输层重试:请求根本没被处理(429、超时、连接被重置),原样重发就可能过,退避重试是对的。另一种是任务层重试:模型已经把活干完了,只是干得不对,这时候原样再来一次只会得到同类结果,必须先改输入——补信息、缩范围、换切法,改完再发。后面「连续三轮修不动就止损」说的是任务层,不是让你把网络抖动也只试三次。判断方法很直接:看服务端有没有真的返回过一份完整输出,有就是任务层,没有就是传输层。
三、换档的判据:先证伪,再升档
换档不该凭手感。定四个指标,跑固定的一批回归任务,拿数字说话。
- 一次通过率:不需要任何人工干预就完成的比例。
- 返工轮次:从开始到通过平均要来回几次。
- 结构合法率:工具调用参数、JSON、代码块能被程序直接解析的比例。
- 人力介入时长:每个任务人真正花在盯它、改它上的分钟数。
前三个能自动统计,第四个得手工记,但它才是最诚实的那个。统计脚本不复杂,把每次调用记一行 JSONL 就够:
import json
ok = total = 0
with open("run_log.jsonl", encoding="utf-8") as f:
for line in f:
r = json.loads(line)
if r["stage"] != "execute":
continue
total += 1
if r["retries"] == 0 and r["human_edits"] == 0:
ok += 1
print(ok, total, round(ok / total, 3) if total else 0)
升档的判据:必须先把三件事证伪,才允许升档。一是输入完整(目标文件、报错原文、相关约定都在上下文里);二是规则在场(项目约定被显式引用,不是指望它猜);三是任务已经拆到单一目标(一次只改一处,不是「顺手把测试也补了」)。这三条都做到之后,一次通过率仍然过不去线,才是真的档位不够。反过来,只要还有一条没做到,升档只是用更贵的方式掩盖问题。
降档的判据:连续几批任务一次通过率稳定在高位、返工基本为零,就该试着往下降一档。降档别直接切,用影子对比:同一份输入两档并行跑,线上只采纳高档的输出,同时记录低档的产物与高档是否等价。攒够样本再切,成本和风险都可控。评测集怎么搭、样本量取多少才不算自欺,可以对着 Agent 评测方法 走一遍。
还有一条经验判据:如果某个环节的输出会被下一环节全量复核,它就该往下降;如果某个环节的输出直接落盘且没人看,它就该往上升。 这条比任何指标都好用,因为它对应的是错误代价,而不是模型宣传。
四、动作清单:改切分优先于改档位
按下面的顺序动手,前面的动作成本低、收益高,做完再考虑后面的。
第一步,把档位变成配置。 别把模型标识散在代码各处,集中成一处配置或环境变量,按阶段命名:
export AGENT_TIER_SCAN="<粗筛档模型标识>"
export AGENT_TIER_EXEC="<执行档模型标识>"
export AGENT_TIER_REVIEW="<终审档模型标识>"
写具体的、可追溯的标识,不要写会随时间漂移的别名。别名今天指向哪个版本、明天指向哪个版本不由你决定,一旦漂了,你连「昨天好今天坏」都复现不了。
第二步,给每次调用打阶段标签并落日志。 至少记:阶段、模型标识、输入的 token 规模、结束原因、是否重试、是否有人工改动。没有这份日志,后面所有的判断都是回忆。用量口径怎么统一,参考 token 统计怎么做。
第三步,改切分。 粗筛的输出格式定死为清单,禁止出现结论性句子;执行档一次只接一个可验证的目标;两环节之间加一道压缩,把中间产物收成结构化的几行,而不是把原始输出整段塞给下一档。
第四步,给终审写硬条件。 终审不能只问「有没有问题」,要给它可判定的打回项:改动是否超出声明范围、是否动了未在任务内的文件、测试是否真的跑过、是否引入了新的外部依赖。审之前先自己看一眼改动面:
git diff --stat
git status --porcelain
终审要开新会话,不要复用执行档那条上下文。同一条会话里,它会顺着自己前面的结论往下走,等于自己给自己盖章。
第五步,才是调档位。 到这一步再动模型,效果才可归因。
五、什么情况下别再折腾了
分层调度最贵的不是算力,是你反复微调它的那几个小时。给自己设死几条线。
止损点一:同一个任务连续三轮修不动。 三轮是我自己定的线,你可以按自己的记录调,但一定要有这条线。原因是三轮之后上下文已经被失败尝试塞满了,模型看到的一大半是它自己写错的版本,它会倾向于在错误方案上做小修补,而不是推翻重来。停掉,重开,或者手写。判断标准不是「有没有进展」,是「这一轮改的是不是和上一轮同一个地方」——同一处反复改说明它已经在原地打转。
止损点二:升到你手上最高的档还是过不去。 这时候基本可以断定不是档位问题,是任务定义有歧义、缺少必要信息,或者需求本身在仓库外面。继续换模型是在错误的维度上找答案。
止损点三:折腾时间超过你自己写的时间。 拿真实分钟数比,不要拿印象比。这条线一旦跨过去,当次就该收手;但把这个案例记下来,它是你下次调整分档边界的证据。
回滚点:每个环节动手之前,工作区必须是干净的可回退状态。养成习惯:开工前建分支,或者至少把当前进度存下来。
# 二选一,别连着跑:先建分支会把后续改动落在新分支上,
# 紧接着 stash 又会把它们全收走,等于白建。
git switch -c agent/task-0729 # 方式一:开工前单独建一条分支
git stash push -u -m "before agent run" # 方式二:手头有未提交改动,先整体存起来
出了乱子,回到最近一个干净提交比让模型自己收拾便宜得多。已经落了一堆无法归因的改动时,git diff 看不懂就直接丢弃重来,别在废墟上继续叠。
该换条路的信号:任务需要仓库之外的事实(真实业务规则、只有人知道的历史决策)、需要一个当前跑不起来的环境、或者需要一个产品层面的取舍。这三类不是分层能解决的,交给人。另外,如果你的分层方案里排了海外工具或模型的档位,先确认可达性再往表里写:部分海外服务对中国大陆存在区域限制或不提供直连,具体以其官方服务条款和可用区域说明为准;这类限制我不提供绕过办法,也不推荐任何第三方中转渠道。把一个连不通的档写进分层表,换来的只会是间歇性的 401/403 和超时,而且这类故障最容易被误读成「模型变笨了」,排查成本极高。真要用,走该服务在中国大陆有正式运营主体的合规入口,或者干脆在这一档换成境内可直连的替代方案。
六、避坑清单
坑一:拿贵贱当唯一的分档依据。 会踩是因为这个维度最直观,一眼就能排序。但在错误代价高的环节省下的开销,会被返工的人力成倍吃回去。避法:先按「错了谁兜」定档位归属,同一档内部再比成本。整体开销怎么控,见 Agent 成本失控。
坑二:让粗筛档顺手做判断。 会踩是因为粗筛已经把材料看了一遍,让它「顺便下个结论」看起来很省事。问题是这个结论没有任何人复核就流进了执行档,等于把一个零复核环节的输出当成了事实。避法:把粗筛的输出格式约束成纯清单,出现结论性表述就当格式错误直接打回。
坑三:终审和执行共用一条会话。 会踩是因为省事,上下文都在。但它会为自己前面的选择辩护,打回率会被压到接近零。避法:终审独立会话,只喂 diff、测试输出和判据清单,不喂过程。
坑四:用一两个样例就决定换档。 会踩是因为单次表现的波动很大,运气好的一次很容易被当成能力证明。避法:固定一批回归任务,每次改动都跑全量,看分布不看单点。
坑五:降档只看平均值。 会踩是因为均值好看,容易过关。但分层链路的痛点在尾部——最差的那一成任务会把人力全吃掉。避法:盯最差 10% 的表现和失败样本的类型分布,均值达标而尾部恶化就是不能降。
坑六:把模型标识写成会漂移的别名。 会踩是因为别名短、好记、文档里到处都是。但它指向哪个版本不由你控制,服务端一调整,你的分层基线就没了参照。避法:配置里写可追溯的具体标识,并在日志里把实际生效的标识记下来。
坑七:分层做完就不管了。 会踩是因为它一开始确实有效,指标好看。但任务分布会漂移,仓库会变大,服务端也在变。避法:把那四个指标做成周度快照,趋势掉了就重新跑一遍本篇的排查顺序,而不是等到有人抱怨。
收束
分层调度的价值不在于省了多少调用开销,在于把「哪一步错了、谁来兜」这件事变得可定位。做对了,你排查故障时能一眼看出问题出在哪一档;做错了,整条链路就是个黑盒,只能靠不停换模型碰运气。
下次觉得分层不好使,先按这份清单过一遍:
- 粗筛的实际输入里,正确答案在不在?
- 项目约定有没有真的进到执行档的上下文里?
- 这次任务是不是被拆成了单一可验证目标?
- 终审拿到 diff 和测试结果了吗,有没有硬性打回条件?
- 终审是不是开的新会话?
- 有没有连续三轮修不动、该止损了?
- 工作区是不是随时可回滚?
七条全过之后仍然不行,再谈换档。