Skip to content

第27章:为什么智能体要先列清单再干活 | 从零理解如何构建 AI Agent

把实践里学到的 ReAct 循环提升到计划与重规划视角,作为进入中级工程专题的桥接节点。

对应路径packages/opencode/src/agent/agent.tspackages/opencode/src/session/processor.tspackages/opencode/src/tool/task.tsdocs/intermediate/examples/27-planning-mechanism/

这篇解决什么问题

当任务跨越多个子目标时,单纯“边想边做”很容易失控。这一章要帮你判断:

  • 什么时候值得先花一轮模型调用做计划
  • 计划执行到一半变得不准时,系统该怎么纠偏

Planning 任务挑战

打造支持上传、搜索和权限控制的文档助手

在一个团队内部快速落地一个文档助手,支撑文件上传、智能搜索、访问权限与审计,并可持续迭代。

确定文档助手愿景目标阶段

任务树

目标阶段

当前推进 阻塞路径 已完成 待执行

  • 文档助手目标进行中
  • 上传主线待处理

    来源节点:文档助手目标

  • 搜索主线待处理

    来源节点:文档助手目标

  • 权限主线待处理

    来源节点:文档助手目标

把刚才的体验映射回 Planning 机制

刚才的模拟器把 Planning 的三件事做成了可感知的差异:

  • ReAct:每一步现场决定下一步,靠工作记忆维持方向。
  • Plan-and-Execute:先生成结构化步骤,再逐步执行。
  • Adaptive Planning:执行中允许重算剩余计划,避免沿错误路径走到底。

你看到的其实是一次从“体验”到“命名”的过程:先让你感受到偏离与纠偏的代价,再回到工程机制解释为什么需要计划、何时需要重规划。

为什么真实系统里重要

Planning 看起来像在执行前多做一步,其实它解决的是复杂任务里的三个高频故障:

  • 遗漏:计划把未完成事项显式化,不再靠模型临场记忆。
  • 重复:已有步骤和已完成结果变成结构化状态,便于避免重复劳动。
  • 可审查:计划本身可以被观察、修改和重规划,而不是埋在模型隐式思考里。

因此它特别适合:

  • 结构化调研报告
  • 多步信息搜集与汇总
  • 需要中间产物串联的复杂执行链
  • 执行中可能因为外部信息变化而改路线的任务

核心概念与主链路

先把三种模式放在一条连续谱里看,便于你把模拟器里的体验落回工程设计:

text
ReAct
  -> 每一步现场决定下一步

Plan-and-Execute
  -> 先生成完整步骤,再逐步执行

Adaptive Planning
  -> 先有计划,但允许中途重算剩余步骤

27.1 ReAct 的问题不是不聪明,而是不保留全局结构

ReAct 在短任务上很高效,但它依赖临场记忆维持任务全景。任务目标一多,系统就容易在执行中丢掉“还有哪些事没做完”的全局结构。

27.2 Plan-and-Execute 的核心是把任务转成显式状态

示例里的 PLAN_PROMPT 非常直接:先让模型把任务拆成 JSON 步骤数组。

python
PLAN_PROMPT = """你是任务规划专家。把以下任务拆解成 3-8 个具体步骤。
每步标注工具:get_weather/calculate/search_news/get_exchange_rate/search_web/none
任务:{task}
输出 JSON 数组:[{{"step": 1, "action": "描述", "tool": "工具名"}}]
只输出 JSON,不要其他文字。"""

一旦计划被结构化,后面的执行就不再是“想到什么做什么”,而变成:

text
任务
  -> 生成计划
  -> 遍历计划
  -> 为每一步选择工具
  -> 保存步骤结果
  -> 最后统一汇总

这就是 Planning 最重要的工程价值:把隐式推理转成可检查的显式状态。

27.3 执行阶段要解决的是“步骤如何落地”

仅有计划还不够,执行阶段还要把“步骤描述”真正翻译成工具调用。示例里的 _execute_step() 就做了这件事:

python
def _execute_step(self, step: dict) -> str:
    action = step.get("action", "")
    tool_hint = step.get("tool", "")
    prompt = (
        f"步骤:{action}\n建议工具:{tool_hint}\n"
        f"可用:get_weather(city), calculate(expression), search_news(keyword), "
        f"get_exchange_rate(from_currency, to_currency), search_web(query)\n"
        f'输出 JSON:{{"tool": "名", "args": {{}}}}{{"tool": null}}'
    )
    ...

这段代码提醒你一个很关键的点:

  • Planning 不是把工具路由写死
  • Planning 是先用计划限制搜索空间,再让模型在小范围里做执行决策

也就是说,Planning 提供的是“宏观顺序”,不是“微观每一步都人工硬编码”。

27.4 自适应规划是在处理“计划赶不上变化”

