在这里插入图片描述

为什么你的Agent总是"想一步做一步"翻车?Plan-and-Execute的"计划阶段"才是隐藏BOSS!深度拆解133课核心机制,手把手教你让AI学会"谋定而后动",告别执行混乱、任务烂尾的尴尬局面。


Plan-and-Execute Agent
计划阶段深度剖析

核心概念篇

计划生成机制

任务拆解艺术

依赖关系构建

动态调整策略

实战避坑指南

性能优化心法

Why需要计划阶段

与ReAct的本质区别

LLM作为规划器

结构化输出设计

原子任务划分

粒度控制技巧

DAG构建逻辑

并行vs串行判断

执行反馈回环

计划重规划触发

常见翻车场景

调试排查方法

Token成本控制

延迟优化方案

目录

  1. 核心概念篇:为什么Plan-and-Execute需要独立的计划阶段
  2. 计划生成机制:LLM如何变身"战略指挥官"
  3. 任务拆解艺术:原子任务的黄金分割法则
  4. 依赖关系构建:DAG是你的任务调度地图
  5. 动态调整策略:计划赶不上变化怎么办
  6. 实战避坑指南:那些让你半夜debug的诡异bug
  7. 性能优化心法:让计划阶段又快又省

嗨,大家好呀,我是你的老朋友精通代码大仙。接下来我们一起学习 《大模型应用开发_动手做AI_Agent》,震撼你的学习轨迹!


“磨刀不误砍柴工”——这句老话咱们从小听到大,但真到了写Agent的时候,多少人恨不得让AI拿到任务立刻就开干?

我见过太多这样的场景:新手开发者兴致勃勃地搭了个ReAct Agent,结果发现AI像个没头苍蝇,想到哪做到哪,做着做着就忘了最初要干啥。任务稍微复杂一点,直接原地爆炸,输出一堆前后矛盾的中间结果。

这就是Plan-and-Execute架构要解决的问题。而其中的计划阶段,才是整个架构的灵魂所在。今天咱们就把第7章133课的内容掰开了、揉碎了,好好聊聊怎么让AI学会"谋定而后动"。


一、核心概念篇:为什么Plan-and-Execute需要独立的计划阶段

点题

Plan-and-Execute(计划-执行)是一种将"思考规划"与"具体执行"显式分离的Agent架构。与ReAct的"边想边做"不同,它要求AI在动手之前,先完整地制定一份可执行的计划书。

Plan-and-Execute模式

反馈

计划阶段
生成完整任务图

执行阶段
按计划逐步执行

重规划
必要时调整

ReAct模式

观察

思考

行动

痛点分析

误区一:“计划就是浪费时间,直接干更快”

很多新手觉得,让LLM先写一堆计划再执行,既耗Token又拖慢响应。于是他们直接把ReAct的prompt改改就用,结果遇到复杂任务就翻车。

举个真实案例:有个朋友要做"查询某城市天气,如果下雨就推荐室内景点,否则推荐户外景点,最后生成一份一日游攻略"。他用ReAct,结果AI查完天气直接开始写攻略,完全忘了要先判断室内外这个分支。最后输出的攻略和天气对不上,用户骂骂咧咧。

误区二:计划做得太粗,等于没做

还有人虽然用了Plan-and-Execute,但计划阶段只输出一句话:“先查天气,再推荐景点,最后生成攻略”。这种"计划"没有任何信息量,执行阶段该懵还是懵。

误区三:计划做得太细,把自己绕进去

另一个极端是计划事无巨细,连"打开浏览器"都要写进计划里。结果计划本身生成就要好几秒,执行时还被冗余步骤拖累。

解决方案/正确做法

理解Plan-and-Execute的核心优势:计划阶段买的是"全局一致性",卖的是"局部灵活性"

正确的认知框架:

维度 ReAct Plan-and-Execute
适用场景 简单任务、工具少、路径明确 复杂任务、多步骤、需要条件分支
思考深度 单步局部最优 全局最优规划
容错能力 低,一步走错可能越偏越远 高,可在重规划时纠正
Token成本 低(但可能重复尝试) 计划阶段较高,执行阶段可控

关键设计原则:计划阶段输出的是任务依赖图(DAG),而非线性步骤列表。每个节点包含:任务ID、任务描述、所需输入、预期输出、依赖的前置任务。

# 好的计划输出示例
plan = {
    "tasks": [
        {
            "id": "t1",
            "description": "查询北京市今日天气",
            "tool": "weather_api",
            "inputs": {"city": "北京"},
            "outputs": ["weather_condition", "temperature"],
            "dependencies": []
        },
        {
            "id": "t2",
            "description": "根据天气选择景点类型",
            "tool": "condition_router",
            "inputs": {"weather": "$t1.weather_condition"},
            "outputs": ["venue_type"],
            "dependencies": ["t1"]
        },
        {
            "id": "t3a",
            "description": "推荐室内景点",
            "tool": "venue_search",
            "inputs": {"type": "indoor", "city": "北京"},
            "outputs": ["venues"],
            "dependencies": ["t2"]
        },
        {
            "id": "t3b",
            "description": "推荐户外景点",
            "tool": "venue_search",
            "inputs": {"type": "outdoor", "city": "北京"},
            "outputs": ["venues"],
            "dependencies": ["t2"]
        },
        {
            "id": "t4",
            "description": "生成一日游攻略",
            "tool": "itinerary_generator",
            "inputs": {"venues": "$t3a.venues or $t3b.venues"},
            "outputs": ["itinerary"],
            "dependencies": ["t3a", "t3b"]  # 或依赖关系
        }
    ]
}

