跳转至

Plan & Solve:先列计划,再逐步执行

有些任务失败不是因为模型做不了某一步,而是因为它回答太快。

旅游助手接到"杭州三天两夜"的请求,直接开写行程——漏掉天气、忽略步行距离、忘记打包建议。问题不是能力不够,而是没有先想清楚要做哪几件事。

Plan & Solve 加了一个最简单的暂停:先写计划,再按计划逐步执行。

一句话说清楚

Plan & Solve 把"直接回答"改成"先列步骤、再逐步完成",让多步任务有一个可检查的骨架。

解决什么问题

问题 表面看起来 实际风险
直接给答案 容易跳步骤
边想边写 自然 任务拆解过程不可见
没有计划产物 简洁 约束条件难以检查

旅游助手一口气写行程,看着完整,但是"下午下雨"这个约束从来没出现在它的推理里。你无法在 trace 里找到它到底想过没有。

引入什么复杂度

  • 多了一次模型调用(生成计划)。
  • 计划太长会变成自己的管理难题。
  • 计划写完就固定了——执行过程中发现天气变了,计划不会跟着改。

和其他模式的关系

模式 谁决定下一步 什么时候用
Prompt Chaining Python 写死步骤 流程已知
Plan & Solve 模型先写计划 任务需要拆解,但计划不会中途失效
PER Replanner 可以改计划 计划可能过时
LATS 搜索控制器比较候选 一个计划不稳定

Plan & Solve 是最轻量的规划。比 Prompt Chaining 灵活(步骤由模型写),比 PER 简单(没有重规划)。

这个模式改变了什么

负责什么
Planner(模型) 接到任务后生成步骤列表
Solver(模型) 按步骤逐个执行
Python 存计划、依次调用步骤、记 trace
  • 谁决定下一步:计划决定。Python 按顺序取下一步。
  • 谁拥有状态:Python 持有计划列表和已完成的步骤输出。
  • 谁能调用工具:Solver 在执行步骤时可调用工具。
  • 谁给最终答案:Solver 在最后一步综合输出。
  • 什么时候停止:所有步骤执行完毕。
  • trace 记录什么plan_and_solve.plan(步骤数)、plan_and_solve.step(每步 index 和内容)、plan_and_solve.done

走一遍真实轨迹

用户请求:"杭州三天两夜,预算 3000,不想走太多路。"

阶段 输出 为什么到这一步
生成计划 ["提取偏好","查天气","设计路线","打包建议"] 模型先列出子任务
执行步骤 1 "偏好:预算 3000,少步行,三天两夜" 把约束提取为结构化信息
执行步骤 2 "第二天下午有雨" 查到天气
执行步骤 3 "Day1 西湖游船→灵隐寺(打车);Day2 上午茶博物馆、下午室内宋城..." 已知下雨,安排室内
执行步骤 4 "带伞、舒适鞋" 基于路线给打包建议
综合 完整行程 + 预算明细 + 打包清单 所有步骤输出拼合

关键:步骤 3 能看到步骤 2 的天气结果——但如果计划本身没列"查天气",这条信息就不会出现。

流程图

flowchart TD
  T["用户任务"] --> P["模型生成计划"]
  P --> S1["执行步骤 1"]
  S1 --> S2["执行步骤 2"]
  S2 --> S3["执行步骤 N"]
  S3 --> F["综合最终答案"]

完整实现

下面是一个完整的旅游助手 Plan & Solve 示例。用 MockLLM 模拟模型,方便离线运行。

from __future__ import annotations
from pathlib import Path
from agent_patterns_lab.patterns.plan_and_solve import plan_and_solve
from agent_patterns_lab.runtime import MockLLM, Tracer


