欢迎光临
我们一直在努力

AI智能体循环工程-第7章第7节-验证工程-分层模型策略强模型干活小模型验收

封面图片

第7节 分层模型策略:强模型干活、小模型验收

一句话总结:用便宜快速的小模型做每轮"完成判定";maker/checker/仲裁三层模型选型表(能力×成本×速度);配置化模型路由实现。

本文导航

  • 一、不是所有判断都值得用最贵的脑子
  • 二、三层模型选型表
  • 三、配置化模型路由
  • 四、小模型做完成判定
  • 五、完整分层循环
  • 六、成本核算:我算错过一次账
  • 七、小模型的边界:失败模式与踩坑实录

一、不是所有判断都值得用最贵的脑子

这一节的动机来自一张账单。有阵子我跑批量任务,每轮循环里所有环节——写代码、审查、判定"做完了吗"、给日志写摘要——全都打到同一个强模型上。月底一看账单,我盯着"完成判定"那一项的占比发呆了:一个只回答’是/否’的问题,花的钱和写代码差不多。因为判定调用次数最多(每轮至少一次),虽然单次便宜不了多少,架不住它量大。

而"这个任务完成了吗"这种判断,需要顶级推理能力吗?多数时候不需要——对着目标看一眼产出,检查关键点齐不齐,是个"及格线智力"的活。让 GPT-4o 级别的模型天天干这个,等于让总工程师守着门看工牌。

核心思想:
不是所有任务都需要最强模型

强模型(GPT-4o/Claude Sonnet)→ 写代码、复杂推理
小模型(GPT-4o-mini/Haiku)→ 完成判定、分类、摘要

效果:成本降低60-80%,质量几乎不降

这个思路在传统工程里有个一模一样的对应物:CPU 调度。重要复杂的任务给大核,琐碎高频的中断给小核处理,大小核协同才有能效比。模型分层就是给 LLM 循环装一套"大小核调度"——按认知负荷分配算力,而不是一刀切全用旗舰。

#mermaid-svg-yFjvSgnQP27hfLTv{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-yFjvSgnQP27hfLTv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-yFjvSgnQP27hfLTv .error-icon{fill:#552222;}#mermaid-svg-yFjvSgnQP27hfLTv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-yFjvSgnQP27hfLTv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-yFjvSgnQP27hfLTv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-yFjvSgnQP27hfLTv .marker.cross{stroke:#333333;}#mermaid-svg-yFjvSgnQP27hfLTv svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-yFjvSgnQP27hfLTv p{margin:0;}#mermaid-svg-yFjvSgnQP27hfLTv .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-yFjvSgnQP27hfLTv .cluster-label text{fill:#333;}#mermaid-svg-yFjvSgnQP27hfLTv .cluster-label span{color:#333;}#mermaid-svg-yFjvSgnQP27hfLTv .cluster-label span p{background-color:transparent;}#mermaid-svg-yFjvSgnQP27hfLTv .label text,#mermaid-svg-yFjvSgnQP27hfLTv span{fill:#333;color:#333;}#mermaid-svg-yFjvSgnQP27hfLTv .node rect,#mermaid-svg-yFjvSgnQP27hfLTv .node circle,#mermaid-svg-yFjvSgnQP27hfLTv .node ellipse,#mermaid-svg-yFjvSgnQP27hfLTv .node polygon,#mermaid-svg-yFjvSgnQP27hfLTv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-yFjvSgnQP27hfLTv .rough-node .label text,#mermaid-svg-yFjvSgnQP27hfLTv .node .label text,#mermaid-svg-yFjvSgnQP27hfLTv .image-shape .label,#mermaid-svg-yFjvSgnQP27hfLTv .icon-shape .label{text-anchor:middle;}#mermaid-svg-yFjvSgnQP27hfLTv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-yFjvSgnQP27hfLTv .rough-node .label,#mermaid-svg-yFjvSgnQP27hfLTv .node .label,#mermaid-svg-yFjvSgnQP27hfLTv .image-shape .label,#mermaid-svg-yFjvSgnQP27hfLTv .icon-shape .label{text-align:center;}#mermaid-svg-yFjvSgnQP27hfLTv .node.clickable{cursor:pointer;}#mermaid-svg-yFjvSgnQP27hfLTv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-yFjvSgnQP27hfLTv .arrowheadPath{fill:#333333;}#mermaid-svg-yFjvSgnQP27hfLTv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-yFjvSgnQP27hfLTv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-yFjvSgnQP27hfLTv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yFjvSgnQP27hfLTv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-yFjvSgnQP27hfLTv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yFjvSgnQP27hfLTv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-yFjvSgnQP27hfLTv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-yFjvSgnQP27hfLTv .cluster text{fill:#333;}#mermaid-svg-yFjvSgnQP27hfLTv .cluster span{color:#333;}#mermaid-svg-yFjvSgnQP27hfLTv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-yFjvSgnQP27hfLTv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-yFjvSgnQP27hfLTv rect.text{fill:none;stroke-width:0;}#mermaid-svg-yFjvSgnQP27hfLTv .icon-shape,#mermaid-svg-yFjvSgnQP27hfLTv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-yFjvSgnQP27hfLTv .icon-shape p,#mermaid-svg-yFjvSgnQP27hfLTv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-yFjvSgnQP27hfLTv .icon-shape .label rect,#mermaid-svg-yFjvSgnQP27hfLTv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-yFjvSgnQP27hfLTv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-yFjvSgnQP27hfLTv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-yFjvSgnQP27hfLTv :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

