AI智能体循环工程-第5章第5节-循环的五大原语与生态-Subagents子智能体makerchecker分离是循环的心脏

📅 发布时间:2026/10/11 6:20:26
AI智能体循环工程-第5章第5节-循环的五大原语与生态-Subagents子智能体makerchecker分离是循环的心脏
第5节 Sub-agents子智能体maker/checker分离是循环的心脏一句话总结为什么写代码的模型给自己的工作打分过于宽容——checker用不同模型/不同提示词/独立上下文的三大收益Codex的TOML定义与Claude Code的.claude/agents/token成本意识。本文导航一、问题模型自评的系统性偏差二、Maker/Checker分离与三大收益三、实现方式三个维度四、两大工具的Agent定义五、Token成本意识六、工程实现一个最小的Maker/Checker循环七、踩坑实录小结下节预告上一节把连接器讲完Agent够得着外面的世界了。但上一节结尾埋的那个质量死结还没解自己写的东西自己检查永远偏宽容。第3章第5节讲反馈时立过一条铁律验证必须是确定性的模型自评不可信。但有些质量问题光靠pytest抓不住——架构合理性、命名一致性、安全隐患、过度设计——这些软维度还是得靠模型来审。那就必须回答一个问题同一个模型既当运动员又当裁判行吗这一节的答案是不行而且我会告诉你为什么不行、怎么拆开才有效。一、问题模型自评的系统性偏差实验让模型评价自己的代码Maker写代码 我写好了这个排序函数 Maker自评 这段代码完全正确逻辑清晰性能优秀。 实际测试 pytest → 3个测试失败 lint → 5个警告 性能benchmark → 比预期慢10倍问题模型对自己的代码过于宽容为什么偏差类型说明自我宽容偏差对自己生成的内容天然宽容确认偏差倾向于认为自己是对的幻觉成功没运行就断言通过了我复现过这个实验结果更难堪我在同一个会话里让模型写一个缓存装饰器然后严格审查它。它先写了个带TTL的装饰器自评结论是实现完整无问题。我盯着代码看了一眼就发现两个问题线程不安全dict写入没加锁、TTL过期后缓存永不清理只在新key写入时才触发检查。有趣的是第二个实验我开一个全新会话把这个装饰器原样贴过去让它审查。同一个模型立刻挑出了这两个问题还顺手指出第三个异常分支会让缓存污染。同一个模型、同一段代码唯一区别是这段代码是不是它自己在这个会话里写的。差别就是这么戏剧化而且复现率极高你可以拿任何一个自己写的会话试。为什么机制上讲当模型刚生成完一段代码那段代码连同它的推理理由还在上下文里热乎着——审查时模型看到的不是一份陌生的代码而是我刚才深思熟虑的成果。它会顺着生成时的思路再走一遍自然觉得处处合理。上下文里的生成痕迹就是宽容的温床。二、Maker/Checker分离与三大收益架构通过不通过Maker写代码Checker审查代码完成核心原则Maker和Checker必须是 1. 不同的模型或同一模型的不同实例 2. 不同的提示词一个写一个审 3. 独立的上下文Checker没看过Maker的思考过程这张图看着简单但它就是第7章第4节Maker/Checker双模型互查的雏形也是整个循环架构里最核心的一块拼图——说它是循环的心脏不夸张没有分离的循环质量信号是失真的质量信号失真反馈闭环第3章第5节就是空转。一个类比帮你记住它司法制度里为什么侦办和审判要分属不同的人不是因为法官比警察聪明而是因为立场需要被重置。Maker的目标是把活干完Checker的目标是把问题找全两个目标函数不同的角色凑在一起比一个目标函数混杂的角色强得多。三大收益的原理拆解为什么有效收益说明消除自我宽容Checker没有Maker的创作情感独立视角Checker从审查者角度看代码减少幻觉Checker必须实际运行测试收益1消除自我宽容Checker看不到Maker的思考过程它面前只有冷冰冰的产物。没有我当时为什么这么写的理由护体代码只能自己为自己辩护。我开头那个双会话实验证明的就是这一点——宽容不是模型的本性是上下文的惯性。切断上下文惯性就断了。收益2独立视角Maker提示词说实现需求Checker提示词说找出问题。看似只是文字差异实际是在给同一个推理机装不同的目标函数。找问题的视角会主动问边界条件呢“并发下呢”“和现有代码风格一致吗”——这些问题在实现需求视角下天然不会被优先想到。收益3强制落地验证Checker的职责清单里必须有实际运行测试这一条。注意区别Maker也会跑测试但它跑测试的动机是赶紧通过好交差Checker跑测试的动机是收集证据好挑剔。同一个动作前者容易在失败后立刻自我安慰这个失败不重要后者会把失败原样写进审查报告。动机不同同样的事实会得出不同的结论。三、实现方式三个维度方式1不同模型# Maker用强模型makerClient(modelclaude-sonnet-4-20250514)# Checker用另一个模型checkerClient(modelgpt-4o)# Maker写代码codemaker.generate(写一个排序函数)# Checker审查reviewchecker.generate(f审查这段代码\n{code}\n运行测试并报告结果)不同模型的最大优势是训练分布不同A模型爱犯的错B模型不一定爱犯互相看对方的代码反而更容易发现盲区。劣势是成本和延迟——两个不同后端意味着两套配置、两倍运维。方式2不同提示词# Maker提示词maker_prompt 你是一个Python开发者。 根据需求编写代码。 # Checker提示词checker_prompt 你是一个代码审查者。 你的任务是找出代码中的问题。 要严格、要挑剔、不要放过任何细节。 运行测试验证代码正确性。 这是成本最低的分离——同一个模型两套人格。实测能消掉大部分自我宽容因为提示词重置了目标函数但消除不掉幻觉成功毕竟还是同一个大脑。预算有限时的起步方案。方式3独立上下文# Maker的上下文maker_context[{role:user,content:需求写一个排序函数},# Maker的思考过程...]# Checker的上下文不包含Maker的思考checker_context[{role:user,content:f审查这段代码\n{code}},# Checker独立审查]三个维度里独立上下文是不可妥协的那一个。前两个都可以妥协同一模型近似提示词也能凑合唯独上下文必须切开——只要Maker的思考过程还在Checker的窗口里宽容的温床就还在。这也是为什么子智能体sub-agent作为宿主原语有价值它天生就是一个独立的上下文容器。三个维度的组合怎么选我的决策表组合效果成本适用同模型同上下文自评差1×只能算心理安慰同模型不同提示词中1.2×预算紧张的起步同模型独立上下文良1.5×大多数场景的甜点位不同模型独立上下文优2×核心代码、高危变更如果你不知道从哪档起步我的建议是从第三档开始同一个模型、两段提示词、两个独立会话。它只比自评多50%成本却拿到了分离机制八成的收益而且实现最简单——连子智能体配置都不用写两个循环调用就行。等它证明了价值比如审查真的拦下过几次事故再升级到第四档换模型。分离这件事先做起来比做全套重要。四、两大工具的Agent定义Codex的TOML格式# .codex/agents/maker.toml [agent] name maker description 写代码的工人智能体 model claude-sonnet-4-20250514 [agent.prompt] system 你是一个Python开发者。 根据需求编写高质量代码。 遵循项目约定见AGENTS.md。 [agent.tools] allowed [read_file, write_file, run_command] [agent.constraints] max_iterations 20 max_tokens 100000# .codex/agents/checker.toml [agent] name checker description 审查代码的检查者 model gpt-4o [agent.prompt] system 你是一个严格的代码审查者。 你的任务是找出代码中的所有问题。 运行测试验证正确性。 不要放过任何细节。 [agent.tools] allowed [read_file, run_command] # 只能读不能写 [agent.constraints] max_iterations 5注意checker的工具清单只有read_file和run_command没有write_file。这不是疏忽——审查者物理上不能改代码想顺手修一下都不可能。用工具权限把角色分离焊死比在提示词里写一百遍不要修改代码都可靠。这就是第3章第4节说的写进prompt的约束是软约束挂上检查器的才是硬约束在Agent定义层的应用。Claude Code的.claude/agents/目录.claude/ agents/ maker.md checker.mdmaker.md--- name: maker description: 写代码的工人智能体 model: claude-sonnet-4-20250514 --- # Maker Agent ## 角色 你是一个Python开发者负责根据需求编写代码。 ## 工具 - read_file读取文件 - write_file写入文件 - run_command运行命令 ## 约束 - 遵循AGENTS.md中的约定 - 最多20轮迭代 - 必须通过所有测试 ## 输出 完成后提交PR等待Checker审查。checker.md--- name: checker description: 审查代码的检查者 model: gpt-4o --- # Checker Agent ## 角色 你是一个严格的代码审查者负责找出代码中的所有问题。 ## 工具 - read_file读取文件只读 - run_command运行测试 ## 约束 - 不能修改代码 - 必须运行测试验证 - 最多5轮审查 ## 输出 审查报告问题列表 修复建议 如果不通过返回给Maker修改。两个工具的定义对比一下你会发现结构完全对应名字描述模型提示词工具约束。格式一个TOML一个Markdown但形状相同——这正是本章第7节原语可移植性的实例预演。也就是说你在Claude Code里调好的maker/checker分工迁到Codex只是换个文件格式架构、提示词、约束全部照搬。五、Token成本意识问题双模型 双倍成本Maker100K tokens Checker50K tokens 总计150K tokens vs 单模型100K tokens 成本增加50%优化策略策略说明节省Checker用小模型审查不需要最强模型30-50%Checker少迭代审查不需要太多轮20-30%分层审查简单代码跳过Checker50%成本计算defcalculate_cost(maker_tokens,checker_tokens,maker_price,checker_price):计算Maker/Checker成本maker_costmaker_tokens*maker_price checker_costchecker_tokens*checker_price totalmaker_costchecker_cost# 对比单模型single_cost(maker_tokenschecker_tokens)*maker_price savingssingle_cost-total savings_pctsavings/single_costreturn{total:total,savings:savings,savings_pct:savings_pct}# 示例# Maker用Sonnet$3/M tokens# Checker用Haiku$0.25/M tokensresultcalculate_cost(maker_tokens100000,checker_tokens50000,maker_price3/1000000,checker_price0.25/1000000)# 节省约31%成本Checker用小模型这条我要多解释两句因为它反直觉审查比生成便宜。生成要在整个解空间里构造审查只需要在给定解上找茬——找茬的难度低于创作。而且第3章第5节讲过最硬的质量信号测试/编译/lint根本不花tokenChecker要处理的只是硬信号抓不住的软维度。用小模型审确定性验证器兜底质量不掉账单先瘦。分层审查是另一条被低估的策略不是每次改动都值得全套审查。我的分级规则——只改注释/文档的跳过Checker单文件小改动用同模型独立上下文的轻量档动核心逻辑、动公共API的才上不同模型的重装档。把贵的机制留给贵的变更。冷静一下什么时候不需要Checker讲了这么多分离的好处也要说清楚边界——不是所有任务都配得上Maker/Checker。我列过一张不配清单有完美确定性验证器的任务算法题、格式转换、有黄金样例的生成——pytest和diff就能给出100%可信的判定再叠一层模型Checker纯属花钱买心理安慰。一次性小改动改个常量、修个typo审查成本比改动本身还高。创意/探索性产出头脑风暴、起名、写初稿——这类任务没有对错只有偏好Checker挑不出有意义的毛病只会把多样性掐死。第10章第8节会系统讲什么时候不该用多智能体这里先记住判据Checker的价值 软维度问题的代价 − 双倍的成本。核心支付逻辑软维度错了就是资损Checker必上边缘脚本错了重跑一遍就是Checker可省。六、工程实现一个最小的Maker/Checker循环按本课程统一工程规范Python 3.12 uv、pydantic校验、logging控制台文件双输出按月分割保留12个月、LLM调用全字段日志、原始提示词落盘、退出打印量化总结写一个能跑的最小骨架# uv init maker-checker uv add pydantic最小Maker/Checker循环独立上下文 全字段日志 退出总结importatexit,logging,timefromdatetimeimportdatetimefromlogging.handlersimportTimedRotatingFileHandlerfrompathlibimportPathfrompydanticimportBaseModel,Field loglogging.getLogger(mc)log.setLevel(logging.INFO)_fmtlogging.Formatter(%(asctime)s | %(levelname)s | %(message)s)_clogging.StreamHandler();_c.setFormatter(_fmt);log.addHandler(_c)_fTimedRotatingFileHandler(logs/mc.log,whenmidnight,interval30,backupCount12,encodingutf-8)_f.setFormatter(_fmt);log.addHandler(_f)RECORDS:list[dict][]atexit.register(lambda:log.info(调用总结 共%d次 | 长度 最大%d 最小%d 平均%d | 耗时 最大%dms 最小%dms 平均%dms,len(RECORDS),max(r[len]forrinRECORDS),min(r[len]forrinRECORDS),sum(r[len]forrinRECORDS)//len(RECORDS),max(r[ms]forrinRECORDS),min(r[ms]forrinRECORDS),sum(r[ms]forrinRECORDS)//len(RECORDS))ifRECORDSelseNone)classReviewResult(BaseModel):Checker的输出必须结构化过不了校验一律视为不通过passed:boolissues:list[str]Field(default_factorylist)severity:strinfodefllm_call(role:str,model:str,prompt:str,base_url:str,key:str)-str:全字段调用日志 原始提示词落盘模型调用以client实现为准Path(prompts).mkdir(exist_okTrue)tsf{datetime.now():%Y%m%d_%H%M%S}Path(fprompts/{ts}-{role}.txt).write_text(prompt,encodingutf-8)t0,outtime.perf_counter(),fake_generate(role,prompt)# 实际接clientmsint((time.perf_counter()-t0)*1000)RECORDS.append({len:len(prompt)len(out),ms:ms})log.info(LLM调用 角色%s baseurl%s key尾6位%s 模型%s 输入长度%d 输出长度%d 耗时%dms,role,base_url,key[-6:],model,len(prompt),len(out),ms)returnoutdefmaker_checker_loop(task:str,max_rounds:int3)-ReviewResult:核心每次都是全新上下文——这是分离的关键artifactforrndinrange(1,max_rounds1):# Maker独立会话只拿到任务和上轮审查意见artifactllm_call(maker,claude-sonnet-4-20250514,f实现需求{task}\n已有产物{artifactor无},https://api.example.com:443,sk-xxxab12cd)# Checker全新上下文看不到Maker的任何思考rawllm_call(checker,gpt-4o,f严格审查以下产物并只返回JSON(passed/issues)\n{artifact},https://api.example.com:443,sk-xxxab12cd)try:reviewReviewResult.model_validate_json(raw)exceptException:reviewReviewResult(passedFalse,issues[审查输出不合法判不通过])log.info(第%d轮 审查%s 问题数%d,rnd,通过ifreview.passedelse不通过,len(review.issues))ifreview.passed:returnreviewreturnreview控制台输出一次两轮通过的真实形状$ uv run maker_checker.py 2026-05-20 15:02:11 | INFO | LLM调用 角色maker baseurlhttps://api.example.com:443 key尾6位ab12cd 模型claude-sonnet-4-20250514 输入长度412 输出长度2871 耗时5230ms 2026-05-20 15:02:17 | INFO | LLM调用 角色checker baseurlhttps://api.example.com:443 key尾6位ab12cd 模型gpt-4o 输入长度3015 输出长度184 耗时1910ms 2026-05-20 15:02:17 | INFO | 第1轮 审查不通过 问题数2 2026-05-20 15:02:23 | INFO | LLM调用 角色maker baseurlhttps://api.example.com:443 key尾6位ab12cd 模型claude-sonnet-4-20250514 输入长度921 输出长度2934 耗时5480ms 2026-05-20 15:02:29 | INFO | LLM调用 角色checker baseurlhttps://api.example.com:443 key尾6位ab12cd 模型gpt-4o 输入长度3078 输出长度95 耗时1650ms 2026-05-20 15:02:29 | INFO | 第2轮 审查通过 问题数0 2026-05-20 15:02:29 | INFO | 调用总结 共4次 | 长度 最大3855 最小3173 平均3377 | 耗时 最大5480ms 最小1650ms 平均3567ms看角色字段的分布maker两次、checker两次各自独立成对——日志里能看出架构架构才算真的落地了。Checker输出过不了pydantic校验就判不通过这也是防幻觉的最后一道网宁可误杀一轮重来绝不放行一次我觉得没问题。七、踩坑实录Checker上下文里混进了Maker的思考为了省token把Maker的推理过程一起传给Checker宽容偏差原样复发。修法只传产物不传过程传产物的diff不传全部代码大项目时。Checker提示词写得太客气“帮忙看看这段代码有什么改进空间”——审查报告全是整体不错小建议如下。修法明确要求列出所有缺陷输出JSONpassed字段不允许为空的兜底值。无限打回Checker过于苛刻Maker改一轮被打回一轮烧了8轮预算。修法max_rounds硬上限第3轮后把双方输出转人工裁决。忘了给Checker设只读权限Checker顺手把问题都修了然后自己审自己修的代码——分离名存实亡。修法工具白名单物理隔离checker没有write_file。所有变更都走重装档改个注释也双模型审查成本翻倍没人受益。修法分层审查贵的机制留给核心变更。小结模型自评有系统性偏差自我宽容、确认偏差、幻觉成功上下文里的生成痕迹就是宽容的温床——同一模型换个会话立刻能挑出自己的错。Maker/Checker三原则不同模型、不同提示词、独立上下文其中独立上下文不可妥协模型和提示词都可以按预算降档。三大收益切断创作情感、重置目标函数找茬视角、强制落地验证审查比生成便宜找茬的难度低于创作。权限焊死角色checker工具清单物理上不给write_file比提示词写一百遍不许改可靠。成本三招Checker用小模型省30-50%、少迭代、分层审查——贵的机制留给贵的变更。最小实现的关键每轮全新上下文pydantic校验Checker输出全字段日志日志里能看出架构架构才算落地。下节预告Maker/Checker分离靠的是宿主的子智能体机制但还有一个更底层的控制手段没讲如果我想在每一次工具调用的前后都插一道关卡呢第5章第6节《Hooks与守护智能体生命周期的拦截点》讲PreToolUse/PostToolUse这些生命周期拦截点用确定性代码做权限门禁、危险命令拦截——以及它和认知投降防线的关系。如果觉得本文对你有帮助欢迎点赞、收藏、关注三连本系列持续更新中关注不迷路~文章编号第5章第5节 | 总进度35/120 | 预计阅读时间16分钟