规划不是让模型写一份漂亮的待办清单,而是让它在动手前想清楚:目标是什么、需要哪些中间状态、哪些步骤可以并行、什么情况下要重来。
隐式规划与显式规划
隐式规划:模型在推理中自己决定下一步,不输出计划。ReAct 就是典型。灵活,但人看不见、系统管不了。
显式规划:模型先输出一份结构化计划,再由执行器逐步完成。好处是:
- 人可以审查、修改;
- 系统可以并行调度;
- 进度可追踪、可恢复;
- 失败时知道该重跑哪一步;
- 评估可以同时看计划质量与执行质量。
代价是额外的 token、可能的过度规划,以及计划过时需要重规划。
任务超过 5 步、或需要跨多个工具/数据源时,用显式计划;简单问答和短循环用隐式规划。
计划的最小结构
不要只让模型输出一串自然语言步骤。给它一个可执行的 Schema:
{
"goal": "评估 ACME 2025 年财务风险并给出三条建议",
"assumptions": ["以审计后的 10-K 为准", "同行取三家可比公司"],
"steps": [
{
"id": "s1",
"action": "获取 ACME 2025 年 10-K",
"tool": "search_filings",
"depends_on": [],
"done_when": "拿到包含利润表与资产负债表的文件"
},
{
"id": "s2",
"action": "抽取关键财务指标",
"tool": "extract_financials",
"depends_on": ["s1"],
"done_when": "得到营收、毛利率、经营现金流、负债率"
},
{
"id": "s3",
"action": "对比三家同行毛利率",
"tool": "search_filings",
"depends_on": ["s2"],
"done_when": "得到三家同行同年数据"
},
{
"id": "s4",
"action": "撰写风险简报",
"tool": "write_report",
"depends_on": ["s2", "s3"],
"done_when": "含三条风险、每条附数据与来源"
}
]
}几个关键字段:
id:稳定的步骤标识,用于重试和引用;depends_on:依赖关系,决定并行与顺序;done_when:完成判据,避免「做了一半就算完成」;tool:预期使用的工具,便于提前做权限检查;assumptions:假设条件,重规划时需要重新审视。
分解到什么粒度
太粗:一步里包含多个不确定动作,失败无法定位。 太细:步骤爆炸,协调成本超过收益。
一个实用标准:每个步骤应该有一个可验证的完成状态。
- 「调研市场」太粗,无法判断完成;
- 「打开第 3 页」太细,没有决策价值;
- 「收集三家同行 2025 年毛利率,并记录来源」合适。
另一个标准是单一责任:一步只产出一类结果。如果一步既检索又计算又写作,失败时你不知道是哪一环。
Plan-and-Execute 循环
伪代码:
def plan_and_execute(goal, max_replans=2):
plan = planner(goal)
state = {"goal": goal, "facts": [], "artifacts": {}}
for attempt in range(max_replans + 1):
for step in ready_steps(plan, state):
result = executor(step, state)
state = merge(state, step, result)
if not verify(step, result, state):
plan = replanner(goal, plan, state, failed=step)
break
else:
if all_done(plan, state) and verify_goal(goal, state):
return finalize(state)
plan = replanner(goal, plan, state, failed=None)
return escalate_to_human(state)注意:
ready_steps按依赖调度,独立步骤可并行;- 每次执行后都更新 state,而不是只追加消息;
- 验证失败触发重规划,而不是盲目重试;
- 重规划有次数上限,避免无限循环。
什么时候重规划
不是每次失败都值得重规划。触发条件应该是:
- 关键假设被推翻:文件不是 10-K 而是新闻稿;
- 工具持续失败:换路径,而不是重试同一个;
- 出现新信息:发现还有子公司需要合并报表;
- 完成判据无法满足:数据缺失,需要换指标;
- 预算/时间接近上限:缩减范围。
不该触发的:
- 单次超时(先重试);
- 格式错误(先修复);
- 模型「觉得」计划不够好(容易陷入无限优化)。
计划的验证
计划质量可以从四个维度检查:
| 维度 | 问题 | 检查方式 |
|---|---|---|
| 完整性 | 是否覆盖目标的所有必要条件? | 人工或 LLM 对照目标清单 |
| 可执行性 | 每步是否有明确工具与输入? | Schema 校验、工具存在性检查 |
| 依赖正确 | 是否有循环依赖或缺失前置? | 拓扑排序 |
| 可验证 | 每步是否有完成判据? | 检查 done_when 非空且可观察 |
一个低成本的自动检查:
def validate_plan(plan, available_tools):
ids = {s["id"] for s in plan["steps"]}
for s in plan["steps"]:
assert s["id"] in ids
assert set(s["depends_on"]) <= ids
assert s["tool"] in available_tools
assert s["done_when"].strip()
assert not has_cycle(plan) # 拓扑排序检测如果计划里引用了不存在的工具,直接让规划器重写,而不是在执行时才发现。
规划提示的写法
你是任务规划器。根据目标生成一份可执行计划。
要求:
1. 每个步骤只做一件事,且有可观察的完成判据。
2. 标明依赖关系;无依赖的步骤应可并行。
3. 只使用给定工具;不要发明工具。
4. 步骤数控制在 3–8 个;超过 8 个时先合并相似步骤。
5. 如果目标本身不清晰,先输出需要澄清的问题,不要硬编计划。
6. 输出 JSON,符合给定 Schema。
目标:{goal}
可用工具:{tools}
已知信息:{context}注意最后一条:允许规划器说「需要澄清」。很多错误计划的根源是目标本身模糊,而模型不敢问。
常见失败模式
把「查天气」拆成五步:识别城市、调用 API、解析、格式化、回复。步骤越多,失败点越多。简单任务直接做。
执行中发现数据不存在,仍然按原计划走,最后编造结果。必须在关键步骤后检查假设。
模型发明了 get_sec_filing 这种不存在的工具,执行时才报错。规划后先做工具存在性与依赖校验。
每步都「完成」了,但最终结果不对。done_when 必须是可观察的,最好由代码或独立评审检查。
每次小失败都重写整份计划,成本和延迟爆炸。限制重规划次数,并优先局部替换失败的步骤。
与 ReAct 的取舍
| 维度 | ReAct | Plan-and-Execute |
|---|---|---|
| 全局视野 | 弱 | 强 |
| 适应性 | 强,随时调整 | 需要重规划 |
| 成本 | 低–中 | 中–高(规划 + 执行) |
| 可审查性 | 低 | 高 |
| 适合 | 探索、短任务 | 多步骤、可分解、需审批 |
一个混合方案:先做轻量规划(3–5 步),执行时用 ReAct 处理每步内部的探索。这样既有全局方向,又保留局部灵活性。
小结
- 显式计划让 Agent 可审查、可调度、可恢复,但会增加成本。
- 好的步骤有单一责任、明确工具、可观察的完成判据。
- 用依赖图调度,独立步骤可并行。
- 验证失败才重规划,且要有次数上限。
- 规划器必须被允许说「目标不清晰」。
下一篇讲如何让 Agent 检查自己的输出——以及为什么大多数「自我反思」其实没用。