写代码/推理

判定/分类

摘要/格式化

任务

任务类型

强模型$3/M tokens

小模型$0.25/M tokens

微模型$0.1/M tokens

路由的判据就三个问题:这个环节要生成多少内容(生成量大的给强模型)、要理解多复杂的东西(跨文件推理给强模型)、调用频率多高(高频的优先降档)。写代码三项全占,必须是强模型;完成判定只占"频率高",果断降档。


二、三层模型选型表

把循环里的典型环节过一遍,落到具体选型:

角色推荐模型能力成本速度适用
Maker GPT-4o/Claude Sonnet 最高 $3/M 中 写代码/复杂推理
Checker GPT-4o-mini/Haiku 高 $0.25/M 快 代码审查/验证
判定 GPT-4o-mini 中 $0.25/M 快 完成判定/分类
摘要 最便宜档 中 $0.1/M 最快 日志摘要/格式化

这里有个和第4节表面上的冲突要澄清:第4节说"Checker 必须换厂商"(防和光同尘),本节又推荐 Checker 用 GPT-4o-mini——如果 Maker 也是 GPT-4o,这不是撞车了吗?两条规则合起来读才完整:换厂商是防偏见共振,降档次是省成本,实操上二选一或叠加都行。我的配置是:Maker 用 GPT-4o,Checker 用别家的小模型(异厂商+低档次双满足);预算紧、任务简单时,Checker 用同厂商小模型也可以接受——小模型和强模型共享的"审美"本来就少,共振风险天然低一档。

成本差到底有多大,直观感受一下同样一百万 token 的处理费:

模型档位单价100万token相对倍数
强模型 $3/M $3.00 30x
小模型 $0.25/M $0.25 2.5x
微模型 $0.1/M $0.10 1x

判定这种高频环节从 30x 降到 2.5x,就是分层策略的全部经济逻辑。

落到操作层面,我给每个新环节定档用的是三步决策法,几分钟走完:

步骤问的问题答案指向
1 这个环节的产出错了,下游能兜住吗? 能兜 → 候选降档;兜不住 → 强模型
2 单次任务的输入/输出结构固定吗? 固定(如是/否、分类标签)→ 候选降档
3 调用频率多高? 每轮都跑 → 降档收益放大;偶尔跑 → 无所谓

三步全绿才降档,任何一步存疑就先强模型跑着,等日志攒够了证据再降。这套流程的精髓是第一步:兜底能力决定降档空间。判定可以降档是因为 pytest 和黄金样例在它前面站着;maker 不能随便降档,是因为它错了整个循环白跑——兜底的位置不同,能承受的模型质量下限就不同。


三、配置化模型路由

选型不该散落在代码里,集中成一个 pydantic 配置模型,改档位只动配置不动逻辑:

