1. Plan-and-Execute模式的核心价值
凌晨两点的实验室里,空调控制Agent反复失败的场景让我彻底理解了传统任务拆解的致命缺陷。大多数开发者容易陷入一个思维误区:认为只要把大任务拆解成小步骤,然后按顺序执行就能万事大吉。但现实世界充满变数——工具可能超时、网络可能抖动、用户可能中途改需求。这时候,Plan-and-Execute模式的价值就凸显出来了。
这个模式的本质是带状态反馈的闭环控制系统。就像老司机开车时会不断观察路况调整方向盘一样,一个健壮的Agent需要三种核心能力:
- 前瞻性规划:基于当前信息制定可行方案
- 适应性执行:在动态环境中落实具体操作
- 实时状态监控:感知环境变化并反馈给决策系统
1.1 与传统链式调用的区别
普通Chain-of-Thought(思维链)就像旅游时 rigidly following a printed itinerary,即使遇到景点关闭也机械地执行原计划。而Plan-and-Execute则是拿着智能手机的背包客,随时查看地图应用调整路线:
| 特性 | 链式调用 | Plan-and-Execute |
|---|---|---|
| 任务拆解 | 一次性生成所有步骤 | 初始规划+动态调整 |
| 状态感知 | 无 | 全流程状态跟踪 |
| 错误处理 | 通常直接失败 | 自动重试/重规划 |
| 执行灵活性 | 固定流程 | 可适应环境变化 |
1.2 典型应用场景
这种模式特别适合以下三类场景:
- 长周期操作:如智能家居控制"先开空调再调温度"这类有严格顺序要求的任务
- 易变环境:需要频繁调用外部API或受网络波动影响的操作
- 多约束条件:存在多种限制条件(如"不能连续调用付费API超过3次")的复杂任务
在我的智能家居控制案例中,正是因为加入了状态跟踪器,Agent才能发现"空调未响应温度设置"的异常,进而触发重规划机制——可能是先检查空调状态,或是通知用户设备离线。
2. 框架实现深度解析
2.1 核心模块设计
让我们拆解框架的每个组件,这些都是在实际项目中踩坑后优化的版本:
2.1.1 规划器(Planner)实现细节
规划器不是简单的prompt engineering,而是需要结构化输出控制。我的模板演进过三个版本:
初版(自由文本)
code复制请为以下任务生成步骤:
1. 第一步做什么
2. 第二步做什么
...
问题:后期解析复杂,且难以提取精确参数
改进版(带类型提示)
python复制"""
返回格式:
步骤1: <动作类型> <参数>
步骤2: <动作类型> <参数>
"""
问题:仍然存在格式不一致风险
当前生产版本(强制JSON Schema)
python复制plan_template = """
输出必须符合以下JSON Schema:
{
"steps": [
{
"action": "工具名",
"input": {"参数名":"值"},
"expected": "预期结果描述",
"retry_policy": {"max_attempts":3, "backoff_factor":1.5}
}
]
}
实际输出:
"""
关键改进:
- 使用JSON Schema进行强约束
- 每个步骤明确失败处理策略
- 输入参数结构化便于直接调用
经验:在prompt中明确给出Schema示例比单纯描述格式要求有效10倍。LLM会严格遵循示例结构。
2.1.2 执行器(Executor)安全封装
执行器是与现实世界交互的边界,需要特别健壮。我的安全封装包含五层防护:
-
超时控制:任何工具调用必须设置timeout
python复制with ThreadPoolExecutor() as executor: future = executor.submit(tool.execute, params) try: return future.result(timeout=5) except TimeoutError: log("Execution timeout") raise -
熔断机制:连续失败时暂时禁用工具
python复制if tool_failure_count > 3: disable_tool(tool_name, cooldown=300) -
结果验证:检查返回值的结构和范围
python复制def validate_temp_response(resp): if not isinstance(resp, dict): raise ValueError("Invalid response type") if not 16 <= resp["temperature"] <= 30: raise ValueError("Temp out of range") -
异常转换:将各种异常转为统一错误格式
python复制try: result = tool.execute() except Exception as e: return { "status": "error", "code": get_error_code(e), "message": str(e) } -
重试策略:根据错误类型实施不同重试逻辑
python复制def should_retry(error): return error.get("code") in [ "TIMEOUT", "RATE_LIMIT", "NETWORK_ERROR" ]
2.1.3 状态跟踪器(State Tracker)优化
状态管理最容易成为性能瓶颈。我的分层方案经过多次迭代:
基础版(扁平字典)
python复制state = {
"last_action": "set_temperature",
"last_result": "error",
"retry_count": 2
}
问题:状态膨胀后难以维护
改进版(按模块分区)
python复制state = {
"system": {
"active_tools": {"climate": "ready"},
"errors": []
},
"user_intent": {
"original": "set comfortable temperature",
"current": "retry setting"
},
"constraints": {
"max_api_calls": 5,
"time_limit": 60
}
}
生产版(带历史压缩)
python复制def compress_history(full_history):
# 保留最近3条完整记录
recent = full_history[-3:]
# 早期记录聚合为统计信息
stats = {
"total_steps": len(full_history),
"success_rate": sum(1 for h in full_history if h["status"]=="success")/len(full_history),
"common_errors": Counter(h["error"] for h in full_history if h["status"]=="error")
}
return {
"recent": recent,
"stats": stats,
"last_updated": datetime.now()
}
2.2 动态重规划机制
重规划是Plan-and-Execute模式最精妙的部分。好的重规划应该像经验丰富的项目经 ideal 在遇到障碍时快速调整计划,而不是推倒重来。
2.2.1 触发条件设计
通过数百次测试,我总结了这些黄金规则:
| 触发条件 | 响应策略 | 示例场景 |
|---|---|---|
| 工具返回4XX/5XX错误 | 立即重试→检查状态→降级操作 | API限流时等待后重试 |
| 结果偏离预期>30% | 验证输入→调整参数→换工具 | 温度设置偏差过大时换控制方式 |
| 执行时间超过阈值 | 中断当前→标记工具异常→重规划 | 设备无响应时跳过该步骤 |
| 用户主动干预 | 暂停流程→确认新意图→生成新规划 | 用户说"先别关灯" |
| 约束条件违反 | 回滚已执行步骤→通知用户 | 超过API调用限额 |
2.2.2 重规划prompt设计
有效的重规划需要给LLM提供精准的上下文。这是我的模板:
python复制replan_template = """
原始目标:{goal}
失败步骤:{failed_step}(原因:{failure_reason})
当前状态:{state}
请执行以下操作:
1. 分析失败是否可恢复
2. 如可恢复,修改后续步骤(保持原JSON格式)
3. 如不可恢复,建议补救措施
输出格式:
{
"recoverable": bool,
"new_steps": [步骤列表],
"fallback_action": str
}
"""
这个模板强制LLM进行结构化思考,避免生成模糊的建议。
3. 实战经验与避坑指南
3.1 性能优化技巧
规划阶段优化
- 对常见任务预生成规划模板,减少LLM调用
- 使用较小模型处理简单任务拆分(如GPT-3.5-turbo)
- 对多步骤任务实施分段规划(每5步重新评估)
执行阶段优化
- 并行执行独立步骤(用线程池或asyncio)
- 实现步骤缓存(相同输入直接返回历史结果)
- 对耗时操作支持异步回调
状态管理优化
- 对大型状态使用增量更新
- 定期清理历史记录
- 对频繁访问的状态实现LRU缓存
3.2 常见故障排查
问题1:规划器生成无效步骤
- 症状:步骤包含不存在工具或错误参数
- 解决方案:
- 在prompt中明确可用工具列表
- 添加输出验证层
- 对常见错误模式实现自动修正
问题2:执行器陷入死循环
- 症状:相同步骤反复重试失败
- 解决方案:
- 设置最大重试次数
- 实现熔断机制
- 添加循环检测(相同步骤出现N次后终止)
问题3:状态膨胀导致性能下降
- 症状:响应时间随运行时长增加
- 解决方案:
- 实施状态压缩(如前文所示)
- 定期持久化状态到数据库
- 对不必要字段设置TTL
3.3 测试策略建议
单元测试重点
- 规划器:验证步骤拆分的合理性和格式合规性
- 执行器:测试各类异常处理(超时、错误格式、权限问题)
- 状态跟踪:检查状态转换的正确性和性能
集成测试场景
- 正常流程测试(阳光路径)
- 单点故障测试(随机使某个工具失败)
- 压力测试(高频连续请求)
- 长时间运行测试(检查内存泄漏)
监控指标
- 规划成功率(% of valid plans)
- 平均重试次数(retries per task)
- 状态大小变化趋势(state size over time)
- 异常类型分布(error type distribution)
4. 进阶应用方向
4.1 与强化学习结合
虽然不推荐一开始就用RL,但当系统稳定后,可以用历史数据训练这些方面:
- 规划策略优化(哪些步骤组合成功率最高)
- 重试策略调参(不同错误类型的最佳重试间隔)
- 工具选择偏好(某些工具在特定场景更可靠)
4.2 多Agent协作
Plan-and-Execute模式可以扩展到多Agent场景:
- 主Agent负责高层规划
- 子Agent负责具体领域执行
- 通过共享状态总线同步信息
架构示例:
python复制class Orchestrator:
def __init__(self, agents):
self.agents = agents # 各领域专家Agent
self.shared_state = SharedState()
def execute_plan(self, plan):
for step in plan:
# 路由到合适的Agent
agent = self.select_agent(step)
result = agent.execute(step, self.shared_state)
# 全局状态更新
self.shared_state.update(
step_id=step.id,
result=result
)
# 全局重规划判断
if need_replan(result):
self.adjust_plan()
4.3 混合规划策略
结合不同规划方法的优势:
- LLM规划:处理模糊、创造性需求
- 规则引擎:处理明确、结构化任务
- 检索增强:从历史成功案例中检索类似规划
实现框架:
python复制def generate_plan(task):
# 先尝试从案例库检索
similar_case = retrieve_similar_case(task)
if similar_case:
return adapt_plan(similar_case.plan)
# 检查是否匹配规则模板
if matches_rule_pattern(task):
return rule_engine.generate(task)
# 最后使用LLM
return llm_planner.generate(task)
在真实项目中,我通常会先让系统运行1-2周收集足够数据,然后分析哪些任务适合哪种规划方式,逐步建立分流规则。