小结

计划阶段不是可有可无的装饰,而是复杂任务可控执行的基石。投入Token买一份好计划,换来的是执行阶段的顺畅和结果的可预期。


二、计划生成机制:LLM如何变身"战略指挥官"

点题

计划阶段的核心是规划器(Planner),通常由一个LLM担任。它的工作是将用户的高层意图,转化为结构化的、可执行的任务图。这要求LLM具备:意图理解能力、任务分解能力、依赖推理能力。

用户输入

意图解析

任务分解

依赖分析

输出生成

识别目标

提取约束

拆分子任务

分配工具

数据流分析

并行性判断

JSON/YAML格式

验证与修正

痛点分析

痛点一:LLM"想象力过剩",编造不存在的工具

新手常遇到的诡异情况:规划器输出了一个看起来很完美的计划,但执行时发现某个任务指定的工具根本不存在,或者参数格式完全不对。

# 翻车示例:LLM编造的"智能"工具
{
    "id": "t2",
    "description": "分析用户情绪并调整回复策略",
    "tool": "emotion_analyzer_with_sentiment_scoring",  # 不存在!
    "inputs": {"text": "$t1.output", "depth": "deep"},
    "outputs": ["emotion_score", "suggested_tone"]
}

痛点二:输出格式不稳定,JSON解析天天报错

让LLM输出结构化数据,结果它一会儿加markdown代码块,一会儿漏个引号,一会儿用单引号代替双引号。你的代码json.loads()天天抛异常,不得不写一堆容错补丁。

痛点三:对工具能力理解偏差,分配错误

LLM以为某个工具能做A,实际上它只能做B。比如把"生成图片"的任务分配给纯文本模型,或者让只能查当前天气的API去预测未来一周。

解决方案/正确做法

方案一:严格的工具描述Schema

给规划器的system prompt里,必须包含完整、准确、无歧义的工具描述。每个工具要说明:功能边界、输入参数(类型、必填/选填、示例)、输出格式、典型使用场景。

PLANNER_SYSTEM_PROMPT = """你是一个任务规划专家。可用工具如下:

【工具名】weather_current
【功能】查询指定城市当前实时天气
【输入】{"city": "string, 城市名,如'北京'", "lang": "string, 可选,语言"}
【输出】{"condition": "天气状况", "temp": "温度数值", "unit": "C/F"}
【限制】只能查当前时刻,不能预测未来
【示例】输入{"city": "上海"} → 输出{"condition": "多云", "temp": 22, "unit": "C"}

【工具名】image_generate  
【功能】根据文本描述生成图片
【输入】{"prompt": "string, 英文描述", "size": "enum[256x256,512x512,1024x1024]"}
【输出】{"url": "图片URL", "seed": "生成种子"}
【限制】仅支持英文prompt,最长500字符

...(其他工具)

【输出格式】
你必须输出纯JSON,格式如下:
{
    "reasoning": "规划思路说明",
    "tasks": [
        {
            "id": "t1",
            "description": "任务描述",
            "tool": "工具名(必须从上面列表选)",
            "inputs": {...},
            "outputs": ["输出字段名"],
            "dependencies": ["依赖的任务id列表,可为空"]
        }
    ]
}

【重要规则】
1. 禁止编造工具名,必须从列表中选择
2. 引用前置任务输出使用 $任务id.字段名 格式
3. 确保无循环依赖
4. 纯JSON输出,不要markdown代码块
"""

方案二:输出解析与自修正机制

别指望LLM一次输出完美JSON,设计容错流程:

import json
from typing import Optional, Dict, Any

def robust_json_parse(text: str, max_retry: int = 2) -> Optional[Dict]:
    """带自修正的JSON解析"""
    # 第一步:清理常见污染
    cleaned = text.strip()
    if cleaned.startswith("```json"):
        cleaned = cleaned[7:]
    if cleaned.startswith("```"):
        cleaned = cleaned[3:]
    if cleaned.endswith("```"):
        cleaned = cleaned[:-3]
    cleaned = cleaned.strip()
    
    # 第二步:尝试解析
    try:
        return json.loads(cleaned)
    except json.JSONDecodeError as e:
        if max_retry <= 0:
            return None
        
        # 第三步:让LLM自己修
        fix_prompt = f"""以下JSON解析出错:{str(e)}
请修正JSON格式错误,输出正确的JSON:

{cleaned}

只输出修正后的JSON,不要其他内容:"""
        
        # 调用LLM修正...
        fixed_text = call_llm(fix_prompt)
        return robust_json_parse(fixed_text, max_retry - 1)