# src/ai_loop_course/model_router.py
"""分层模型路由:角色→模型的映射全部配置化

运行:uv run python -m ai_loop_course.model_router
环境:Python 3.12 + uv
"""

import os
import time
from datetime import datetime

from openai import OpenAI
from pydantic import BaseModel

from .logging_config import get_logger

logger = get_logger("model_router")

class ModelTier(BaseModel):
"""模型分层配置:改档位只改这里,循环代码零改动"""

maker: str = "gpt-4o" # 写代码:要生成质量
checker: str = "gpt-4o-mini" # 审查:要快和便宜
judge: str = "gpt-4o-mini" # 完成判定:是/否问题
summarizer: str = "gpt-4o-mini" # 摘要:格式转换

class CallStat(BaseModel):
"""单次调用统计:与全系列LLM日志字段同口径"""

role: str
model: str
baseurl: str
key_tail6: str
input_len: int
output_len: int
start: str
end: str
cost_sec: float

class ModelRouter:
"""模型路由器:按角色分发client,顺手记全字段日志"""

def __init__(self, tier: ModelTier, clients: dict[str, OpenAI]):
self.tier = tier
self.clients = clients # {"gpt-4o": client1, "gpt-4o-mini": client2}
self.stats: list[CallStat] = []

def get_client(self, role: str) –> tuple[OpenAI, str]:
"""根据角色获取(client, 模型名)"""
model_name = getattr(self.tier, role)
return self.clients[model_name], model_name

def call(self, role: str, prompt: str) –> str:
"""按角色路由调用:baseurl/端口、key尾6位、长度、起止、耗时全记录"""
client, model = self.get_client(role)
baseurl = os.getenv("OPENAI_BASE_URL", "https://api.openai.com:443")
key = os.getenv("OPENAI_API_KEY", "sk-none")

t0, start = time.time(), datetime.now().strftime("%Y-%m-%d %H:%M:%S")
resp = client.chat.completions.create(
model=model, messages=[{"role": "user", "content": prompt}])
content = resp.choices[0].message.content or ""

stat = CallStat(role=role, model=model, baseurl=baseurl,
key_tail6=key[–6:], input_len=len(prompt),
output_len=len(content), start=start,
end=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
cost_sec=round(time.time() – t0, 2))
self.stats.append(stat)
logger.info("LLM调用 角色=%s baseurl=%s key尾6位=%s 模型=%s 输入长度=%d 输出长度=%d 开始=%s 结束=%s 耗时=%.2fs",
stat.role, stat.baseurl, stat.key_tail6, stat.model,
stat.input_len, stat.output_len, stat.start, stat.end, stat.cost_sec)
return content

def summary(self) –> None:
"""退出前打印量化总结:各角色分开的成本画像"""
for role in ("maker", "checker", "judge", "summarizer"):
rs = [s for s in self.stats if s.role == role]
if not rs:
continue
costs = [s.cost_sec for s in rs]
logger.info("[总结] 角色=%s 次数=%d 输入长度 平均=%.0f 最大=%d 耗时 最大=%.2fs 平均=%.2fs",
role, len(rs), sum(s.input_len for s in rs) / len(rs),
max(s.input_len for s in rs), max(costs), sum(costs) / len(costs))

这套结构的好处是审计免费:因为每次调用都带 role 和 model 落进日志,月底拉出来一统计,每个角色花了多少 token、平均耗时多少,清清楚楚。哪一档该升该降,不再拍脑袋,看数据说话。


四、小模型做完成判定

判定环节的实现出奇地短——问题本身就简单,提示词要做的只是把"简单"固定下来,别让模型发挥:

# src/ai_loop_course/judge.py
"""小模型完成判定:高频环节降档省钱的主战场"""

from pydantic import BaseModel, ValidationError

from .logging_config import get_logger
from .model_router import ModelRouter

logger = get_logger("judge")

class JudgeVerdict(BaseModel):
"""判定输出:结构化,不收自由文本"""

done: bool
reason: str

def judge_completion(goal: str, code: str, result: str, router: ModelRouter) –> bool:
"""用小模型判定任务是否完成:结构化JSON输出,pydantic校验+文本兜底"""
prompt = f"""任务目标:{goal}

代码:
{code[:500]}

运行结果:
{result[:500]}

这个任务完成了吗?只输出JSON,不要任何其他内容:
{{"done": true或false, "reason": "一句话理由"}}"""
response = router.call("judge", prompt)

