AgentDepthAgent 工程学习指南
输入概念或问题,检索正文和待编写目录。
第一次学习?从 L0 总览开始 →

Plan-and-Execute:先列步骤,再根据结果修订

我的学习 →

正在读取学习记录…

1. 计划由哪些东西组成

Plan-and-Execute 可以读成“先规划,再执行”。规划把目标拆成若干步骤,执行器按步骤做事,并记录每一步的结果。一个清楚的计划要回答三件事:这一步做什么、需要哪些前置结果、完成后交出什么。

例如客服回答“这笔订单能退多少钱”,先读取订单金额,再按规则计算退款,最后组织答复。依赖指某一步必须先取得另一部分的结果:金额尚未查到,计算就缺输入。这和做饭时“买菜→洗菜→炒菜”有相似之处,步骤清单帮你安排工作,冰箱里实际有什么,还得打开看。

读取订单或支付记录,取得金额,再计算退款,最后生成答复
读取入口可以根据资料可用性调整,计算与答复的依赖保持一致。

计划和执行记录适合分别保存。计划写“准备读取订单”,记录写“订单服务返回金额100元”。前者安排后续工作,后者确认已经取得的事实。模型提出的计划可由程序检查工具、参数和依赖,工具返回的数据决定后续是否执行得下去。工程里的计划式执行还常包括执行后的重新规划。Plan-and-Solve 论文可帮助理解先分解再求解的提示方式。

2. 运行一个能调整入口的计划

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l3-planning" -Force
cd "F:\学习\l3-planning"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

在新文件夹创建 planning.py,写入完整代码:

先在 PowerShell 创建并进入学习目录:

Bash
mkdir "F:\学习\l3-planning" -Force
cd "F:\学习\l3-planning"

保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。

Python
"""把目标拆成带依赖的步骤,并根据资料可用性调整计划。"""
import json

def make_plan(order_available=True):
    # 先拿到证据,后续计算才有输入;资料缺失时换一个读取入口。
    first = "读取订单" if order_available else "查询支付记录"
    return [
        {"id": "evidence", "label": first, "needs": []},
        {"id": "refund", "label": "计算退款", "needs": ["evidence"]},
        {"id": "answer", "label": "生成答复", "needs": ["refund"]},
    ]

def execute(plan):
    completed = set()
    actions = []
    for step in plan:
        # needs 保存前置步骤的编号,确保按依赖顺序执行。
        if not set(step["needs"]) <= completed:
            raise ValueError("前置步骤尚未完成")
        actions.append(step["label"])
        completed.add(step["id"])
    return actions

if __name__ == "__main__":
    for available in (True, False):
        print(json.dumps({"order_available": available,
                          "steps": execute(make_plan(available))}, ensure_ascii=False))
Bash
ls
python planning.py

输出:

TXT
{"order_available": true, "steps": ["读取订单", "计算退款", "生成答复"]}
{"order_available": false, "steps": ["查询支付记录", "计算退款", "生成答复"]}

第一行表示订单服务可用,第二行换成支付记录。make_plan 返回列表,每个字典就是一个步骤。id 是程序使用的编号,label 是动作名称,needs 是前置步骤编号。refund 依赖 evidence,所以两种入口都先取得证据,再计算。

execute 用集合 completed 保存已完成编号。它先检查依赖,再记录当前步骤。示例把执行表现为动作名称的列表;接入真实工具时,应在取得合格结果后再加入 completed。查询返回“订单不存在”,就要补资料或停止,而非直接进入计算。

3. 改一项,看依赖如何影响结果

先把主程序的 (True, False) 改成 (False,) 再运行,只保留从支付记录开始的那行输出。接着恢复循环,删掉计划列表中第一项。此时计算仍依赖 evidence,运行会出现“前置步骤尚未完成”。恢复第一项后再继续。

这个实验把“换入口”和“跳过必要输入”区分开了:前者保留依赖,后者打断依赖。

4. 保留检查用例

同一目录创建 test_planning.py:

下面的测试检查步骤依赖,以及已知订单资料不可用时选择支付记录的计划。make_plan(False) 在执行前选择入口,execute 检查顺序并记录步骤名称。

测试代码:test_planning.py
Python
"""检查planning示例的正常路径和边界结果。"""
import unittest
from planning import make_plan, execute
class Tests(unittest.TestCase):
    def test_original(self):
        self.assertEqual(execute(make_plan()), ["读取订单", "计算退款", "生成答复"])
    def test_revision(self):
        self.assertEqual(execute(make_plan(False)), ["查询支付记录", "计算退款", "生成答复"])
    def test_missing_dependency(self):
        with self.assertRaises(ValueError): execute([{"id":"answer","label":"生成答复","needs":["refund"]}])

if __name__ == "__main__":
    unittest.main()
Bash
python -m unittest test_planning -v

测试同时检查正常路径和改变条件后的结果。遇到导入错误时,先核对两个文件是否在同一目录;遇到断言失败时,比较预期值和实际值,再回到相应分支。

5. 何时修订,怎样保留已有工作

