Chapter 01

基础:规划是什么 & 那条权衡轴

起点页给出了概念地图和一条贯穿全程的「承诺↔适应」轴。这一章把轴本身讲透,并定义后面四章所有范式都要复用的词汇:分解、承诺、适应、三大家族、ReAct 基线。

本章你将建立的 schema

  • 规划 = 在「自回归生成」之上加的一层控制流——决定何时停下调工具、把什么喂回、何时判完成
  • 「承诺↔适应」是一条轴,不是一组对立选项;每种范式都是轴上的落点,各自付出特定代价
  • 任务分解是所有规划的底层操作;分解粒度是一个会反噬的隐藏旋钮
  • 三大家族(分解 / 多计划选择 / 反思)各自回答一个不同的问题
  • ReAct 是最小、最适应的循环,也是后面所有"更省更并行"范式的对照基线

1.1规划是什么,为什么 agent 需要它

规划 = agent 在「目标」和「一串工具调用」之间架起的桥:决定做哪些步、什么顺序、出错怎么办。

为什么需要它

单次 LLM 调用加单个工具调用,只能解决"一跳"问题。真实任务——订差旅、多跳问答、定位并修一个 bug——需要多步,步骤之间有依赖,执行途中还会有步骤失败。没有规划这一层,工程师就得为每个任务手写一套 if/else 状态机,把工具调用按顺序和分支串起来。规划层把这套控制流交给模型自己生成。

底层机制(比文档深一层):一个 LLM 本身是无状态的 next-token 预测器——给定输入 token,吐出下一个 token,仅此而已。它没有"循环"、没有"调用工具"、没有"判断完成"的内建能力。规划本质是在这个生成过程之外,套一层控制流:什么时候打断生成去执行一个工具、把工具结果以什么形式拼回上下文、什么条件下判定任务结束。不同的规划范式,就是不同形状的控制流——这句话是理解后面所有内容的钥匙。

类比 · 带边界声明

规划像项目经理(PM)与一线执行的关系:PM 决定拆几个任务、谁先谁后、卡住了要不要重排。类比失效在记忆上——真人 PM 有持久的世界模型和记忆,而 LLM 规划器每一步能"记得"的,只有这一次显式塞进 prompt 的那些 token;上下文之外的一切,它都不知道。

场景走查:一个需要规划的任务长什么样

目标:「找出上周 GitHub 上 star 增长最快的 Python 项目,并总结它的 README」。把它摊开看:

目标 多跳·有依赖 ① 搜索 repo列表 ② 选 repo 选中1个 ③ 读 README 正文 ④ 总结 步骤间有依赖:第 N 步的输入 = 第 N−1 步的输出
图 1.1一个目标摊成四个有依赖的步骤。 注意:「③ 读 README」的输入(哪个 repo)来自「② 选 repo」的输出——所以无法把整件事压成一次提问,必须有一层东西决定"先做哪步、拿到结果再做哪步"。那层东西就是规划。
想一想

如果目标改成「分别查北京、上海、广州三个城市明天的天气」,它和上面那个任务在"步骤依赖"上有什么本质区别?这个区别会指向轴的哪一端?

展开答案(先停 10 秒再点)

三个城市的天气查询彼此独立——查北京不需要先知道上海的结果。而上面的 README 任务是顺序依赖的。独立子任务可以并行、可以一次性把计划列全,指向轴的"提前承诺"那一端(省钱、可并行);顺序依赖且每步结果会影响下一步走向的任务,指向"临场适应"那一端。

这正是 §1.2 那条轴要回答的核心问题——而它也预告了第 2 章里 LLMCompiler(擅长并行独立任务)和 ReAct(擅长顺序适应)的分野。

1.2「承诺 ↔ 适应」权衡轴(本教程的核心)

一条轴:左端「提前把整个计划定死」,右端「每做一步再决定下一步」。七种范式都是这条轴上的落点。

为什么需要它

七种范式表面上五花八门、名字各异,但它们都在回答同一个问题:在看到工具的真实结果之前,你愿意对计划承诺多少?把这个问题画成一条轴,七种范式立刻各就各位——你不再需要孤立地背七套机制,而是记住一条轴加每个范式的落点。

底层机制(比文档深一层):

承诺早(在信息不足时就把计划定死)买到两样东西:① 可并行——既然步骤间的依赖关系已经在计划里写明,没有依赖的步骤就能同时发;② 省 token——不需要每一步都把已有的观察结果重新喂回模型去"想下一步"。付出的代价是:计划建立在"假设的世界状态"上,一旦某一步的真实结果出乎意料,整个计划无法纠偏。

适应晚(每做一步、看到真实结果,再决定下一步)买到纠错能力:每个决策都基于最新信息。代价是两条:每一步都要把累积的全部上下文重新编码进模型(token 与延迟随轨迹长度线性增长),而且天然串行——下一步要等这一步的观察出来才能想。

记住这一点 · 后面会反复用到

