跳转至

PER:计划 + 执行 + 显式重规划

旅游助手做了一份计划:上午西湖、下午茶博物馆、晚上河坊街。然后天气变了,或者茶博物馆临时闭馆。Plan & Solve 写完计划就不管了——它会继续执行一份已经过时的计划。自由 Agent Loop 可以调整,但容易跑偏、没有计划约束。

PER 坐在两者之间:计划、执行一步、然后决定——继续、修改计划、还是直接结束。

一句话说清楚

PER 把"计划一次执行到底"改成"计划 → 执行一步 → 检查/重规划",让长任务可以中途修正方向。

解决什么问题

问题 表面看起来 实际风险
一份计划执行到底 有条理 过时计划继续跑
每一步都自由选择 灵活 目标漂移、成本膨胀
没有重规划节点 简单 失败会一路传播

引入什么复杂度

  • 三个模型角色(Planner、Executor、Replanner),调用次数更多。
  • Replanner 需要明确的判断标准——"什么算过时"不好定义。
  • 频繁重规划可能导致每步都重写,反而不如 Plan & Solve。

和其他模式的关系

模式 谁决定下一步 什么时候用
Plan & Solve 按计划顺序执行 计划不太可能失效
PER Replanner 每步检查 执行可能让计划失效
ReAct 模型每步选动作 不需要显式长计划
LLM Compiler 计划变成依赖图 任务可以并行化

PER = Plan & Solve + 重规划能力。比 Plan & Solve 重(多了 replanner),比 ReAct 有结构(始终有计划在手)。

这个模式改变了什么

负责什么
Planner(模型) 生成初始计划
Executor(模型) 执行当前步骤
Replanner(模型) 看执行结果,决定 continue / replan / final
Python 存计划、限步数、调度三个角色、记 trace
  • 谁决定下一步:Replanner。它返回 continue(继续下一步)、replan(修改剩余计划)或 final(直接给最终答案)。
  • 谁拥有状态:Python 持有当前计划和所有历史执行结果。
  • 什么时候停止:Replanner 返回 final,或达到 max_steps

走一遍真实轨迹

用户请求:"杭州三天两夜,预算 3000,想去茶博物馆。"

轮次 当前步骤 执行结果 Replanner 决策 为什么
1 查天气 Day2 下午暴雨 continue 天气信息不改计划结构
2 安排 Day1 路线 西湖 + 灵隐寺 continue 正常
3 安排 Day2 路线 茶博物馆 + 户外徒步 replan 下午暴雨,户外不行
3' 安排 Day2 路线(重规划后) 茶博物馆 + 宋城室内 continue 换成室内活动
4 安排 Day3 + 综合 良渚博物院 + 返程 final 任务完成

关键:轮次 3 执行完发现计划里有户外徒步和暴雨冲突,Replanner 把剩余计划改成室内活动。Plan & Solve 不会做这件事。

流程图

flowchart TD
  T["用户任务"] --> P["Planner 生成计划"]
  P --> E["Executor 执行当前步骤"]
  E --> R["Replanner 检查结果"]
  R -->|continue| E
  R -->|replan| P2["更新剩余计划"]
  P2 --> E
  R -->|final| O["最终答案"]

