Plan-and-Execute:先列步骤,再根据结果修订
正在读取学习记录…
1. 计划由哪些东西组成
Plan-and-Execute 可以读成“先规划,再执行”。规划把目标拆成若干步骤,执行器按步骤做事,并记录每一步的结果。一个清楚的计划要回答三件事:这一步做什么、需要哪些前置结果、完成后交出什么。
例如客服回答“这笔订单能退多少钱”,先读取订单金额,再按规则计算退款,最后组织答复。依赖指某一步必须先取得另一部分的结果:金额尚未查到,计算就缺输入。这和做饭时“买菜→洗菜→炒菜”有相似之处,步骤清单帮你安排工作,冰箱里实际有什么,还得打开看。
计划和执行记录适合分别保存。计划写“准备读取订单”,记录写“订单服务返回金额100元”。前者安排后续工作,后者确认已经取得的事实。模型提出的计划可由程序检查工具、参数和依赖,工具返回的数据决定后续是否执行得下去。工程里的计划式执行还常包括执行后的重新规划。Plan-and-Solve 论文可帮助理解先分解再求解的提示方式。
2. 运行一个能调整入口的计划
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l3-planning" -Force
cd "F:\学习\l3-planning"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
在新文件夹创建 planning.py,写入完整代码:
先在 PowerShell 创建并进入学习目录:
mkdir "F:\学习\l3-planning" -Force
cd "F:\学习\l3-planning"保存下面的文件后,在这个终端运行 dir 核对文件名,再执行本节给出的运行命令。
"""把目标拆成带依赖的步骤,并根据资料可用性调整计划。"""
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))ls
python planning.py输出:
{"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
"""检查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()python -m unittest test_planning -v测试同时检查正常路径和改变条件后的结果。遇到导入错误时,先核对两个文件是否在同一目录;遇到断言失败时,比较预期值和实际值,再回到相应分支。
5. 何时修订,怎样保留已有工作
执行中新资料可能改变计划。订单接口暂时不可用,可以改查支付记录;已经发现部分商品退过款,要调整计算规则;答复模板失败,可以换模板并保留已算出的金额。
| 当前结果 | 修订动作 | 保留的工作 |
|---|---|---|
| 订单接口不可用 | 查询支付记录 | 用户问题与订单编号 |
| 已查到部分退款 | 调整计算条件 | 订单金额和退款记录 |
| 已算出金额,答复失败 | 重新组织语言 | 金额、规则和来源 |
| 时间不足以查新来源 | 结束并列出待确认项 | 已经查到的资料 |
修订从当前结果出发,可以减少重复工作。如果已经发送退款请求,应先查回执,确认这次写入的结果,再决定下一动作。计划版本也值得保存:版本1读订单,版本2读支付记录,让运行路径的变化有迹可循。
目标能拆成有明确依赖的子任务时,计划式执行方便检查遗漏、时间和权限。若每一步都要根据刚发现的信息临时选择工具,ReAct的反馈循环更自然。规划越细,维护成本越高;先划分阶段、进入当前阶段后再补细节,常比预先列十几条未知步骤更实用。
把步骤清单连接到真正的工具结果
现在沿一次退款任务手算输入和输出。订单查询取得订单金额100元,退款规则允许全额退回,计算器得到100元,答复器引用这两个结果。每个步骤的产物,都成为下一步的输入。
| 步骤 | 开始时需要 | 执行后保存 |
|---|---|---|
| 读取订单 | 订单编号 | 金额100元与订单来源 |
| 读取退款规则 | 商品类别与购买时间 | 允许退款条件与政策来源 |
| 计算退款 | 金额与规则 | 退款金额100元 |
| 生成答复 | 金额、规则和来源 | 用户可读的答复 |
例子中的三步计划把规则当作已知条件。扩展为四步时,可让“计算退款”的needs同时包含订单与规则编号。集合比较会要求两项都完成,所以只查到金额还不足以计算。
接入工具时,可以让步骤返回字典,比如状态、数据、来源,再由执行器决定是否把编号加入completed。成功取得数据时更新,查询空时保存待补项,服务失败时保留错误类别。这比仅凭动作名称推进更贴近实际业务。
把金额真正传给下一步
在同一目录再保存 plan_results.py。先看正常路线:查询给出 amount=100 与 source='订单记录';计算读这个金额;答复保留它的来源。第二条路线在读取订单时抛出连接错误,执行器才改查支付记录。
"""让查询结果进入计算步骤,并在查询失败后切换资料入口。"""
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}")运行:
python -X utf8 plan_results.py输出:
正常:读取订单 → 计算退款 → 生成答复;可退100元,依据:订单记录
读取失败:读取订单失败 → 改查支付记录 → 计算退款 → 生成答复;可退100元,依据:支付记录evidence 保存实际取得的数据,refund 读取其中的金额,answer 组合金额与来源。trace 另记执行路线。将函数的 amount=100 改为 amount=80,保存后运行 python -X utf8 plan_results.py,两行答复都变为可退80元;金额来自查询结果,路线仍按订单服务是否失败选择。恢复100再运行,对照原来的两行。
修订还需要记录版本。版本1打算读取订单;订单接口暂时不可用,版本2换支付记录;已经完成的政策查询可以保留。修订器接收原计划、已完成结果和新观察,输出剩余步骤,避免重新执行已确认的工作。
回顾与下一步
目标说明要完成什么,计划说明准备怎么做,依赖限制执行顺序,观察推动修订。下一篇 结果复核检查做出的答复是否符合资料。
课后练习展开习题收起习题
正在读取本地记录…
不调用模型,不执行代码。简答、代码和场景题仅保存答案并展示自查标准,不生成评分、徽章或认证。输入内容不会自动保存,请点击提交;不要填写个人信息或密钥。
选择题 · 先判断边界
关于“Plan-and-Execute 与计划修订”,哪种做法更稳妥?
对应知识点:L3-planning
简答题 · 解释失败
写出一个失败输入并说明记录字段。
对应知识点:L3-planning
代码题 · 边界测试
补充正常、边界、失败三组输入和预期结果。
对应知识点:L3-planning
场景题 · 结果未知
证据不足时如何设计状态和人工接管?
对应知识点:L3-planning
描述会发送给本站维护者,用于修订文章,保存90天。请只填写文章问题,省略个人信息、密钥和内部资料。