"适应晚"的 token 成本,不是因为调用次数多,而是因为每一步都把之前所有的 Thought/Action/Observation 重新喂回去推理。这个机制是第 2 章 ReWOO 为什么能省 5 倍 token 的全部秘密——它不是少调了几次,而是不再重复编码 observation。现在记住这句话,到 §2.3 你会"啊哈"一下。

提前承诺 一次性把计划定死 临场适应 每步再决定下一步 Plan-Execute(折中:定计划但可重规划) ✓ 步骤可并行 ✓ 省 token ✗ 意外无法纠偏 ✓ 每步能纠错 ✓ 总用最新信息 ✗ 串行 · token随轨迹涨
图 1.2同一条轴,两端是对称的取舍:左端用"看不见意外"换"省钱+并行",右端用"串行+贵"换"能纠错"。 注意:没有哪一端"更好"——选哪端,取决于你的任务更怕"意外"还是更怕"贵"。这张图请记在脑子里,后面每个范式都会回到它上面定位。

1.3任务分解:所有规划的底层操作

任务分解 = 把一个大目标切成若干可执行的子任务。无论哪个范式,第一步都是它。

为什么需要它

模型不能直接"执行"一个抽象目标,只能执行具体的工具调用。分解就是从"帮我订差旅"到"查航班 / 比价 / 下单 / 订酒店"的那一步翻译。它是规划的最底层动作,七种范式的差别只在于怎么分、什么时候分,而不在于要不要分。

底层机制(比文档深一层):分解发生的时机本身就落在那条轴上。一次性全分解(Plan-and-Execute、ReWOO 的 planner 一口气列出全部子任务)是"提前承诺";增量分解(ReAct 每步只决定下一个子任务)是"临场适应"。还有一个容易被忽视的旋钮——分解粒度:拧太粗,子任务仍然太难、模型一步做不完;拧太细,步骤数爆炸,延迟和 token 成本飙升,这就是第 2 章会专门讲的"过度分解"失败模式。粒度没有万能值,它本身就是个要权衡的设计决策。

类比 · 带边界声明

分解像庖丁解牛——沿着关节下刀,省力。类比失效在:庖丁知道牛的关节在哪,而 LLM 不一定找得到任务的"关节",它有时在错误的地方下刀(切出根本不存在的子任务,即"工具幻觉"),或者把一刀能解决的硬切成十刀(过度分解)。分解的质量本身就是规划质量的一部分。

1.4三大家族:每个家族回答一个不同的问题

把现代 LLM 规划范式归类,最清晰的一套来自 Huang 等人 2024 年的规划综述。它把方法分成五类,本教程聚焦其中最主流的三类(另两类——外部模块辅助、记忆增强——分别在第 4 章桥接经典规划时提到、以及并入 Reflexion 讲)。三大家族的根本区别,是它们各自回答一个不同的问题:

表 1.1 · 三大家族各自回答的问题
家族它回答的问题代表范式本教程位置
① 任务分解"怎么把目标拆成步骤、按什么顺序执行?"ReAct · Plan-and-Execute · ReWOO · LLMCompiler第 2 章(核心)
② 多计划选择"生成多条候选路径,怎么评估并选出最好的一条?"Tree of Thoughts · LATS第 3 章
③ 反思与提炼"上一次失败了,怎么把教训用上、让下一次更好?"Reflexion第 3 章

这三个问题正交:一个真实系统可以同时用到——比如先分解(①),对其中难的一步做多路径搜索(②),整体失败后反思重试(③)。第 3 章末尾的 LATS 就是把三者缝在一起的范式。分类不是为了贴标签,而是为了在选型时知道"此刻缺的是哪种能力"。

想一想

"客服 agent 调外部 API 时经常报错,要能当场换个方法重试"——这个需求最直接命中哪个家族要回答的问题?

展开答案(先停 10 秒)

"当场换方法"指向临场适应,是任务分解家族里 ReAct 那一端的强项(每步看真实结果再决定)。注意它不是反思家族——反思(Reflexion)是"整次任务失败后,把教训写进记忆、下一次整体重试",是跨尝试的改进,不是同一次执行内的当场纠错。两者都"从失败中学",但时间尺度不同:一个在 step 之间,一个在 episode 之间。这个区分第 3 章会再钉一次。

1.5ReAct:最小的、最适应的基线

ReAct = 让模型在「推理(Thought)」和「行动(Action)」之间交替,每次行动后把真实「观察(Observation)」喂回,再想下一步。

为什么需要它(为什么先讲它)

ReAct(Yao 等人 2022)是最小的、最靠"适应"端的规划循环,也是后面所有"更省钱 / 更并行"范式诞生的对照基线——ReWOO、LLMCompiler 的论文都是拿 ReAct 当靶子来证明自己的改进。先把基线吃透,后面的对比才有锚点。

底层机制(比文档深一层):ReAct 把模型的解码循环强制成重复的 Thought → Action → Observation 三元组。关键在 Action 这一步:模型生成一个工具调用后,生成在此暂停,外部代码真正执行这个工具,把真实结果作为 Observation 文本拼回上下文,然后解码恢复,模型基于这个新信息想下一步。