执行中新资料可能改变计划。订单接口暂时不可用,可以改查支付记录;已经发现部分商品退过款,要调整计算规则;答复模板失败,可以换模板并保留已算出的金额。

当前结果修订动作保留的工作
订单接口不可用查询支付记录用户问题与订单编号
已查到部分退款调整计算条件订单金额和退款记录
已算出金额,答复失败重新组织语言金额、规则和来源
时间不足以查新来源结束并列出待确认项已经查到的资料

修订从当前结果出发,可以减少重复工作。如果已经发送退款请求,应先查回执,确认这次写入的结果,再决定下一动作。计划版本也值得保存:版本1读订单,版本2读支付记录,让运行路径的变化有迹可循。

目标能拆成有明确依赖的子任务时,计划式执行方便检查遗漏、时间和权限。若每一步都要根据刚发现的信息临时选择工具,ReAct的反馈循环更自然。规划越细,维护成本越高;先划分阶段、进入当前阶段后再补细节,常比预先列十几条未知步骤更实用。

把步骤清单连接到真正的工具结果

现在沿一次退款任务手算输入和输出。订单查询取得订单金额100元,退款规则允许全额退回,计算器得到100元,答复器引用这两个结果。每个步骤的产物,都成为下一步的输入。

步骤开始时需要执行后保存
读取订单订单编号金额100元与订单来源
读取退款规则商品类别与购买时间允许退款条件与政策来源
计算退款金额与规则退款金额100元
生成答复金额、规则和来源用户可读的答复

例子中的三步计划把规则当作已知条件。扩展为四步时,可让“计算退款”的needs同时包含订单与规则编号。集合比较会要求两项都完成,所以只查到金额还不足以计算。

接入工具时,可以让步骤返回字典,比如状态、数据、来源,再由执行器决定是否把编号加入completed。成功取得数据时更新,查询空时保存待补项,服务失败时保留错误类别。这比仅凭动作名称推进更贴近实际业务。

把金额真正传给下一步

在同一目录再保存 plan_results.py。先看正常路线:查询给出 amount=100 与 source='订单记录';计算读这个金额;答复保留它的来源。第二条路线在读取订单时抛出连接错误,执行器才改查支付记录。

Python
"""让查询结果进入计算步骤,并在查询失败后切换资料入口。"""

def run_plan(order_fails=False, amount=100):
    # 固定两份查询结果,便于观察金额和来源怎样传给后续步骤。
    order = {"amount": amount, "source": "订单记录"}
    payment = {"amount": amount, "source": "支付记录"}
    trace = []
    try:
        if order_fails:
            raise ConnectionError("订单服务暂时不可用")
        evidence = order
        trace.append("读取订单")
    except ConnectionError:
        # 真正遇到读取失败后才修订入口,后面的计算步骤保持不变。
        trace.extend(["读取订单失败", "改查支付记录"])
        evidence = payment

    # 这套固定规则允许全额退回;计算使用刚取得的金额。
    refund = evidence["amount"]
    trace.append("计算退款")
    answer = f"可退{refund}元,依据:{evidence['source']}"
    trace.append("生成答复")
    return trace, answer


if __name__ == "__main__":
    for label, fails in (("正常", False), ("读取失败", True)):
        trace, answer = run_plan(fails)
        print(f"{label}:{' → '.join(trace)};{answer}")

运行:

Bash
python -X utf8 plan_results.py

输出:

TXT
正常:读取订单 → 计算退款 → 生成答复;可退100元,依据:订单记录
读取失败:读取订单失败 → 改查支付记录 → 计算退款 → 生成答复;可退100元,依据:支付记录

evidence 保存实际取得的数据,refund 读取其中的金额,answer 组合金额与来源。trace 另记执行路线。将函数的 amount=100 改为 amount=80,保存后运行 python -X utf8 plan_results.py,两行答复都变为可退80元;金额来自查询结果,路线仍按订单服务是否失败选择。恢复100再运行,对照原来的两行。

修订还需要记录版本。版本1打算读取订单;订单接口暂时不可用,版本2换支付记录;已经完成的政策查询可以保留。修订器接收原计划、已完成结果和新观察,输出剩余步骤,避免重新执行已确认的工作。

回顾与下一步

目标说明要完成什么,计划说明准备怎么做,依赖限制执行顺序,观察推动修订。下一篇 结果复核检查做出的答复是否符合资料。

课后练习展开习题收起习题

4 道练习 · 客观题规则评分 · 其余题目待批改

正在读取本地记录…

不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。

练习 1 / 4 · v1.0.1

选择题 · 先判断边界

关于“Plan-and-Execute 与计划修订”,哪种做法更稳妥?

对应知识点:L3-planning

选择一个答案
练习 2 / 4 · v1.0.0

简答题 · 解释失败

写出一个失败输入并说明记录字段。

对应知识点:L3-planning

练习 3 / 4 · v1.0.0

代码题 · 边界测试

补充正常、边界、失败三组输入和预期结果。

对应知识点:L3-planning

练习 4 / 4 · v1.0.0

场景题 · 结果未知

证据不足时如何设计状态和人工接管?

对应知识点:L3-planning

图片查看器
100%

文章配图 · 按 Esc 关闭