# 更稳妥:用Pydantic做结构化验证
from pydantic import BaseModel, Field, validator

class TaskNode(BaseModel):
    id: str = Field(..., pattern=r"^t\d+$")
    description: str = Field(..., min_length=5)
    tool: str
    inputs: Dict[str, Any]
    outputs: List[str]
    dependencies: List[str] = []
    
    @validator('tool')
    def tool_must_exist(cls, v, values):
        valid_tools = ["weather_current", "image_generate", "search", "calculator"]
        if v not in valid_tools:
            raise ValueError(f"未知工具: {v}")
        return v
    
    @validator('dependencies')
    def no_self_dependency(cls, v, values):
        task_id = values.get('id')
        if task_id in v:
            raise ValueError("任务不能依赖自己")
        return v

class Plan(BaseModel):
    reasoning: str
    tasks: List[TaskNode]

方案三:工具能力示例增强(Few-shot)

在prompt中加入2-3个典型任务的规划示例,让LLM"照葫芦画瓢":

EXAMPLES = """
【示例1:简单顺序任务】
用户:查北京天气,然后生成一张北京地标的图片
规划输出:
{
    "reasoning": "需要先获取天气信息,再生成图片。两个任务有数据依赖。",
    "tasks": [
        {"id": "t1", "description": "查询北京当前天气", "tool": "weather_current", 
         "inputs": {"city": "北京"}, "outputs": ["condition"], "dependencies": []},
        {"id": "t2", "description": "生成北京地标图片", "tool": "image_generate",
         "inputs": {"prompt": "Beijing landmark, $t1.condition weather", "size": "512x512"},
         "outputs": ["url"], "dependencies": ["t1"]}
    ]
}

【示例2:含条件分支】
用户:如果上海下雨就推荐博物馆,否则推荐公园
规划输出:
{
    "reasoning": "这是一个条件分支任务,需要先用条件路由工具判断,再并行查询两类场所",
    "tasks": [
        {"id": "t1", "description": "查询上海天气", "tool": "weather_current",
         "inputs": {"city": "上海"}, "outputs": ["condition"], "dependencies": []},
        {"id": "t2", "description": "判断场所类型", "tool": "condition_router",
         "inputs": {"condition": "$t1.condition", "if_rain": "museum", "else": "park"},
         "outputs": ["venue_type"], "dependencies": ["t1"]},
        {"id": "t3", "description": "查询推荐场所", "tool": "venue_search",
         "inputs": {"type": "$t2.venue_type", "city": "上海"},
         "outputs": ["venues"], "dependencies": ["t2"]}
    ]
}
"""

小结

规划器的可靠性取决于:工具描述的清晰度、输出格式的约束力、以及失败时的自修正能力。别跟LLM赌运气,用Schema和验证机制给它套上缰绳。


三、任务拆解艺术:原子任务的黄金分割法则

点题

任务拆解是计划阶段的核心技术。拆得太粗,执行时还是一团乱麻;拆得太细,计划本身就成了负担。原子任务的定义标准是:不可再分、工具可执行、结果可验证。

60% 30% 10% 任务粒度分布建议 原子任务(单工具单次调用) 组合任务(需简单后处理) 控制任务(条件/循环)

痛点分析

痛点一:“万能任务”——一个任务干太多事

新手常写这样的任务描述:“分析用户评论的情感、提取关键词、总结要点并生成回复”。一个任务塞了四个子功能,结果找不到匹配的工具,或者强行塞给LLM导致输出质量差。

痛点二:“碎片化灾难”——拆得太细失去意义

另一个极端:把"查询天气"拆成"构建HTTP请求"、“发送请求”、“解析响应”、“提取温度字段”。这些步骤对业务无意义,还增加了失败点和延迟。

痛点三:忽视任务的"可验证性"

有些任务输出模糊,比如"优化这段代码",怎么算优化成功?没有明确验收标准,执行阶段无法判断是否完成,也无法触发重规划。

解决方案/正确做法

黄金法则:MECE + 工具边界 + 状态可观测

MECE原则:Mutually Exclusive, Collectively Exhaustive
- 任务之间不重叠(避免重复执行)
- 合起来覆盖完整目标(无遗漏)

工具边界:一个原子任务对应一次工具调用
- 外部API调用一次
- LLM生成一次(有明确prompt)
- 代码执行一段(有明确输入输出)

状态可观测:任务完成有明确信号
- 输出字段非空
- 状态码成功
- 通过验证规则

实战拆解示例

复杂任务:“帮我规划从北京到杭州的周末旅行,要便宜、有特色、适合拍照”

错误拆解(太粗):

t1: 规划整个旅行(输入:需求,输出:完整攻略)

错误拆解(太细):

t1: 打开航班查询网站
t2: 输入出发地北京
t3: 输入目的地杭州
t4: 选择日期
t5: 点击搜索按钮
...

