lab02 · 规划与分解(对应教程 Part 3)
墨叔 vs 老哥 对话式讲解。老哥是 5 年 Go 后端,想系统学 agent 开发。
0. 先跑起来
# 默认 mock 模式:零依赖、零 API key,克隆即跑
python main.py
# 想接真实模型(需要 OPENAI_API_KEY)
AGENT_MODE=real python main.py跑完你会看到三幕:规划器先画出步骤清单(可检查的中间产物)→ 执行器逐步调工具落地、中途被 Critic 抓出”漏了提醒”并修正 → 最后汇总。轨迹里清楚标了 think / act,你能看见 agent 的”心跳”。
3.1 规划为什么难:LLM 的”想一步走一步”缺陷
老哥:墨叔,我让 agent 干”整理待办并提醒我”还行,但一让它干”重构支付模块”,它就中途跑偏、前后命名对不上。咋回事?
墨叔:根子一句话——模型是个”局部续写器”。它每走一步,只看得见”脚下三米”(当前上下文窗口),预测”接下来最合理的一段文字”。它没有一张独立于上下文之外的”全局蓝图”。所以长任务里,原始目标被海量工具返回冲淡(目标稀释),前半段定的规范到后半段接不上(前后矛盾)——它自己还觉得”我这步走得挺合理”。
老哥:那我为啥一行一行写代码不会前后矛盾?
墨叔:因为你落键盘前,脑里/文档里已有函数签名、模块边界、命名规范——那是先存的全局设计在约束你。模型没有这个,它的”设计”是边写边在墙上现画的,画着画着就歪了。一句话:人把约束存在脑里(持久),模型把约束存在纸上(易逝)。所以解法不是等模型变聪明,而是我们外挂一张它每步都能对齐的地图。
3.2 Plan-and-Execute:先画地图再走路
老哥:地图咋外挂?
墨叔:Plan-and-Execute——把”规划”和”执行”拆成两个角色。规划器只”想全局、列清单”,这一轮不调任何工具(保住俯瞰视角,否则一读文件又掉回脚下三米)。执行器只盯着”当前这一步”去调工具落地。
看 main.py 的 planner_responder:它面对 user 消息只 text_resp(计划),从不 tool_resp。这份计划是个显式、可检查的中间产物——执行前你能打印出来审一遍(就像装修先出施工图、业主签字再施工)。
老哥:那计划列错了咋办?
墨叔:计划能重画。执行挂了,把”失败报告”喂回规划器,让它带着新信息重列。这就是 main.py 里”两个 run_loop”的用意:loop 1 出计划,loop 2 按计划在每步对齐。计划不是一次焊死,是能迭代的。
代价账:比纯 ReAct 多花一次(多次)模型调用,更稳但更慢更贵。本质就是你本行”编译型 vs 解释型”的取舍——任务越复杂越不能出错,越该付这笔”规划税”。
3.3 任务分解的粒度控制
老哥:计划里每一步该细到啥程度?
墨叔:最吃手感的一节。两个极端都坑:
- 太粗:“实现用户注册”——一步里要搞定校验、查重、哈希、写库、发邮件,中间没法验证,错了你都定位不到哪半步。
- 太细:“读第 1 行""读第 2 行”——步骤爆炸,计划本身塞爆窗口,模型在琐碎里忘了”为啥干”。
刚好粒度的尺子:一个步骤 = 模型能独立完成、且有明确可验证产出的最小单元。翻译成你的母语就是——单一职责在任务层面的翻版。main.py 的计划每步都占满了”独立 + 明确产出 + 可验证”:检索→列表、起草→草稿、自检→通过/问题、提醒→回执。
经验法则:某步失败了,你能不能一句话说清错在哪?说不清就是太粗,拆。这跟你审 PR 一个道理——你最怕”改了 30 个文件的大 PR”(粗)和”一行一 commit 的碎提交”(细),喜欢中等粒度的单一职责 PR。
3.4 反思与自我修正:Critic 循环
老哥:执行器走完一步,它咋知道自己对没对?
墨叔:靠 Critic 循环:执行 → 批评 → 改写。关键来了——main.py 的 critic_check 不是让模型自夸,而是一个确定性函数(像单测):它只看产出里有没有”待办项”和”提醒动作”。第一次草稿漏了”提醒”,critic_check 直接返回”未通过 + 问题清单”,执行器被迫回到 draft_summary 修正,再过一次 Critic 才放行。
老哥:这不就是 TDD 吗?
墨叔:正是!你 CI 里的 lint + 单测 + 评审,本质就是”确定性 critic”。critic_check 这种硬事实,比”模型自己检查自己”强一万倍——模型只”续写不验证”,自评时还带着生成时的错误前提,挑不到根上。main.py 演示的正是 Part 3.4 那句:模型挑方向,测试定生死。
进阶:Critic 不止能放”产出之后”,也能前移到”计划阶段”审思路(就是 3.2 的计划审阅)。生成后批评 + 过程中批评,两道一起上,返工成本最低。
3.5 真实经验:规划失败的 5 种常见形态
老哥:真实项目里规划都怎么翻车?
墨叔:5 种形态,常结伴出现、连锁发作(后面给串案):
| 形态 | 现象 | 根因 | 解法(对应兵器) |
|---|---|---|---|
| ① 计划过于乐观 | 列 5 步信心满满,第 2 步才发现有隐藏老接口,计划作废 | 规划不读细节,复杂度靠”想象” | 给规划器一点只读”侦察预算”;接受计划是假设,靠重规划兜底 |
| ② 步骤依赖没理清 | ”步骤3 用步骤2 的配置X”,结果步骤2 只产出类似的 Y | 步骤产出描述模糊(粒度太粗) | 像定义函数签名一样写死”输入依赖/输出契约”,Critic 在交接处校验 |
| ③ 卡死循环 | 反复调同一工具,或规划↔执行来回踢皮球 | 无失败记忆、无次数上限 | 步数/重试上限 + 失败记忆(“不得重复方案 Z”)+ 已验证步骤锁定 |
| ④ 目标漂移 | 让”加限流”,它顺手重构了整个 session 模块 | 长任务里原始目标被稀释 | 系统提示写”范围围栏”;独立 critic 盯越界 |
| ⑤ 工具失败无备选 | ”调 API 拉数据”超时,计划没说挂了咋办,整任务卡死 | 只写 happy path | 工具层做重试/降级;计划层给关键步骤写 fallback/备选路径 |
串案(一次中五刀):让 agent”给订单服务加幂等防重”——规划乐观低估了要动 DB 索引(①);含糊写”加去重键”没声明存哪(②);执行时 Redis 超时(⑤),无兜底卡住;重规划没失败记忆又生成”再试 Redis”(③);窗口被报错刷屏,模型忘了”只加幂等”的围栏,顺手改了表结构(④)。
老哥:这五种病我写代码时也犯过啊。
墨叔:同病同源。①乐观 + ⑤happy path 思维,人类工程师也爱犯——区别是你会吃一堑长一智,裸模型每次从零来。规划失败的 5 种形态,本质是”概率程序”把”确定性程序早就在防的异常”放大重演了一遍:侦察预算≈灰度探测、步骤契约≈API schema、失败上限≈重试熔断、围栏≈权限校验、fallback≈服务降级。你防御性编程的功力,平移过来就行。
起飞前检查单(pre-flight checklist):跑大任务前过一遍——计划侦察过真实复杂度没?每步输入输出契约写死没?有重试上限+失败记忆没?范围围栏+独立 critic 有没?关键工具 fallback 有没?
思考题
【思考题】
- Part 3.1 说模型”想一步走一步”,可你写 Go 也是一行一行敲的,为啥你不会像模型那样前后矛盾?你和它在”顺序执行”上的本质区别是什么?
- 3.3 的”刚好粒度”和 3.2 的”计划步骤”是同一件事的两面。如果 planner 老把任务拆得太碎(50 步那种),除了人工合并,你能在流程上怎么”结构性”地逼它拆粗一点?
- 3.4 的
critic_check在main.py里是个确定性函数(硬事实)。如果一个任务的”正确性”无法用代码简单判定(比如”这段文案是否打动人”),你该怎么给这类任务接 Critic?self-reflection 和确定性 critic 怎么搭配?
【参考答案】
- 你和模型都”顺序执行”,但你的执行被先存的全局设计约束着:落键盘前脑里已有函数签名、模块边界、命名规范,每敲一行都在”核对设计”,不是”凭感觉续写”。模型的”设计”是随文字生成临时长出来的,没有先于动作的蓝图——图跟着施工一起长,自然漂移。一句话:你”先有图再照图施工”,模型”边施工边在墙上画施工图”。所以一致性不来自”顺序/并行”,而来自”图是否先于动作存在且持久”。
- 结构性逼粗三招:① planner 提示加硬约束——“每步必须交付一个可独立运行的产物,禁止『读取第 X 行』这类原子操作;计划总步数 ≤ N(如 8)“;② executor 层做”步骤合并”——连续几步都只动同一文件且无分支,自动并成一步;③ 用重规划反馈治粒度病:太碎导致上下文被步骤噪声淹没、中途走神,就把”上次步数过多、请控制在 N 步内”喂回 planner 重画。本质是用流程的反馈回路把粒度收敛进甜区,而不是靠人每次手改。
- 不可代码判定的任务,硬事实 critic 接不上,就得两层软 critic 叠加 + 必要时人审:① self-reflection 做第一道软筛(同模型换”严格审稿人”角色,抓方向/结构/明显遗漏,便宜);② 若允许,上独立 critic(另一调用/更强模型)制造视角隔离,抓 self-reflection 的盲区;③ 这类任务”必然正确”无法靠代码保证,所以把最终判定权留给人类(HITL)——agent 只产出”带自检意见的候选”,人点头才交付。记住尺子:出错代价 × 发生概率 超过一次额外调用成本,就上独立 critic;代价极高且无法自动化判定的,必须留人审这道确定性闸门。这正呼应 Part 1”凡必然正确的放代码,否则交人”。
附:本 lab 文件结构
main.py:规划器 responder + 执行器 responder + 4 个工具(含确定性critic_check)- 跑通路径:
planner 出计划→search_docs→draft_summary(漏提醒) →critic_check(未通过) →draft_summary(修正) →critic_check(通过) →send_reminder→ 汇总