这和纯思维链(CoT,一段不被打断的内心独白)的本质区别是:ReAct 的 token 流被外部真实字符串反复打断并重新接地。CoT 一旦在第二步推错,错误会顺着独白滚下去;ReAct 因为每步都灌入真实观察,能纠正事实漂移。这就是 ReAct 论文的核心论点——CoT "受困于幻觉,因为它不与外部世界接地"。

Thought 推理 Action 行动 生成暂停·调真实工具 Observation 工具真实结果 结果喂回 1 轮 = 1 个三元组
图 1.3ReAct 的循环:想 → 做 → 看,再回到想。 注意:箭头 "Observation → Thought" 是 ReAct 的灵魂——真实结果回到推理,让下一步基于事实而非想象。也正是这条回边,使得每一轮都要带上之前全部内容重新推理(轴右端的代价)。

场景走查:一次两跳问答的 ReAct 轨迹

问题:「导演了《盗梦空间》的人,下一部上映的电影是什么?」单跳查不到——要先查导演,再用导演查新片。ReAct 的轨迹(示意):

react-trace · 示意轨迹 text
Thought: 要先找出《盗梦空间》的导演是谁
Action:  search("盗梦空间 导演")
Observation: 克里斯托弗·诺兰          ← 外部真实结果,喂回

Thought: 现在查诺兰下一部上映的电影
Action:  search("诺兰 2026 上映 新片")
Observation: 片名《XXX》,2026-07 上映  ← 又一次接地

Thought: 已拿到答案,可以收尾
Action:  finish("《XXX》")

未本地验证 · 仅为说明 Thought/Action/Observation 的交替结构

  • 第 2 行 Action: search(...):体现"行动"——生成在这里暂停,外部真去搜索。这就是定义里"行动"的含义。
  • 第 3 行 Observation:真实结果接地,纠正了模型未接地时的瞎猜。这是 ReAct 区别于 CoT 的关键。
  • 第 5 行起:第二跳的 Thought用上了第一跳的 Observation(诺兰)——这就是"每步带着全部历史重新推理"的具体样子。轨迹越长,重新喂回的历史越多。
一个反直觉点(后面第 3、4 章会回扣)

ReAct 单独使用,未必比"纯 CoT + 自洽采样(CoT-SC)"更强。原论文的头条数字,很多来自 ReAct 与 CoT-SC 互相回退切换的组合策略。而且"强制每步产出一个合法 action"是一种结构约束,反而降低了推理的灵活性——论文里 23% 的错误是"无信息搜索"把 agent 带进了重复的思考/行动死循环。"最适应"不等于"最强",它只是轴右端那个最朴素的落点。

§本章 self-check

先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节再读一遍。

  1. 用一句话说出「承诺↔适应」轴左右两端各自买到什么、各自付出什么代价。
  2. ReAct "每一步把累积的全部上下文重新喂回推理"——这件事会让什么量随轨迹长度增长?
  3. 任务分解的"粒度"旋钮拧得太细,会触发哪个失败模式?拧太粗呢?
  4. 三大家族里,哪个家族的核心动作是"生成多条候选路径再用评估函数选"?它和"反思"家族在"从失败中学"的时间尺度上有什么不同?
答案(先做完再展开)
  1. 左端"提前承诺":买到可并行 + 省 token,代价是计划建立在假设的世界状态上、意外无法纠偏。右端"临场适应":买到每步用最新信息纠错,代价是串行 + token/延迟随轨迹线性增长。
  2. token 消耗与延迟随轨迹线性(偏二次)增长——因为每一步都把之前所有 Thought/Action/Observation 重新编码进 prompt。这正是 ReWOO 的攻击点。
  3. 太细 → 过度分解:步骤数爆炸,延迟与成本飙升。太粗 → 子任务仍太难,模型一步做不完。粒度本身是个要权衡的设计旋钮,没有万能值。
  4. "多计划选择"家族(ToT / LATS)。区别:多计划选择和 ReAct 的当场纠错都在一次执行内(step 之间);反思(Reflexion)是跨尝试(episode 之间)——整次失败后把教训写进记忆,下一次整体重试。
进阶挑战 · 刚好够不着

把一个任务钉到轴上

任务:「给定一个 GitHub 仓库 URL,自动跑通它的测试套件并报告失败的用例」。在纸上画出图 1.2 的轴,标出这个任务的理想落点,并写下:如果错用了轴的另一端(比如本该适应却提前把计划定死,或反之),具体会在哪一步崩?

提示(卡住再展开)

问自己:跑测试这件事,步骤之间的依赖在执行前能不能完全预知?(克隆→装依赖→跑测试 的大框架可预知,但"装依赖"这步极易因环境千奇百怪而失败,需要看到真实报错再决定怎么修。)这意味着它不是纯粹的一端,而是"大框架可提前承诺、易错步骤需临场适应"——这恰好是第 2 章 Plan-and-Execute"定计划 + 失败时重规划"的折中位。