完整实现

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


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

    planner = MockLLM(
        [
            '{"plan":["查杭州天气","安排 Day1 路线","安排 Day2 路线","安排 Day3 路线并综合"]}',
        ]
    )

    executor = MockLLM(
        [
            "杭州:Day1 晴,Day2 下午暴雨,Day3 多云。",
            "Day1:上午西湖游船,下午灵隐寺,晚上河坊街。",
            "Day2:上午茶博物馆,下午宋城室内演出(原计划户外,因暴雨调整)。",
            "Day3:上午良渚博物院,下午返程。预算约 2700 元。",
        ]
    )

    replanner = MockLLM(
        [
            # 查完天气 -> 继续
            '{"action":"continue","plan":[],"answer":""}',
            # Day1 -> 继续
            '{"action":"continue","plan":[],"answer":""}',
            # Day2 发现暴雨冲突 -> 重规划
            '{"action":"replan","plan":["安排 Day2(改室内)","安排 Day3 并综合"],"answer":""}',
            # Day2 改完 -> 继续
            '{"action":"continue","plan":[],"answer":""}',
            # Day3 -> 结束
            '{"action":"final","plan":[],"answer":"杭州三天行程:Day1 西湖+灵隐寺+河坊街;Day2 茶博物馆+宋城(避雨);Day3 良渚+返程。预算 2700 元。"}',
        ]
    )

    result = planner_executor_replanner(
        planner,
        executor,
        replanner,
        task="杭州三天两夜,预算 3000,想去茶博物馆。",
        limits=RunLimits(max_steps=8),
        tracer=tracer,
    )

    print(result.answer)
    trace_path = tracer.export_jsonl(Path(".traces") / "51_per_zh.jsonl")
    print(f"[trace] {trace_path}")


if __name__ == "__main__":
    main()

运行:

PYTHONPATH=src python examples/51_planner_executor_replanner.py

完整运行记录

杭州三天行程:Day1 西湖+灵隐寺+河坊街;Day2 茶博物馆+宋城(避雨);Day3 良渚+返程。预算 2700 元。
[trace] .traces/51_per_zh.jsonl

trace 中的事件序列:

planner_executor_replanner.plan  steps=4
planner_executor_replanner.execute  step="查杭州天气"
planner_executor_replanner.replan  action=continue
planner_executor_replanner.execute  step="安排 Day1 路线"
planner_executor_replanner.replan  action=continue
planner_executor_replanner.execute  step="安排 Day2 路线"
planner_executor_replanner.replan  action=replan  new_steps=2
planner_executor_replanner.execute  step="安排 Day2(改室内)"
planner_executor_replanner.replan  action=continue
planner_executor_replanner.execute  step="安排 Day3 并综合"
planner_executor_replanner.replan  action=final

注意 replan 事件——这是 Plan & Solve trace 里不会出现的。

踩坑与诊断

现象 trace 信号 修法
每步都重规划 Replanner 每轮返回 replan replan 事件占比 > 50% 给 Replanner 更明确的重规划条件;限制最大重规划次数
目标漂移 最终答案和原始请求不匹配 final 里的关键词和用户任务关键词交集小 Replanner prompt 始终带原始任务和约束
Replanner 不给理由 看 trace 不知道为什么重规划 replan 事件无额外字段 要求 Replanner 返回 {"action":"replan","reason":"...","plan":[...]}
无限循环 步数耗尽才停 max_steps 被触发 设合理的 max_steps(通常 2x 初始计划步数)

关键诊断技巧:如果 replan 频率超过 50%,先检查是不是初始计划太粗——更好的初始计划会减少重规划需求。

工程备忘

成本公式

总调用 = 1(初始计划) + N(执行) + N(重规划判断) + R(额外重规划执行)
       = 1 + 2N + R

4 步计划、1 次重规划 ≈ 10 次模型调用。比 Plan & Solve(6 次)贵约 60%。

生产注意事项

  • Replanner 的 continue/replan/final 是结构化输出,走校验和重试。
  • max_steps 建议设为初始计划步数的 2 倍。
  • 重规划次数也要限制(比如最多 3 次),否则 Replanner 会变成一个无限循环的 Agent Loop。
  • 如果你的任务基本不需要重规划(<10% 的情况触发 replan),直接用 Plan & Solve 更划算。

读完以后

PER 适合"计划有用但可能过时"的任务。

  • 如果计划基本不会变,退回 Plan & Solve
  • 如果任务可以变成依赖图,读 LLM Compiler
  • 如果需要在多个计划之间搜索,读 LATS

参考资料