正确拆解(原子任务):

plan = {
    "tasks": [
        # 信息收集层(可并行)
        {"id": "t1", "description": "查询北京-杭州往返低价航班", 
         "tool": "flight_search", "inputs": {...}, "outputs": ["flights"], "dependencies": []},
        {"id": "t2", "description": "搜索杭州网红拍照景点",
         "tool": "venue_search", "inputs": {"tags": ["photography", "instagrammable"]}, 
         "outputs": ["spots"], "dependencies": []},
        {"id": "t3", "description": "查询杭州特色民宿/酒店",
         "tool": "hotel_search", "inputs": {"style": "boutique", "budget": "medium"},
         "outputs": ["hotels"], "dependencies": []},
        
        # 分析决策层(依赖收集结果)
        {"id": "t4", "description": "综合航班、景点、酒店生成3个备选方案",
         "tool": "itinerary_planner", 
         "inputs": {
             "flights": "$t1.flights", 
             "spots": "$t2.spots", 
             "hotels": "$t3.hotels",
             "constraints": {"budget": "low", "theme": "photography"}
         },
         "outputs": ["options"], 
         "dependencies": ["t1", "t2", "t3"]},
        
        # 输出优化层
        {"id": "t5", "description": "为每个方案生成拍照攻略和预估花费",
         "tool": "detail_enhancer",
         "inputs": {"options": "$t4.options"},
         "outputs": ["final_plans"],
         "dependencies": ["t4"]}
    ]
}

任务描述的最佳实践

要素 反例 正例
动作明确 “处理图片” “将图片压缩至宽度800px,保持比例,输出JPEG格式”
输入清晰 “需要的数据” “输入:用户ID(string),日期范围(YYYY-MM-DD格式)”
输出具体 “返回结果” “输出:订单列表(List[Order]),每个包含id、金额、状态”
成功标准 “完成分析” “成功标准:输出至少3个关键发现,每个有数据支撑”

小结

好的任务拆解像乐高积木——每个块足够简单、接口标准、组合灵活。记住:计划阶段的复杂度应该体现在结构关系上,而非单个任务的描述上。


四、依赖关系构建:DAG是你的任务调度地图

点题

依赖关系决定了任务的执行顺序和并行可能性。Plan-and-Execute使用**有向无环图(DAG)**建模依赖,既保证执行顺序正确,又能最大化并行效率。

开始

加载用户配置

验证API密钥

获取历史数据

数据清洗

特征工程

模型训练

生成报告

痛点分析

痛点一:隐式依赖没发现,执行时数据缺失

规划器漏掉了数据依赖,比如任务B要用任务A的输出,但dependencies里没写。执行时B拿到空值或错误值,整个计划崩盘。

痛点二:过度串行化,浪费并行机会

新手写的计划所有任务都依赖前一个,形成一条长链。实际上很多任务可以并行,比如同时查询多个独立的数据源。

痛点三:循环依赖导致死锁

复杂规划中可能出现A依赖B、B依赖C、C又依赖A的情况。执行调度器陷入死循环,或者拓扑排序报错。

解决方案/正确做法

方案一:显式数据流分析

规划时强制检查:每个任务的输入,是否来自前置任务的输出?

def validate_data_flow(plan: Dict) -> List[str]:
    """验证数据流完整性"""
    errors = []
    task_outputs = {}  # 累积所有可用输出
    
    # 拓扑排序保证顺序
    sorted_tasks = topological_sort(plan["tasks"])
    
    for task in sorted_tasks:
        # 检查每个输入
        for input_key, input_val in task["inputs"].items():
            # 检测引用格式 $task_id.field
            if isinstance(input_val, str) and input_val.startswith("$"):
                ref_parts = input_val[1:].split(".")
                if len(ref_parts) != 2:
                    errors.append(f"{task['id']}: 输入引用格式错误 {input_val}")
                    continue
                    
                ref_task, ref_field = ref_parts
                if ref_task not in task_outputs:
                    errors.append(f"{task['id']}: 引用了未完成的任务 {ref_task}")
                elif ref_field not in task_outputs[ref_task]:
                    errors.append(f"{task['id']}: 引用了不存在的字段 {ref_field}")
        
        # 注册当前任务的输出
        task_outputs[task["id"]] = set(task["outputs"])
    
    return errors

# 在规划器输出后立刻验证
raw_plan = call_planner(user_query)
plan = robust_json_parse(raw_plan)
errors = validate_data_flow(plan)
if errors:
    # 让规划器修正
    fix_prompt = f"计划有以下数据流错误,请修正:\n" + "\n".join(errors)
    # ...

方案二:并行调度优化

识别可并行任务组,减少总执行时间:

from collections import defaultdict, deque

