任务规划与分解(Planning)
你让一个采购助手完成这件事:
帮我比较笔记本 A、B、C,预算不超过 6000 元,重量不超过 1.5 kg,主要用来做 Python 开发。给出有来源的推荐,先不要购买。
助手查到了 A 的价格,又在 B 的页面上看到了“限时优惠”,于是开始研究优惠券。几轮查询后,它推荐了 B,却忘了查 C,也没确认 B 的重量。每一步看起来都与买电脑有关,整个任务却没有完成。
Planning 要解决的就是这类遗漏:让 Agent 记得还有哪些工作、每项工作要得到什么结果,以及什么时候才算交付。本文用这个任务走完一次规划和执行过程。
1. 先把用户的话变成完成条件
Agent 通常会循环执行“读取当前状态、决定下一步、调用工具、接收结果”。ReAct 着重讲这个过程中如何依据观察调整行动。任务一长,只盯着下一步容易漏掉尚未完成的要求,计划便给这个循环增加了一份可持续更新的工作记录。
先别急着让模型列出十个步骤。用户要的结果是什么,需要先确定。
| 用户的要求 | 执行时具体检查什么 |
|---|---|
| 比较 A、B、C | 三台都要有记录;查不到信息也要说明,不能悄悄漏掉一台 |
| 不超过 6000 元 | 比较同一配置的当前到手总价;有运费或附加条件时一并记录 |
| 不超过 1.5 kg | 确认对应配置的机身重量,统一使用 kg |
| Python 开发 | 说明用途范围,并结合内存、CPU、系统兼容性等证据解释推荐 |
| 有来源 | 价格、重量和配置都能找到对应页面,并记录查询时间 |
| 不要购买 | 只允许查询和整理,执行程序不提供下单权限 |
“Python 开发”还不够具体。写脚本、跑 Web 服务与本地训练大模型,对机器的要求差很多。助手可以先问一句用途;如果用户希望直接给建议,就明确按“日常脚本与 Web 开发,不包括本地训练大模型”来比较,不能把自己的假设当成用户已确认的要求。
预算和重量属于硬约束,超过了就不能作为符合条件的推荐。性能、续航和扩展性则用于比较已经满足硬约束的候选。模型可以提出“要不要放宽预算”,但不能在计划里自行改成 6500 元。
2. 拆出的每一步,都要有看得见的产物
把任务写成“查资料、做分析、给结论”仍然太宽泛。执行到一半时,很难判断“查资料”到底完成没有。
可以先拆成下面这些工作:
| 步骤 | 需要得到的产物 | 什么时候算完成 |
|---|---|---|
| 查 A | A 的配置、总价、重量、库存和来源记录 | 必要字段有证据,查不到的字段明确标为未知 |
| 查 B | B 的同类记录 | 使用与 A 一致的比较口径 |
| 查 C | C 的同类记录 | 使用与 A 一致的比较口径 |
| 比较 | 三台对比表及候选推荐 | 逐项检查预算、重量和用途,不把未知写成符合 |
| 核对 | 可交付的推荐说明 | 结论与来源一致,缺失信息和不满足条件的原因写清楚 |
“B 缺货”也可以是一次成功查询的产物。查询完成,只说明已经获得并记录结果;它不代表 B 符合推荐要求。这个区别会影响后面的调度:不能因为商品缺货,就永远把“查 B”留在未完成状态。
步骤也不必细到“打开页面、点击参数栏、复制价格”。在规划层面,把“取得 A 的完整记录”作为一个子任务就够了。执行这个子任务时,再根据实际页面决定如何查询。这样既能看见整体进度,也保留了工具使用的灵活性。
3. 计划如何进入 Agent 的执行过程
计划可以先用文字表达。需要程序保存和调度时,再转换成结构化数据。下面是一个简化示例,字段由应用自己约定,不是某个框架要求的格式:
{
"plan_version": 1,
"goal": "比较 A、B、C,给出带来源的 Python 开发笔记本推荐",
"constraints": {
"max_total_price_cny": 6000,
"max_weight_kg": 1.5,
"purchase_allowed": false
},
"steps": [
{"id": "collect_A", "task": "取得 A 的商品记录", "depends_on": []},
{"id": "collect_B", "task": "取得 B 的商品记录", "depends_on": []},
{"id": "collect_C", "task": "取得 C 的商品记录", "depends_on": []},
{
"id": "compare",
"task": "筛选并比较三台笔记本",
"depends_on": ["collect_A", "collect_B", "collect_C"]
},
{"id": "verify", "task": "核对推荐与来源", "depends_on": ["compare"]}
]
}goal 保存本次要交付什么,constraints 保存筛选条件。id 让程序能准确记录“哪一步完成了”,depends_on 则决定“哪一步现在可以开始”。plan_version 在修改计划时递增,用来区分修改前后的安排。
前面表格里的产物要求和验收条件,也需要保存在任务配置或各步骤的说明中。这里省略重复字段,方便先看清依赖关系。
模型输出这份 JSON 后,应用还要检查步骤是否齐全、依赖是否存在、是否出现循环依赖,以及工具动作是否在允许范围内。purchase_allowed: false 只是计划中的声明;真正阻止下单的是执行程序的权限控制,不能交给模型自行遵守。
一份计划可以由模型生成,执行由外层程序推进:
用户目标与约束
↓
模型提出计划 → 程序校验计划
↓
程序选择依赖已满足的步骤
↓
模型选择查询动作 → 程序校验并调用工具
↓
保存工具结果 → 检查本步产物 → 更新状态
↓
继续下一步,或根据新情况调整计划这里的计划是面向任务的行动安排,例如“查全三台机器的重量”。它不要求展示模型的内部思维,也不能充当执行证据。计划上写着“核对价格”,只有工具查到了价格、保存了来源并通过检查,才能记成已经完成。
4. 哪些步骤可以一起做,哪些必须等待
查 A 不需要等 B 的结果,三台商品可以分别查询。比较必须等到三份记录都返回,或者已经明确某份记录暂时拿不到。
查 A ─┐
查 B ─┼──→ 比较 ──→ 核对推荐 ──→ 返回用户
查 C ─┘这类带方向、没有环的依赖关系叫 DAG(有向无环图)。读图时只需要先记住:箭头指向的步骤依赖箭头起点的结果。
程序可以并发查询三台机器,也可以因为搜索服务限流而只同时查两台。并发多少由执行程序和工具能力决定,不是模型写了“并行”就自然实现。查商品、整理结果也不需要分别创建多个 Agent;一个模型配合程序调度就能完成。
在一次执行中,状态可能这样变化:
| 时刻 | 查 A | 查 B | 查 C | 比较 | 核对 |
|---|---|---|---|---|---|
| 计划通过校验 | 待执行 | 待执行 | 待执行 | 等待依赖 | 等待依赖 |
| 开始查询 | 执行中 | 执行中 | 执行中 | 等待依赖 | 等待依赖 |
| B 暂时超时 | 已完成 | 等待重试 | 已完成 | 等待依赖 | 等待依赖 |
| B 重试成功 | 已完成 | 已完成 | 已完成 | 执行中 | 等待依赖 |
| 对比表检查通过 | 已完成 | 已完成 | 已完成 | 已完成 | 执行中 |
状态由程序根据实际事件更新。若 B 最终仍查不到,可以将该查询记为失败,再根据任务约定决定是停止,还是调整为“比较已有信息并明确 B 的缺失项”。不能直接把失败改成成功,让下游假装数据齐全。
5. 谁来规划,谁来执行,谁来验收
你会在 Agent 文章里看到 Planner、Executor、Verifier。这几个词表示职责,未必是三个独立 Agent,更不要求使用三个不同模型。
Planner 负责提出步骤、依赖和完成条件。Executor 按当前步骤调用工具,保存证据与结果。Verifier 检查结果是否符合用户要求。它们可以由同一个模型在不同调用中承担,配合程序完成调度和校验。
沿用 ReAct 示例中的查询记录:
| 型号 | 查到的事实 | 比较时怎样使用 |
|---|---|---|
| A | 页面低价对应 8 GB 版本,16 GB 版本超过 6000 元 | 把两个配置分开记录,不能用低价配上高配参数 |
| B | 重量超过 1.5 kg | 标明不满足用户的重量约束 |
| C | 5999 元,1.42 kg,16 GB 内存、512 GB 存储 | 价格和重量满足约束,继续结合用途与其他规格判断 |
每条记录在实际系统里还要附带具体配置、来源和查询时间。这张表只展示用于说明判断过程的字段。
比较步骤可能倾向于 C,因为它满足明确的预算、重量要求,16 GB 内存也给 IDE 和多个开发服务留下了更多空间。但 16 GB 只是这里公开说明的推荐依据,用户并没有要求“低于 16 GB 一律排除”。A 的低配版本应当如实列出其取舍,不能被悄悄改写成违反用户硬条件。
验收也应分工。价格是否不超过 6000、重量是否不超过 1.5,可以让程序直接比较数字;来源是否对应同一款配置、推荐理由是否超出了资料,需要结合原始页面核对,模型可以辅助完成。缺少 CPU、系统兼容性等必要资料时,推荐应保留相应条件,不能只凭内存断言 C 适合所有 Python 工作负载。
如果再让模型检查一次,它有可能发现遗漏,也可能重复第一次的错误。模型写出“审核通过”不能代替规则检查和来源核验。最终结果至少要解释:谁满足硬约束、谁被排除、推荐依据是什么、还有什么尚未确认。
6. 执行中变了条件,计划怎样改
B 缺货,不一定需要重新规划
假设后续查询又发现 B 缺货,把这条事实写进商品记录,比较时说明当前不可购买即可。现有计划已经能处理这个结果,没必要为了“动态规划”再生成一遍计划。
若用户随后要求“找一台有货、重量合适的同级替代品 D”,任务范围才发生变化。新计划需要增加查 D,更新比较范围,并重新核对最终推荐。A、C 的资料如果仍然有效,可以继续使用。
C 从 5999 元涨到 6299 元,需要重算相关结论
假设对比表已经完成,最后核对时发现 C 的价格涨了。旧推荐依赖“C 不超过 6000 元”,现在这个前提已经失效。
已保存的商品记录:C.v1,5999 元
旧对比表:引用 A.v1、B.v1、C.v1,条件推荐 C
重新查到的记录:C.v2,6299 元
处理:保留旧记录供追溯,但停止使用旧对比表和旧推荐
继续:基于 A.v1、B.v1、C.v2 重新比较,再核对新推荐如果执行安排需要增加“刷新 C 的报价”步骤,就生成新版本计划;如果原计划本来就包含价格复核,也可以在既定规则下重新执行受影响步骤。无论采用哪一种方式,都要把依赖旧价格的比较和推荐标记为失效,不能只更新表中的一个数字,却保留“推荐 C”的结论。
局部重规划与结果失效处理需要配合:计划说明接下来做什么,依赖记录帮助程序找出哪些旧结论需要重做。
已经完成的步骤是否可以复用,不能只看状态是不是“成功”。还要看它使用的输入、商品配置、证据版本和有效时间是否变化。例如 A 的重量记录通常比促销价格稳定,但仍要确认是在比较同一配置;过了一周重新执行任务,旧价格也可能需要刷新。若用户把预算改为 5500 元,即使商品数据全部未变,比较和推荐仍然必须重做。
7. 重试、改计划和停止,分别在什么时候发生
网络超时后,用相同参数再查一次 B,属于重试。发现原页面无法提供重量,增加“查官方规格页”的步骤,属于调整计划。发现没有候选同时满足预算和重量,则是当前任务得不到符合条件的推荐,继续重复搜索同一批页面通常没有帮助。
例如,单次查询最多尝试 2 次,整个任务最多调整计划 2 次,并设置总用时限制。具体次数和时限根据业务调整。
达到限制后,助手应该交付已确认的结果和剩余问题。例如:
B 超重;C 的最新价格超出预算。A 的页面低价对应 8 GB 版本,但它的重量和系统兼容性资料尚未查全,目前无法给出满足全部条件的推荐。
这是信息尚不充分时的另一种执行结果,不能把未查全的信息写成已经验证。如果三台都确认不满足要求,就直接说明没有合适候选,并让用户决定是否扩大型号范围或调整条件。不能通过偷偷放宽约束来让任务显示“已完成”。
8. Planning、ReAct 和 Memory 怎样放在一起
三者可以出现在同一个 Agent 里,分别解决执行过程中的不同问题。
Planning 保存“查 A、查 B、查 C,然后比较和核对”的整体安排。在“查 A”这个子任务内部,ReAct 可以依据工具返回的内容继续行动:商品页没有重量,就转到规格页;规格页有多个配置,就先确认型号。
Memory 则保存这次任务需要延续的状态:用户约束、当前计划版本、已完成步骤、商品证据、哪些结论已经失效。服务中断后,程序读取这些记录,才知道应当继续查 B,还是重算引用旧价格的推荐。只保存一句“我们正在帮用户选电脑”,不足以恢复任务。
对于固定、成熟的业务流程,步骤可以直接写在程序里,让模型只处理查询和比较中需要理解语言的部分。查一台机器的重量,也往往用一次工具调用就够了。只有当任务需要临时分解、步骤会随结果变化、遗漏又会影响交付时,才值得让模型参与规划。
在这个采购例子里,最先需要做好的就是那张工作表:三台机器都记录了吗,哪一步在等资料,最终推荐引用的是哪一版价格。把这些问题回答清楚,规划才真正参与了 Agent 的执行。