# 阶段五 · 小点 2:规划能力增强 > 所属:阶段五 进阶技术:能力增强与优化 > 定位:长任务的 Agent 最怕「跑一半崩了」「某一步失败全链死掉」「死板执行不撞南墙不回头」。这一讲先把论文范儿的 ToT/GoT 讲清(了解即可),再给出真正工程的武器:里程碑 / 失败回滚 / 断点续执行 / 动态重规划。 ## 精简大纲 1. 🟢 思维树 ToT / 思维图 GoT(论文概念,了解即可) 2. 🔴 工程可用:里程碑追踪 / 失败回滚 / 断点续执行 3. 🔴 动态规划(重规划) 4. 长任务管理器要点 ## 学习内容详情 > 核心认知:ToT/GoT 是「论文的优雅」,里程碑/回滚/断点/重规划是「生产的刚需」。学规划增强,重点把后面四个工程技能练到能写代码。 ### 1. ToT / GoT(🟢 论文范儿,了解即可) ```mermaid graph TD subgraph tot["思维树 ToT"] T1[问题] --> T2[路径A
自评8分] T1 --> T3[路径B
自评3分 剪枝✂] T1 --> T4[路径C
自评6分] T2 --> T5[继续展开A1] T2 --> T6[继续展开A2] end subgraph got["思维图 GoT"] G1[推理] --> G2[分支可合并] G1 --> G3[分支可循环] end ``` - **思维树(ToT)**:对同一问题**并行生成多条推理路径**,自评打分后剪枝、保留高分路径继续展开(像下棋推演多个分支)。效果惊艳但一次任务几十次 LLM 调用,工业极少落地。 - **启示**:生产中可以用「多方案生成 + 评分择优」的**简化版**(生成 2~3 个方案 → 打分选优),没必要整树搜索。 - **思维图(GoT)**:把树推广成图,推理分支可**合并、循环**。更前沿,同样了解即可。 > ⏳ **短期可不深究**:ToT/GoT 是论文级的「优雅」,一次任务就要几十次 LLM 调用,工业极少落地;第一遍只需记住"多方案生成 + 评分择优"的简化启发即可,不必深入整树搜索、图合并机制的实现。 ### 2. 工程可用的规划增强(🔴 生产真需求) #### 2.1 里程碑 + 回滚 + 断点续执行的关系 ```mermaid graph TD M1[里程碑 取数
pending] -->|run| M1r[running] M1r -->|成功| M1d[done ✓] M1r -->|重试耗尽| M1b[rolled_back ✗] M1b --> M2[清洗 收到上游缺失
走降级路径] M1d --> M2 M1d -.每步落盘.- CK[检查点文件
崩溃后读档] CK -.跳过快照 done 的步骤.- M2 ``` - **里程碑(Milestone)**:把长任务切成有明确产出的检查点(「取数完成→校验完成→报告完成」)。作用:**进度可观测、失败可定位、断点可恢复**。 - **失败回滚(Rollback)**:某步失败重试耗尽后,标记该步回滚(放弃 / 补偿),后续步骤收到「上游缺失」信号走降级路径——而不是让整条链死掉。 - **断点续执行(Resume)**:任务状态每步落盘;进程崩溃 / 重启后从上次完成的里程碑继续,不从头再来。生产对照:**LangGraph 的 Checkpointer** 就是框架级实现。 - **动态规划 / 重规划(Re-planning)**:执行中根据工具反馈修改计划:发现脏数据就临时插一个「数据清洗」步骤——不僵硬执行初始方案。 > 🔬 **深度理解 · 规划(Plan-and-Execute / ReAct / 任务拆解)与长任务管理**: > 1. **本质**:规划是把"一步到底"的隐式推理,显式化为「可观测、可回退、可恢复的子步骤序列」——让长任务从"黑盒碰运气"变成"白盒可管理"。 > 2. **机制**:以 ReAct 为代表的「推理–行动–观察」循环是内置在单次迭代里的瞬时规划;而 Plan-and-Execute 把「先整体规划」与「逐步执行」拆成两个阶段,更适合长任务。工程落地常用「任务拆解」把目标切成里程碑(Milestone),每步执行后视反馈决定推进 / 回滚 / 追加;再叠加断点续执行与动态重规划,形成"先拆、再跑、坏了能回、变了能改"的闭环。 > 3. **为什么重要**:长任务最怕"跑一半崩掉 / 某步失败全链死 / 撞了南墙不回头"。拆解 + 里程碑让进度可观测、失败可定位;回滚与断点让失败不致命;重规划让计划跟随现实。它解决的是"可靠性"与"可控性",这正是论文级规划与生产级规划的分水岭。 > 4. **易错点**:把「一次性计划」当成规划的全部——真正的长任务规划必须引入"执行反馈→修订"的动态闭环;另一种错误是把任务拆得过细,每步都重调度反而放大 token 与调度开销,拆解的粒度要匹配失败的可恢复性而非越细越好。 #### 2.2 里程碑状态机 ```python # 里程碑的四种状态流转: pending → running → done / rolled_back from enum import Enum class MilestoneStatus(Enum): PENDING = "pending" RUNNING = "running" DONE = "done" ROLLED_BACK = "rolled_back" def transition(step, old, new): """状态机: 只允许合法的迁移, 防止状态错乱""" legal = { MilestoneStatus.PENDING: {MilestoneStatus.RUNNING}, MilestoneStatus.RUNNING: {MilestoneStatus.DONE, MilestoneStatus.ROLLED_BACK}, MilestoneStatus.DONE: set(), # done 后不可再变(除非回滚补偿) MilestoneStatus.ROLLED_BACK: {MilestoneStatus.PENDING}, # 允许重试恢复 } if new not in legal[old]: raise ValueError(f"非法状态迁移: {old} -> {new}") print(f" [里程碑:{step}] {old.value} → {new.value}") ``` #### 2.3 断点续执行 + 动态重规划的长任务管理器 ```python import json, os, time STATE_FILE = "./plan_state.json" # 状态每步落盘 class LongTaskManager: """带 落盘 + 断点恢复 + 动态插入 的长任务管理器""" def __init__(self, steps: list): # steps: [{"name": "取数", "fn": callable}] self.steps = steps self.state = {s["name"]: "pending" for s in steps} # ---- 落盘 & 读档 ---- def save(self): with open(STATE_FILE, "w", encoding="utf-8") as f: json.dump(self.state, f, ensure_ascii=False) def load(self) -> bool: """崩溃重启后先读档, 返回是否接着上次跑""" if not os.path.exists(STATE_FILE): return False with open(STATE_FILE, encoding="utf-8") as f: self.state = json.load(f) print(f"[断点恢复] 读档: 还有 {sum(s=='pending' for s in self.state.values())} 步未完成") return True # ---- 主循环 ---- def run(self, replan=None): self.load() # ① 先读档 for step in self.steps: name = step["name"] if self.state.get(name) == "done": # ② 已完成的直接跳过 print(f"[跳过] {name} 已完成, 不复跑") continue transition(name, MilestoneStatus.PENDING, MilestoneStatus.RUNNING) self.state[name] = "running"; self.save() try: step["fn"]() # ③ 真正执行 self.state[name] = "done" except Exception as e: self.state[name] = "rolled_back" # ④ 失败标记回滚 print(f"[回滚] {name} 失败: {e}") self.save() # 失败态也要落盘(否则重启后会丢失回滚标记) continue # ⑤ 不中断, 让后续走降级 self.save() if replan: # ⑥ 动态重规划钩子 replan(self) # (可临时插入新里程碑) print("[完成] 所有里程碑处理结束") ``` ```python # 使用: 模拟中途崩溃后恢复 def make_task(): return LongTaskManager([ {"name": "取数", "fn": lambda: time.sleep(0.1)}, {"name": "清洗", "fn": lambda: time.sleep(0.1)}, {"name": "报告", "fn": lambda: time.sleep(0.1)}, ]) # 第一次跑到一半模拟进程被杀(只跑了取数), 状态已落盘 t1 = make_task(); t1.state["取数"] = "done"; t1.save() # 重启后: t2 = make_task(); t2.run() # 从"清洗"继续, 不复跑"取数" ``` > ⏳ **短期可不深究**:这里用手写 JSON 每步落盘只是为了让你看懂"状态持久化→重启读档跳步"的原理;第一遍不必自行造检查点轮子,生产直接用 LangGraph Checkpointer 这类框架级实现即可,道理一样、能力更全。 ### 3. 动态重规划代码 ```python def dirty_data_replan(manager: LongTaskManager): """ 重规划钩子示例: 若发现"清洗"阶段标记了脏数据, 就临时插一个"数据清洗"里程碑。 在 run() 的 replan 回调里调用, 实现"不僵硬执行初始方案"。 """ if manager.state.get("清洗") == "rolled_back": new_step = {"name": "数据清洗", "fn": lambda: print("临时插入: 深度清洗脏数据")} manager.steps.insert(1, new_step) # 动态插入计划 manager.state["数据清洗"] = "pending" print("[重规划] 检测到脏数据, 已插入『数据清洗』里程碑") ``` > 🔬 **深度理解 · 动态重规划(Re-planning)**: > 1. **本质**:把计划从"当真理"降级为"当假设"——执行过程中用工具反馈持续校验并修订它,让"计划"从静态文本变成"随现实更新的可执行假设"。 > 2. **机制**:典型的闭环是「执行子步 → 观察工具返回 → 识别与假设不符的异常 → 更新剩余计划(插入 / 重排 / 放弃子步)→ 继续」。本文的重规划钩子即在 `run()` 主循环里提供回调点,检测到脏数据(如清洗步回滚)就向步骤列表插入补偿里程碑。它与 ReAct 的瞬时"观察后决策"本质同源,只是作用在任务级而非单步级。 > 3. **为什么重要**:现实环境(网页结构变化、数据脏、工具返回异常)不会按初始计划走,僵硬执行初始方案会让 Agent"撞了南墙不回头";动态重规划是长任务能在不确定世界里存活的关键。 > 4. **易错点**:警惕"过度重规划"——每次反馈都大改计划会造成震荡与无限追加,通常要设修订阈值或与回滚 / 补偿配合,避免重规划本身成为死循环来源。 ### 4. 长任务管理器要点 - **裸写版原理**:状态落盘(JSON / DB)→ 崩溃重启后先读档 → 已完成的步骤直接跳过。 - **里程碑状态机**:`pending / running / done / rolled_back` 四态。 - **动态插入**:`run()` 里插「计划修订」钩子,如检测到脏数据就追加新里程碑。 - 生产上不用自造轮子,直接用 **LangGraph Checkpointer**(框架级落盘 + 恢复)。 ## 本节自检 - [ ] 能说清 ToT/GoT 与里程碑/回滚/断点续执行的本质差异(论文 vs 工程) - [ ] 能实现一个带状态落盘 + 断点恢复的长任务管理器 - [ ] 能写出里程碑四态状态机 + 动态重规划钩子 ## 本节常见面试题(深度解析) > 针对本节核心知识的面试高频点,配合"本质+机制"式理解,能让你答得既有深度又有广度。 ### 面试题 1:ToT/GoT 这类"论文级"规划为什么生产里很少用?真实的工程规划增强做了什么? - **面试官想考察**:能否区分"学术优雅"与"生产刚需",并说清工程规划的核心矛盾。 - **专业作答(含深度)**: - ToT 一次性并行生成多条推理路径、自评剪枝再展开,效果惊艳但一次任务几十次 LLM 调用,token 与延迟都不可接受;GoT 在图层面做分支合并 / 循环,更前沿、更复杂。 - 所以工业落地取"简化版":生成 2~3 个方案 → 打分选优,而非整树搜索。 - 真正生产需要的是工程规划三件套:里程碑(任务可观测、失败可定位)、失败回滚(某步失败不让全链死)、断点续执行(崩溃后从上次完成点接着跑),核心是可靠性而非搜索最优解。 - **加分亮点 / 深度追问**:可主动点出"论文关心效果上界、工业关心可靠性下界",追问常是"生产里还需要什么"——答:动态重规划,让计划随现实变化。 ### 面试题 2:断点续执行的原理是什么?手写 JSON 落盘和 LangGraph Checkpointer 有何差异? - **面试官想考察**:是否真正理解"状态持久化 + 重启读档"的机制,以及为什么生产要框架级实现。 - **专业作答(含深度)**: - 原理:每步执行完把该步状态"落盘"(JSON/DB/框架检查点),崩溃重启后先读档,把已完成(done)的里程碑直接跳过,从第一个未完成(pending)的继续。 - 要点:失败态(rolled_back)也要落盘,否则重启会丢失回滚标记导致状态错乱;恢复粒度决定了"从哪一步接着跑"。 - 手写版只演示原理;Checkpointer 提供框架级快照、可序列化任意图状态(含中间变量、内存、历史消息),并天然支持提交/回滚与分支,且不污染业务代码。 - 局限边界:状态若存在进程外(数据库 / 外部副作用),仅靠检查点不足以回滚已发生的外部副作用,那需要补偿机制。 - **加分亮点 / 深度追问**:可主动提到"落盘 + 读档 = 计划增长为幂等/可恢复",追问常是"外部副作用怎么恢复",答:检查点只管内部状态,副作用要用补偿 / 事务 / 降级路径处理。 ### 面试题 3:动态重规划与 ReAct 有什么关系?重规划会不会引入新的风险? - **面试官想考察**:能否把"任务级重规划"与"单步推理循环"打通,并意识到动态机制的双刃剑属性。 - **专业作答(含深度)**: - 同源:ReAct 是"推理–行动–观察"的瞬时决策循环,动态重规划把同样的"观察反馈→修订计划"提升到任务级,二者哲学一致,只是作用粒度不同(单步 vs 任务)。 - 机制:执行子步 → 观察工具返回 → 识别异常 → 插入/重排/放弃子步 → 继续,可用 replan 钩子在主循环回调。 - 风险:过度重规划会导致计划震荡、无限追加子步,反而制造新的死循环或成本失控;应设修订阈值、与回滚/补偿联动。 - **加分亮点 / 深度追问**:可主动区分"Plan-and-Execute(先总体规划再执行)"与"ReAct(边执行边决策)"的取舍,追问常是"动态插入计划如何避免冲突"——答:插入要校验状态机合法性并落盘,且要与已回滚步骤的降级路径协同。