def build_execution_batches(tasks: List[Dict]) -> List[List[Dict]]:
    """将DAG分层,每层内任务可并行执行"""
    # 构建邻接表和入度表
    graph = defaultdict(list)
    in_degree = {t["id"]: 0 for t in tasks}
    task_map = {t["id"]: t for t in tasks}
    
    for task in tasks:
        for dep in task.get("dependencies", []):
            graph[dep].append(task["id"])
            in_degree[task["id"]] += 1
    
    # Kahn算法分层
    batches = []
    queue = deque([tid for tid, deg in in_degree.items() if deg == 0])
    
    while queue:
        current_batch = list(queue)
        batches.append([task_map[tid] for tid in current_batch])
        
        queue.clear()
        for tid in current_batch:
            for neighbor in graph[tid]:
                in_degree[neighbor] -= 1
                if in_degree[neighbor] == 0:
                    queue.append(neighbor)
    
    return batches

# 执行时使用异步并行
async def execute_batch(batch: List[Dict]):
    """并行执行一批无依赖任务"""
    tasks = [execute_single_task(t) for t in batch]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    return dict(zip([t["id"] for t in batch], results))

方案三:循环依赖检测

def detect_cycle(tasks: List[Dict]) -> Optional[List[str]]:
    """检测循环依赖,返回环路上的任务ID"""
    WHITE, GRAY, BLACK = 0, 1, 2
    color = {t["id"]: WHITE for t in tasks}
    parent = {}
    
    def dfs(node_id, path):
        color[node_id] = GRAY
        
        task = task_map[node_id]
        for dep in task.get("dependencies", []):
            if color[dep] == GRAY:  # 发现回边
                # 提取环路
                cycle_start = path.index(dep)
                return path[cycle_start:] + [dep]
            
            if color[dep] == WHITE:
                result = dfs(dep, path + [dep])
                if result:
                    return result
        
        color[node_id] = BLACK
        return None
    
    task_map = {t["id"]: t for t in tasks}
    for task in tasks:
        if color[task["id"]] == WHITE:
            cycle = dfs(task["id"], [task["id"]])
            if cycle:
                return cycle
    
    return None

小结

DAG是Plan-and-Execute的骨架。花时间在规划阶段建好依赖关系,执行阶段就能既正确又高效。记住:依赖要显式、并行要挖掘、循环要杜绝。


五、动态调整策略:计划赶不上变化怎么办

点题

再完美的计划,执行时也可能遇到意外:工具报错、结果不符合预期、用户中途改需求。**重规划(Replanning)**机制让Agent能根据执行反馈,动态调整后续计划。

执行计划

任务成功?

更新状态
继续下一步

可重试?

重试当前任务

触发重规划

分析失败原因

生成新计划

还有任务?

完成

痛点分析

痛点一:一错就崩,没有容错机制

很多新手实现Plan-and-Execute时,某个任务一失败就直接返回错误,不会尝试恢复或调整。用户体验极差。

痛点二:重规划太频繁,陷入震荡

稍有异常就全盘推翻重来,结果新计划执行不久又遇到问题,再重规划……Agent在原地打转,Token烧了一堆,事情没办成。

痛点三:重规划时"失忆",重复已完成的任务

新计划里又包含了已经执行成功的任务,造成重复执行、数据不一致,甚至副作用(比如重复扣款)。

解决方案/正确做法

方案一:分层容错策略

class TaskExecutor:
    def __init__(self):
        self.max_retries = 2
        self.retryable_errors = ["timeout", "rate_limit", "temporary_failure"]
    
    async def execute_with_resilience(self, task: Dict, context: Dict) -> TaskResult:
        # 第一层:重试
        for attempt in range(self.max_retries + 1):
            try:
                result = await self.call_tool(task["tool"], task["inputs"])
                return TaskResult(success=True, data=result)
            except ToolError as e:
                if e.code in self.retryable_errors and attempt < self.max_retries:
                    await asyncio.sleep(2 ** attempt)  # 指数退避
                    continue
                break  # 不可重试,进入下一层
        
        # 第二层:替代方案
        alternative = self.find_alternative_tool(task)
        if alternative:
            try:
                result = await self.call_tool(alternative, task["inputs"])
                return TaskResult(success=True, data=result, used_alternative=True)
            except:
                pass
        
        # 第三层:触发重规划
        return TaskResult(success=False, error=e, needs_replanning=True)

方案二:智能重规划触发条件

不是所有失败都需要重规划,设定合理阈值:

场景 处理方式 示例
临时性错误 重试即可 API超时、限流
工具替代可行 换工具执行 天气API A不可用,换B
单任务失败但目标可达 局部调整 某景点查不到,换另一个
基础假设错误 全局重规划 用户说"便宜"实际要"豪华"
目标本身不可行 终止并解释 “明天去火星”

方案三:状态继承的新计划生成

重规划时必须携带已执行状态,避免重复:

def replan(failed_task: Dict, execution_history: List[Dict], 
           original_plan: Dict, user_query: str) -> Dict:
    
    # 构建状态摘要
    completed_tasks = [h for h in execution_history if h["success"]]
    failed_info = {
        "task_id": failed_task["id"],
        "error": failed_task.get("error"),
        "partial_result": failed_task.get("partial_result")
    }
    
    replan_prompt = f"""原任务:{user_query}

已完成的步骤(**禁止重复执行**):
{format_completed(completed_tasks)}

当前失败:
{json.dumps(failed_info, indent=2, ensure_ascii=False)}

请生成**剩余任务**的新计划,要求:
1. 不要包含已完成的任务
2. 基于已完成的结果继续
3. 针对失败提供替代方案或调整策略
4. 保持输出格式与原计划一致

输出:"""
    
    new_plan_raw = call_llm(replan_prompt)
    new_plan = robust_json_parse(new_plan_raw)
    
    # 验证:新计划的任务ID不能与已完成任务重复
    completed_ids = {h["task_id"] for h in completed_tasks}
    for task in new_plan["tasks"]:
        if task["id"] in completed_ids:
            raise ReplanError(f"新计划包含已完成的任务: {task['id']}")
    
    return new_plan

方案四:用户介入的优雅降级

当自动重规划也解决不了时,优雅地请求用户帮助:

async def handle_unrecoverable(failure: Dict, context: Dict):
    # 生成用户友好的解释
    explanation = await generate_explanation(failure, context)
    
    # 提供选项
    options = [
        {"id": "modify", "label": "修改需求", "example": "比如换个日期或地点"},
        {"id": "simplify", "label": "简化任务", "example": "只做核心部分"},
        {"id": "manual", "label": "手动提供信息", "example": "直接告诉我结果"}
    ]
    
    return {
        "status": "need_user_input",
        "message": explanation,
        "options": options,
        "current_progress": calculate_progress(context)
    }

小结

动态调整是Plan-and-Execute的"保险丝"。设计好分层容错、智能触发、状态继承这三层机制,你的Agent就能像老司机一样,遇到路况变化从容变道,而不是一脚刹车停死。


六、实战避坑指南:那些让你半夜debug的诡异bug

点题

理论再完美,落地总有坑。这一节汇总Plan-and-Execute计划阶段最常见的实战陷阱,以及排查思路。

Plan-and-Execute
常见坑点

规划阶段

工具描述不全

示例质量差

输出格式混乱

依赖关系

隐式依赖

循环依赖

版本不兼容

执行阶段

状态丢失

竞态条件

超时死锁

重规划

状态不一致

无限循环

用户感知差

痛点分析

坑点一:工具描述的"冰山效应"

你只描述了工具浮在水面上的功能,LLM不知道水下的限制。结果规划时看起来很合理,执行时各种报错。

真实案例:某搜索工具描述写"搜索网络信息",没说明"每次最多返回10条"。LLM规划了一个"搜索并分析前50条结果"的任务,执行时只拿到10条,后续分析基于不完整数据,结论全错。

坑点二:异步执行的数据竞争

并行任务同时修改共享状态,导致数据混乱。

# 错误示例:共享状态无保护
shared_context = {}

async def task_a():
    shared_context["temp"] = await fetch_data()
    result = process(shared_context["temp"])  # 可能被task_b覆盖!

async def task_b():
    shared_context["temp"] = await fetch_other()
    # ...

坑点三:重规划的"记忆碎片"

多次重规划后,执行历史冗长,LLM处理时丢失关键信息,或者混淆不同版本的计划。

坑点四:计划与执行的"语义漂移"

计划阶段理解的工具语义,和执行阶段实际行为不一致。比如计划时以为"send_email"会返回发送状态,实际它是个fire-and-forget的异步接口。

解决方案/正确做法

排查工具描述完整性

制作工具描述检查清单:

□ 功能边界:能做什么,明确不能做什么
□ 输入约束:必填/选填、类型、格式、长度限制
□ 输出保证:成功时返回什么,失败时返回什么
□ 性能特征:典型延迟、并发限制、配额
□ 副作用:是否修改数据、是否可重入
□ 版本信息:API版本、弃用计划

异步执行的状态隔离

from dataclasses import dataclass
from typing import Dict, Any

@dataclass(frozen=True)
class TaskContext:
    """不可变的任务上下文,避免竞争"""
    task_id: str
    inputs: Dict[str, Any]
    parent_outputs: Dict[str, Any]  # 只读引用前置结果
    
    def with_result(self, result: Any) -> "TaskContext":
        """创建新上下文,不修改原对象"""
        return TaskContext(
            task_id=self.task_id,
            inputs=self.inputs,
            parent_outputs={**self.parent_outputs, self.task_id: result}
        )

# 每个任务独立上下文
async def execute_task(task: Dict, parent_ctx: TaskContext) -> TaskResult:
    ctx = TaskContext(
        task_id=task["id"],
        inputs=resolve_inputs(task["inputs"], parent_ctx),
        parent_outputs=parent_ctx.parent_outputs
    )
    # 执行时只读ctx,结果通过返回值传递
    result = await tool_call(ctx.inputs)
    return TaskResult(context=ctx.with_result(result), data=result)

执行历史的智能压缩