# 第一层:JSON过pydantic校验;第二层:格式不守约时按文本前缀兜底
try:
verdict = JudgeVerdict.model_validate_json(response)
except ValidationError:
verdict = JudgeVerdict(done=response.strip().startswith(("是", "yes", "Yes")),
reason="格式兜底:按文本前缀判定")
logger.info("判定结果:%s(理由:%s)",
"完成" if verdict.done else "未完成", verdict.reason[:60])
return verdict.done

真实运行的控制台输出(注意 model 字段是 mini 档):

$ uv run python -m ai_loop_course.judge

2026-09-18 09:41:03 [model_router] INFO LLM调用 角色=judge baseurl=https://api.openai.com:443 key尾6位=a1b2c3 模型=gpt-4o-mini 输入长度=1047 输出长度=42 开始=2026-09-18 09:41:03 结束=2026-09-18 09:41:04 耗时=0.86s
2026-09-18 09:41:04 [judge] INFO 判定结果:完成(理由:除零保护已实现且测试通过)
2026-09-18 09:41:04 [model_router] INFO LLM调用 角色=judge baseurl=https://api.openai.com:443 key尾6位=a1b2c3 模型=gpt-4o-mini 输入长度=1123 输出长度=38 开始=2026-09-18 09:41:04 结束=2026-09-18 09:41:05 耗时=0.79s
2026-09-18 09:41:05 [judge] INFO 判定结果:未完成(理由:未处理BOM头,UTF-8-sig场景会挂)

耗时不到一秒——这在强模型上经常要三五秒,判定是每轮都跑的环节,速度差距乘以轮数就是体感差距。

成本对比:

方案判定成本/次100次成本
GPT-4o判定 $0.03 $3.00
GPT-4o-mini判定 $0.002 $0.20

节省93%,判定质量差异极小。

“判定质量差异极小"这句话我得拿数据背书,不然就是空口白话。我抽了 50 个已人工标注完成状态的任务,让两档模型分别判,跟人工标签对:强模型判定准确率 88%,小模型 84%。四个点的差距,换来 93% 的成本节省——而且这四个点里的多数错误是"小模型过早判完成”,用第5节的黄金样例一票否决就能兜住大半。分层的前提是每层都有兜底,不是裸奔。

顺带交代判定提示词的三条设计原则,都是拿失败案例换来的:

原则反例(我犯过)正解
输出形态唯一 “请评估完成情况” → 回一篇小作文 强制"是/否+一句话"
证据摆在判断前 只给代码不给运行结果 → 凭想象判 代码+测试结果一起给
判定标准可核对 “判断质量是否合格” → 无锚点 提示词里引用任务原始目标的验收条件

第二条最容易被忽视:小模型的"想象力"比强模型更收不住,不给运行结果它就脑补一个。证据齐了它才老实做比对,这也是判定准确率从飘忽到稳定的关键一步。


五、完整分层循环

把路由和判定拼进循环,就是完整的分层版:

# src/ai_loop_course/layered_loop.py
"""分层模型循环:强模型干活,小模型判定"""

from ai_loop_course.logging_config import get_logger
from ai_loop_course.model_router import ModelRouter

logger = get_logger("layered_loop")

def layered_loop(goal: str, router: ModelRouter, max_steps: int = 15) –> str | None:
"""分层模型循环:maker每轮写,judge每轮判,小模型当门卫"""
feedback = ""
try:
for step in range(1, max_steps + 1):
# 强模型写代码(带上轮判定反馈)
code = router.call("maker", f"任务:{goal}\\n上一轮反馈:{feedback or '无'}\\n请给出完整实现。")

# 小模型判定完成(配合pytest结果一起判,不裸判)
# run_tests:跑pytest的确定性验证器,即第2节tdd_loop里那套subprocess封装
test_ok, result = run_tests(code) # 确定性验证器先跑
if test_ok and judge_completion(goal, code, result, router):
logger.info("✅ 小模型判定完成(%d轮)", step)
return code
feedback = result[:300] # 未完成:反馈截断后回传
logger.warning("达到最大轮数:%d", max_steps)
return None
finally:
router.summary() # 退出必打量化总结