def main() -> None:
    tracer = Tracer()

    # 模型会依次返回:计划 JSON、每步输出、最终综合
    model = MockLLM(
        [
            # 第一次调用:返回计划
            '{"plan":["提取用户偏好","查询杭州天气","设计三天路线","给出打包建议"]}',
            # 步骤 1:提取偏好
            "偏好:预算 3000 元,少步行,三天两夜,杭州。",
            # 步骤 2:查天气
            "杭州未来三天:Day1 晴 28°C,Day2 下午雷阵雨 25°C,Day3 多云 27°C。",
            # 步骤 3:设计路线
            (
                "Day1:西湖游船(上午)→ 灵隐寺(下午,打车前往)→ 河坊街晚餐\n"
                "Day2:中国茶叶博物馆(上午)→ 宋城室内演出(下午,避雨)→ 酒店附近晚餐\n"
                "Day3:良渚博物院(上午)→ 返程"
            ),
            # 步骤 4:打包建议
            "必带:折叠伞、舒适运动鞋、充电宝。",
            # 最终综合
            (
                "【杭州三天两夜行程】\n"
                "预算:约 2800 元(住宿 1200 + 餐饮 600 + 交通 400 + 门票 600)\n"
                "Day1:西湖游船 → 灵隐寺 → 河坊街\n"
                "Day2:茶博物馆 → 宋城(室内,避雨)\n"
                "Day3:良渚博物院 → 返程\n"
                "打包:折叠伞、运动鞋、充电宝"
            ),
        ]
    )

    result = plan_and_solve(model, task="杭州三天两夜,预算 3000,不想走太多路。", tracer=tracer)
    print("=== 计划 ===")
    for i, step in enumerate(result.plan, 1):
        print(f"  {i}. {step}")
    print("\n=== 最终答案 ===")
    print(result.answer)

    trace_path = tracer.export_jsonl(Path(".traces") / "50_plan_and_solve_zh.jsonl")
    print(f"\n[trace] {trace_path}")


if __name__ == "__main__":
    main()

运行:

PYTHONPATH=src python examples/50_plan_and_solve.py

完整运行记录

=== 计划 ===
  1. 提取用户偏好
  2. 查询杭州天气
  3. 设计三天路线
  4. 给出打包建议

=== 最终答案 ===
【杭州三天两夜行程】
预算:约 2800 元(住宿 1200 + 餐饮 600 + 交通 400 + 门票 600)
Day1:西湖游船 → 灵隐寺 → 河坊街
Day2:茶博物馆 → 宋城(室内,避雨)
Day3:良渚博物院 → 返程
打包:折叠伞、运动鞋、充电宝

[trace] .traces/50_plan_and_solve_zh.jsonl

trace 文件中可以看到:

{"event":"plan_and_solve.plan","steps":4}
{"event":"plan_and_solve.step","step_index":0,"step":"提取用户偏好"}
{"event":"plan_and_solve.step","step_index":1,"step":"查询杭州天气"}
{"event":"plan_and_solve.step","step_index":2,"step":"设计三天路线"}
{"event":"plan_and_solve.step","step_index":3,"step":"给出打包建议"}
{"event":"plan_and_solve.done"}

踩坑与诊断

现象 trace 信号 修法
计划太长 10+ 步骤,后面几步变成"总结""检查""再检查" steps 数远大于预期 max_plan_steps 限制在 4-6
计划太模糊 步骤是"深入分析""仔细思考" 步骤输出和前一步几乎一样 system prompt 要求每步写明具体动作和产出
执行跑偏 Solver 忽略计划,自己发挥 步骤输出和计划内容无关 每步 prompt 带上 step_id 和计划原文
计划过时 第二步发现暴雨,第三步还安排户外 最终答案包含矛盾信息 升级到 PER(加重规划)

关键诊断技巧:检查每一步的 prompt 是否包含了前面步骤的输出。如果步骤 3 的 prompt 里没有步骤 2 的天气结果,问题在 _step_prompt 的拼接逻辑。

工程备忘

成本公式

总调用 = 1(计划) + N(步骤) + 1(综合) = N + 2

4 步计划 = 6 次模型调用。每加一步,多一次调用。

生产注意事项

  • max_plan_steps 建议 4-6。超过 6 步的计划通常质量下降。
  • 计划是 JSON 结构化输出,走 structured_complete,有校验和重试。
  • 步骤执行是普通 model.complete,没有结构化校验——如果你需要中间步骤也返回 JSON,需要自己加 parser。
  • Plan & Solve 是一次性计划。如果你在生产中发现"计划写完就不对了"的频率超过 20%,不要在这个模式上修补,直接换 PER。

读完以后

Plan & Solve 是最轻量的规划模式。

  • 如果执行过程会让计划失效,读 PER
  • 如果需要在多个计划之间搜索,读 LATS
  • 如果计划里的步骤之间有依赖和并行关系,读 LLM Compiler

参考资料