def compress_history(full_history: List[Dict], max_tokens: int = 4000) -> str:
    """压缩历史,保留关键信息"""
    # 第一层:只保留关键节点
    key_events = [
        h for h in full_history 
        if h["type"] in ["plan_created", "task_completed", "replan_triggered", "user_input"]
    ]
    
    # 第二层:摘要已完成任务
    completed_summary = summarize_tasks(
        [h for h in key_events if h["type"] == "task_completed"]
    )
    
    # 第三层:详细保留最近3个事件
    recent_detail = format_detail(key_events[-3:])
    
    return f"""
【执行摘要】
{completed_summary}

【最近事件】
{recent_detail}
"""

计划-执行一致性验证

class ToolContract:
    """工具契约,计划与执行的桥梁"""
    def __init__(self, name: str, schema: Dict):
        self.name = name
        self.input_schema = schema["input"]
        self.output_schema = schema["output"]
        self.mock_behavior = schema.get("mock_for_planning")
    
    def validate_plan_usage(self, planned_task: Dict) -> List[str]:
        """验证计划中的使用是否符合契约"""
        errors = []
        # 检查输入是否符合schema
        # 检查输出引用是否合理
        return errors
    
    def mock_for_planning(self, inputs: Dict) -> Dict:
        """规划阶段用mock验证数据流"""
        return self.mock_behavior(inputs)

小结

坑是踩出来的,经验是debug堆出来的。建立系统性的检查清单、保持状态隔离、做好契约验证,能让你少熬很多夜。记住:诡异bug背后,往往是某个假设 silently failed。


七、性能优化心法:让计划阶段又快又省

点题

计划阶段调用LLM生成完整计划,是主要的Token消耗点和延迟来源。优化目标:在保证质量的前提下,减少Token、降低延迟、提升可缓存性。

35% 25% 20% 15% 5% 延迟优化收益分布 计划缓存复用 模型降级策略 并行规划 Prompt压缩 输出格式优化

痛点分析

痛点一:相似任务重复规划,Token白白浪费

用户连续问"北京明天天气"、“上海明天天气”、“广州明天天气”,每次都要重新生成几乎一样的计划结构。

痛点二:规划模型"杀鸡用牛刀"

用GPT-4做简单任务的规划,延迟2秒+,成本高。其实很多结构化任务用更小模型也能做好。

痛点三:复杂计划的生成时间过长

任务多、依赖复杂时,LLM生成JSON的时间线性增长,用户等待焦虑。

解决方案/正确做法

方案一:计划模板与缓存

from functools import lru_cache
import hashlib

class PlanCache:
    def __init__(self):
        self.template_store = {}  # 意图模板
        self.instance_cache = {}  # 具体实例
    
    def get_plan_template(self, intent_signature: str) -> Optional[Dict]:
        """获取意图对应的计划模板"""
        # 意图签名:从query提取的结构化表示
        # "查{城市}天气" → "weather_query_template"
        return self.template_store.get(intent_signature)
    
    def instantiate_template(self, template: Dict, params: Dict) -> Dict:
        """用具体参数实例化模板"""
        # 替换模板中的占位符
        plan_json = json.dumps(template)
        for key, val in params.items():
            plan_json = plan_json.replace(f"{{{key}}}", json.dumps(val))
        return json.loads(plan_json)
    
    @lru_cache(maxsize=1000)
    def cached_plan(self, query_hash: str) -> Optional[Dict]:
        """缓存具体查询的计划"""
        return self.instance_cache.get(query_hash)

# 使用示例
def plan_with_cache(user_query: str):
    # 1. 提取意图签名
    intent = extract_intent(user_query)  # e.g., "weather:city"
    
    # 2. 检查模板
    template = cache.get_plan_template(intent)
    if template:
        params = extract_params(user_query)
        return cache.instantiate_template(template, params)
    
    # 3. 回退到LLM规划
    return llm_plan(user_query)

方案二:分层模型策略

class TieredPlanner:
    def __init__(self):
        self.tiers = {
            "simple": {
                "model": "gpt-3.5-turbo",
                "max_tasks": 3,
                "max_deps": 2,
                "cost_per_1k": 0.0015
            },
            "standard": {
                "model": "gpt-4-turbo-preview", 
                "max_tasks": 10,
                "max_deps": 5,
                "cost_per_1k": 0.01
            },
            "complex": {
                "model": "gpt-4",
                "max_tasks": 50,
                "max_deps": 20,
                "cost_per_1k": 0.03
            }
        }
    
    def select_tier(self, query: str, estimated_complexity: float) -> str:
        """根据查询特征选择规划层级"""
        # 快速启发式判断
        if estimated_complexity < 0.3:
            return "simple"
        elif "如果" in query or "根据" in query:  # 条件逻辑
            return "complex"
        elif estimated_complexity > 0.7:
            return "complex"
        else:
            return "standard"
    
    async def plan(self, query: str) -> Dict:
        complexity = estimate_complexity(query)  # 轻量级分类器
        tier = self.select_tier(query, complexity)
        config = self.tiers[tier]
        
        return await call_llm(
            model=config["model"],
            prompt=build_prompt(query, config),
            response_format={"type": "json_object"}
        )