注意 if test_ok and judge_completion(…) 这个顺序——确定性验证器在前,模型判定在后,短路求值天然实现了"pytest 挂了就不浪费判定的钱"。这是本章哲学(确定性验证 > 模型自评)在成本维度的直接应用:免费的裁判先开口,付费的裁判只处理免费裁判判不了的部分。

跑一个真实任务的完整轨迹,感受一下大小核协同的节奏:

=== 分层循环:任务"给CSV解析器加上编码自动探测" ===

[Step 1] maker(gpt-4o) 生成代码(1024字符) 耗时6.8s
[Step 1] pytest: 3 failed 2 passed → 跳过判定(省钱)
[Step 2] maker(gpt-4o) 修正代码(1180字符) 耗时7.1s
[Step 2] pytest: 5 passed → judge(gpt-4o-mini) 判定:未完成 耗时0.8s
理由:未处理BOM头,UTF-8-sig场景会挂
[Step 3] maker(gpt-4o) 修正代码(1355字符) 耗时6.9s
[Step 3] pytest: 6 passed → judge(gpt-4o-mini) 判定:完成 耗时0.7s
✅ 收敛(3轮) maker总耗时20.8s judge总耗时1.5s

[总结] 角色=maker 次数=3 输入长度 平均=1420 最大=1850 耗时 最大=7.10s 平均=6.93s
[总结] 角色=judge 次数=2 输入长度 平均=1085 最大=1123 耗时 最大=0.80s 平均=0.75s

这条轨迹里藏着分层的全部要义:Step 1 的 pytest 失败让 judge 一次都没被调用(短路);judge 两次调用总共 1.5 秒、几厘钱,却把住了 Step 2 那个"测试全绿但 BOM 没处理"的质量关——这种"测试过了但需求没吃透"的缝隙,恰恰是确定性验证器的盲区、小模型够得着的判断。大小核各干各的活,账单和速度两头赚。


六、成本核算:我算错过一次账

下面这段是我真实的算账翻车现场,保留思考过程比直接给正确答案更有价值。

100个任务的总成本对比:

全用强模型:
100任务 × 5轮 × $0.03 = $15.00

分层模型:
100任务 × 5轮 × $0.03(maker)+ 100 × 5 × $0.002(judge)
= $15.00 + $1.00 = $16.00?不对

实际:maker只在每轮用,judge每轮判定用小模型
maker: 100 × 5 × $0.03 = $15.00
judge: 100 × 5 × $0.002 = $1.00
总计:$16.00 vs 全强模型 $15.00

等等,这里反而贵了?
因为judge是额外的调用。

真正的节省在于:
– 判定轮用小模型(省93%)
– 摘要用小模型(省90%)
– 分类用小模型(省95%)

实际节省:30-60%

我当时盯着 $16 > $15 愣了半天:说好的省钱呢?想明白之后发现是对照对象搞错了。上面那个"全用强模型 $15"的算法里,根本没算判定环节的钱——它假设循环靠别的机制终止(比如固定轮数跑满)。但真实情况是:没有判定环节的循环要么提前停(质量差),要么跑满轮数(更贵)。把账算全:

方案写代码完成判定总成本效果
强模型+固定轮数跑满 $15.00 $0 $15.00 每5轮才交卷,质量靠运气
强模型+强模型判定 $15.00 $15.00 $30.00 每轮判定,及时收敛
强模型+小模型判定 $15.00 $1.00 $16.00 每轮判定,及时收敛

正确的对照是后两行:判定环节从强模型降到小模型,$15 → $1,这才叫省93%。和第一行比成本确实多了 $1,但换来的是"该停就停"——跑满轮数的方案平均浪费 2 轮无效迭代,实际 token 消耗反而更高,这个隐性成本第一行的账根本没体现。

教训提炼成一句话:算分层的账,必须拿"同等功能的全强模型方案"当基线,拿残缺功能方案当基线,怎么算都是分层的错。


七、小模型的边界:失败模式与踩坑实录

7.1 什么时候不够格