一次性计划的最大弱点,是它默认世界不会在执行中变化。更高级的形态是:

text
执行一步
  -> 检查结果是否偏离预期
  -> 如有必要,重算剩余步骤
  -> 用新计划替换旧的 pending 部分

这就是自适应规划真正的价值:不是让计划更复杂,而是让计划可以被现实修正。

27.5 什么时候该用哪一种

不要一上来就上最重的方案。Planning 能提升成功率,但也一定增加调用次数、状态维护成本和调试复杂度。工程上更稳的判断是:

  • 短任务、信息清晰:ReAct
  • 多步骤、结构稳定:Plan-and-Execute
  • 目标可能变动、外部信息不稳定:Adaptive Planning

教学代码示例映射

下面这些都是教学示例,不是 OpenCode 原仓实现。 本章对应示例目录是 docs/intermediate/examples/27-planning-mechanism/,核心文件为 plan_and_execute.py

它主要分成两层:

  • PlanAndExecuteAgent:先做计划,再逐步执行
  • SmartAgent:根据任务复杂度在 reactplan_execute 之间自动路由

这份脚本接近页面可读性的上限,因此正文只保留关键片段。完整版本请直接看示例目录。

关键片段 1:Planner 负责把任务转成显式步骤

python
class PlanAndExecuteAgent:
    def __init__(self):
        self.plan: list[dict] = []
        self.results: dict = {}

    def _make_plan(self, task: str) -> list[dict]:
        resp = client.chat.completions.create(
            model=MODEL_NAME,
            messages=[{"role": "user", "content": PLAN_PROMPT.format(task=task)}]
        )
        content = resp.choices[0].message.content.strip()
        if "```" in content:
            content = content.split("```")[1].replace("json", "").strip()
        self.plan = json.loads(content)
        return self.plan

关键片段 2:执行器逐步推进并累积结果

python
for step in self.plan:
    s_num = step.get("step", "?")
    result = self._execute_step(step)
    self.results[s_num] = result
    print(f"  Step {s_num}: {result[:80]}{'...' if len(result) > 80 else ''}")

关键片段 3:路由器根据复杂度决定是否值得先做计划

python
class SmartAgent:
    def _select_mode(self, task: str) -> str:
        resp = client.chat.completions.create(
            model=MODEL_NAME,
            messages=[{"role": "user", "content": f"评估任务复杂度(1-10...)。只输出数字。\n任务:{task}"}]
        )
        try:
            score = int(resp.choices[0].message.content.strip())
            return "react" if score <= 3 else "plan_execute"
        except:
            return "plan_execute"

这三个片段足够你抓住 Planning 示例的骨架:

text
先决定要不要规划
  -> 规划后把步骤结构化
  -> 按步骤执行
  -> 收集中间结果
  -> 最后统一汇总

常见误区

误区1:Planning 一定比 ReAct 高级,所以复杂任务都该先上 Planning

错误理解:只要任务稍微复杂一点,就默认先做完整规划。

实际情况:Planning 有明确成本,包括额外的模型调用、计划解析、状态维护和重规划逻辑。短任务上这些成本往往高于收益。

误区2:计划越细越好

错误理解:把 5 步能做完的事拆成 20 步,会让系统更稳。

实际情况:过度规划会让系统花大量 token 在维护计划本身,执行灵活性反而下降。好的计划粒度,应该让每一步都是“可执行且有单一目标”,而不是“无限细分”。

误区3:有了计划,执行阶段就不需要灵活性

错误理解:计划一旦生成,就应该严格照着走,不能动。

实际情况:计划只是当前信息下的最优猜测。只要外部事实变化、工具返回异常、查询结果不足,系统就必须有能力修正剩余步骤,否则会稳定地走向错误答案。

误区4:Planning 只是一个 Prompt 技巧

错误理解:让模型“先列步骤再回答”,就等于实现了 Planning。

实际情况:真正的 Planning 还包括结构化解析、步骤状态、执行器、结果汇总,甚至重规划策略。没有这些配套,Planning 只是一段好看的提纲,不是工程能力。

读完这一章,下一步做什么

  • /intermediate/

    继续沿学习路径进入下一章或对应入口。

  • P10 ReAct Loop

    实践篇 · 把最小 Agent 升级成显式推理链,让你能看见每一步为什么行动,并为后续 Planning 主题搭桥。

本章行动任务

  • 回到 P10 对照 ReAct Loop

    把这一章讨论的 Planning 与实践里的 ReAct Loop 放在一起比较,观察“边想边做”和“先列计划”在复杂任务中的差异。

  • 再看 P11 的 Planning 实践

    用实践项目验证计划生成、执行和状态显式化如何改变任务完成率与可观察性。

  • 继续进入上下文工程专题

    当你理解了 Planning,再往下看上下文工程如何为计划执行提供稳定上下文预算。

延伸阅读与回链