方案三:流式规划与渐进展示

async def streaming_plan(user_query: str):
    """边生成边验证,提前返回部分结果"""
    # 第一阶段:快速生成骨架(高温度,快)
    skeleton = await quick_skeleton_plan(user_query)
    yield {"type": "skeleton", "data": skeleton}
    
    # 第二阶段:并行填充细节
    detail_tasks = [
        expand_task_details(task) 
        for task in skeleton["tasks"]
    ]
    
    for completed in asyncio.as_completed(detail_tasks):
        detail = await completed
        yield {"type": "task_detail", "data": detail}
        
        # 已完成的可以开始执行了!
        if can_start_early(detail):
            yield {"type": "executable", "data": detail}
    
    # 第三阶段:最终整合
    final_plan = await consolidate(skeleton, detail_tasks)
    yield {"type": "complete", "data": final_plan}

方案四:Prompt压缩技术

def compress_tool_descriptions(tools: List[Dict], query: str) -> List[Dict]:
    """根据查询相关性筛选工具描述"""
    # 用embedding计算相关性
    query_vec = embed(query)
    
    scored_tools = []
    for tool in tools:
        tool_vec = embed(tool["description"])
        similarity = cosine_similarity(query_vec, tool_vec)
        scored_tools.append((similarity, tool))
    
    # 取Top-K,其余用摘要
    scored_tools.sort(reverse=True)
    essential = scored_tools[:5]  # 详细描述
    others = scored_tools[5:10]   # 简要描述
    
    result = []
    for sim, tool in essential:
        result.append(tool)  # 完整描述
    
    for sim, tool in others:
        result.append({
            "name": tool["name"],
            "one_liner": tool["description"][:100] + "..."
        })
    
    return result

小结

性能优化不是一味省钱,而是在用户体验和成本之间找平衡。模板缓存省的是重复劳动,分层模型省的是过度配置,流式规划省的是用户等待。记住:先度量,再优化,别凭感觉。


写在最后

聊到这里,Plan-and-Execute的计划阶段应该不再是个黑盒了。咱们从为什么要分离计划与执行,到规划器怎么设计、任务怎么拆解、依赖怎么管理、出了问题怎么调整、常见坑怎么避、性能怎么优化——这七个维度,构成了一个相对完整的知识体系。

但我最想说的是:别被架构的复杂度吓住。Plan-and-Execute不是银弹,ReAct也不是过时货。简单任务用简单方案,复杂任务才值得上复杂架构。很多新手的问题不是"学得太少",而是"想得太复杂",一个天气查询都要上DAG,反而把系统搞臃肿。

编程之路就是这样,每一步都是在权衡。权衡正确性与成本,权衡灵活性与可控性,权衡当下的交付与长期的维护。Plan-and-Execute教给我们的,不只是技术方案,更是一种思维方式:面对复杂问题,先停下来想清楚了再动手,往往比急着开干更高效

这本书的第7章,133课,值得你反复读、动手试、踩坑再爬出来。Agent开发这个领域变化快,但底层的设计思想是稳定的。把基础打扎实了,新框架出来你也能快速上手。

保持好奇,持续学习,你也能成为代码高手。咱们下节课见!


关注私信备注:“资料代找获取”,全网计算机学习资料代找:例如:
《课程:2026 年多模态大模型实战训练营》
《课程:AI 大模型工程师系统课程 (22 章完整版 持续更新)》
《课程:AI 大模型系统实战课第四期 (2026 年开课 持续更新)》
《课程:2026 年 AGI 大模型系统课 23 期》
《课程:2026 年 AGI 大模型系统课 21 期》
《课程:AI 大模型实战课 8 期 (2026 年 2 月最新完结版)》
《课程:AI 大模型系统实战课三期》
《课程:AI 大模型系统课程 (2026 年 2 月开课 持续更新)》
《课程:AI 大模型全阶课程 (2025 年 12 月开课 2026 年 6 月结课)》
《课程:AI 大模型工程师全阶课程 (2025 年 10 月开课 2026 年 4 月结课)》
《课程:2026 年最新大模型 Agent 开发系统课 (持续更新)》
《课程:LLM 多模态视觉大模型系统课》
《课程:大模型 AI 应用开发企业级项目实战课 (2026 年 1 月开课)》
《课程:大模型智能体线上速成班 V2.0》
《课程:Java+AI 大模型智能应用开发全阶课》
《课程:Python+AI 大模型实战视频教程》
《书籍:软件工程 3.0: 大模型驱动的研发新范式.pdf》
《课程:人工智能大模型系统课 (2026 年 1 月底完结版)》
《课程:AI 大模型零基础到商业实战全栈课第五期》
《课程:Vue3.5+Electron + 大模型跨平台 AI 桌面聊天应用实战 (2025)》
《课程:AI 大模型实战训练营 从入门到实战轻松上手》
《课程:2026 年 AI 大模型 RAG 与 Agent 智能体项目实战开发课》
《课程:大模型训练营配套补充资料》

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