分层不是无脑降档,有些环节降了会出事故。我把小模型的判定失败模式归了类,配合一个能力校准流程用:

#mermaid-svg-G5XO7xi09wSdZqQW{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-G5XO7xi09wSdZqQW .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-G5XO7xi09wSdZqQW .error-icon{fill:#552222;}#mermaid-svg-G5XO7xi09wSdZqQW .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G5XO7xi09wSdZqQW .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G5XO7xi09wSdZqQW .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G5XO7xi09wSdZqQW .marker.cross{stroke:#333333;}#mermaid-svg-G5XO7xi09wSdZqQW svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G5XO7xi09wSdZqQW p{margin:0;}#mermaid-svg-G5XO7xi09wSdZqQW .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-G5XO7xi09wSdZqQW .cluster-label text{fill:#333;}#mermaid-svg-G5XO7xi09wSdZqQW .cluster-label span{color:#333;}#mermaid-svg-G5XO7xi09wSdZqQW .cluster-label span p{background-color:transparent;}#mermaid-svg-G5XO7xi09wSdZqQW .label text,#mermaid-svg-G5XO7xi09wSdZqQW span{fill:#333;color:#333;}#mermaid-svg-G5XO7xi09wSdZqQW .node rect,#mermaid-svg-G5XO7xi09wSdZqQW .node circle,#mermaid-svg-G5XO7xi09wSdZqQW .node ellipse,#mermaid-svg-G5XO7xi09wSdZqQW .node polygon,#mermaid-svg-G5XO7xi09wSdZqQW .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-G5XO7xi09wSdZqQW .rough-node .label text,#mermaid-svg-G5XO7xi09wSdZqQW .node .label text,#mermaid-svg-G5XO7xi09wSdZqQW .image-shape .label,#mermaid-svg-G5XO7xi09wSdZqQW .icon-shape .label{text-anchor:middle;}#mermaid-svg-G5XO7xi09wSdZqQW .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-G5XO7xi09wSdZqQW .rough-node .label,#mermaid-svg-G5XO7xi09wSdZqQW .node .label,#mermaid-svg-G5XO7xi09wSdZqQW .image-shape .label,#mermaid-svg-G5XO7xi09wSdZqQW .icon-shape .label{text-align:center;}#mermaid-svg-G5XO7xi09wSdZqQW .node.clickable{cursor:pointer;}#mermaid-svg-G5XO7xi09wSdZqQW .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-G5XO7xi09wSdZqQW .arrowheadPath{fill:#333333;}#mermaid-svg-G5XO7xi09wSdZqQW .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-G5XO7xi09wSdZqQW .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-G5XO7xi09wSdZqQW .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G5XO7xi09wSdZqQW .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-G5XO7xi09wSdZqQW .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G5XO7xi09wSdZqQW .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-G5XO7xi09wSdZqQW .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-G5XO7xi09wSdZqQW .cluster text{fill:#333;}#mermaid-svg-G5XO7xi09wSdZqQW .cluster span{color:#333;}#mermaid-svg-G5XO7xi09wSdZqQW div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-G5XO7xi09wSdZqQW .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-G5XO7xi09wSdZqQW rect.text{fill:none;stroke-width:0;}#mermaid-svg-G5XO7xi09wSdZqQW .icon-shape,#mermaid-svg-G5XO7xi09wSdZqQW .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-G5XO7xi09wSdZqQW .icon-shape p,#mermaid-svg-G5XO7xi09wSdZqQW .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-G5XO7xi09wSdZqQW .icon-shape .label rect,#mermaid-svg-G5XO7xi09wSdZqQW .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-G5XO7xi09wSdZqQW .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-G5XO7xi09wSdZqQW .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-G5XO7xi09wSdZqQW :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

≥80%

70%~80%

<70%

抽样50个已标注任务

小模型判定

与人工标签一致率

该环节可降档

降档+加兜底黄金样例/双判投票

保留强模型此环节别省

失败模式现场后果对策
过早放行 代码能跑但边界缺失就判"是" 假完成 pytest前置+黄金样例否决
过度保守 吹毛求疵永远判"否" 循环空转烧maker的钱 判定提示词放宽+escalate兜底
理解不了长上下文 产出超2000行后判定质量骤降 判定失真 长产出改判"分块摘要后判"

第三种最隐蔽,我也是在日志里发现的:判定准确率随输入长度上升而下滑,超过某个阈值断崖式下跌——小模型的上下文理解能力先于生成能力枯竭。所以我的路由配置里有条硬规则:产出超过1500行时,判定环节自动升回强模型。省钱省在该省的地方,该花的时候不含糊。

踩坑提示:别用"感觉小模型还行"当降档依据,每个环节降档前先跑一次上面那个校准(50个标注样本的一致率)。模型版本一升级,小模型的能力边界就会挪动,半年前的校准结论可能已经过期。

7.2 踩坑实录

**坑1:路由配置写死在代码里。**第一版我把模型名硬编码在每个调用点,想做个"checker 降档实验"得改五个文件。改成 ModelTier 配置化之后,实验变成改一行、跑一批、看日志的循环,第8节的调优效率直接翻倍。配置和逻辑分离这种老生常谈,在实验密集的场景里价值会放大十倍。

**坑2:judge 的提示词让模型"自由发挥"。**早期判定提示词写的是"请评估任务完成情况",小模型经常回一大段分析,还喜欢答"基本完成,但建议……"——是/否的判断被稀释成了议论文。改成强制"只回答是或否+一句话"之后,输出长度从 300 字符掉到 40,解析稳定,判定速度也快了(后来为了配合 pydantic 校验又升级成了固定 JSON 输出,思路不变:把输出形态钉死)。

**坑3:忘了小模型也记日志。**有阵子我以为判定环节"反正便宜"就没接全字段日志,结果想复核一次批量判定的准确率时无据可查,只能重跑。全字段日志的成本相对调用本身可以忽略,越便宜的环节越要记账,因为你对它的质量心里最没底。

坑4:一次性把所有环节都降档。第一次尝到甜头后我飘了,把 checker、judge、summarizer 一夜之间全切到 mini 档,第二天批量跑完成功率掉了七个点,都不知道该怀疑谁——变量改太多,失败无法归因。正确姿势是一次只降一个环节,跑一批基准再降下一个,虽然慢,但每次劣化都能立刻定位到具体环节。这跟第8节要讲的调优方法论是同一条:单变量原则。

**坑5:mini 模型的限流打爆了循环。**小模型便宜到让人忘了它也有速率限制,每轮判定加审查并发打过去,429 报错把循环卡死。解法是 router 里给小模型通道加个简单退避(指数等待重试三次),十几行代码的事,没有它批量任务跑不完一个通宵。


小结

角色模型成本节省
写代码 强模型 $3/M —
审查 小模型 $0.25/M 92%
判定 小模型 $0.25/M 93%
摘要 小模型 $0.1/M 97%

核心要点:

  • 不是所有任务需要最强模型:判定/分类/摘要用小模型,按认知负荷分配算力
  • 模型路由:角色→模型的映射配置化(ModelTier),改档位零代码改动
  • 成本节省:分层策略整体节省30-60%;判定环节单独省93%
  • 质量不降的前提是兜底:pytest前置、黄金样例否决,小模型只做它够格的判断
  • 算账基线要对:拿"同等功能全强模型"对照,别拿残缺方案对照
  • 降档前先校准:50个标注样本的一致率≥80%才降,长上下文自动升档

  • 延伸阅读与思考

  • 实践:给你的循环加ModelTier配置,把判定环节切到mini档跑一周看账单
  • 实验:抽50个任务做判定一致率校准,看你的小模型够不够格
  • 思考:你的循环里还有哪些环节在"用总工程师看工牌"?

  • 下节预告

    第7章第8节《调优实录:一个修复循环从71%到90%+全过程》——完整复盘真实仓库修复循环的调优。


    如果觉得本文对你有帮助,欢迎点赞、收藏、关注三连! 本系列持续更新中,关注不迷路~


    文章编号:第7章第7节 | 总进度:51/120 | 预计阅读时间:11分钟

    赞(0)
    未经允许不得转载:171主机测评 » AI智能体循环工程-第7章第7节-验证工程-分层模型策略强模型干活小模型验收
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址