第 10 章:Planning 模式

Planning 不是"想好了再动手",而是"边想边做、边做边评估"的持续循环——关键在于知道何时停止,而不是追求完美的计划。


10.1 为什么需要规划?

想象这个场景:

你让一个实习生研究三家竞争对手——特斯拉、比亚迪、Rivian。你期待什么?一份结构清晰的对比报告:各家的产品线、技术特点、市场定位、优劣势分析。

但实习生给你的是:特斯拉的 Wikipedia 摘要 + 比亚迪的新闻剪报 + Rivian 的官网复制粘贴。信息是有了,但完全没有结构,无法对比,更谈不上洞察。

问题出在哪?他没有规划。 他直接开始搜索、复制、整理,但没想过"我要回答什么问题"、"应该按什么结构组织"、"哪些信息重要哪些不重要"。

Agent 也会犯同样的错误。如果你直接让它"研究 OpenAI 写一份竞争分析报告",它会立即开始搜索、提取、生成——但往往会产出一堆信息堆砌,缺乏结构和深度。

这就是 Planning 要解决的问题。

复杂任务的特征

直觉解释

你去装修房子,你不会上来就开始刷墙。你会先想:

  1. 水电要改吗?
  2. 墙面要不要拆?
  3. 地板什么时候铺?
  4. 家具什么时候进场?

然后你会发现:有些事情必须先做(水电),有些事情可以并行(吊顶和刷墙),有些事情必须最后做(家具)。

这就是 Planning。

Agent 也是一样。面对一个复杂任务,它需要:

  1. 拆解:把大任务拆成小任务
  2. 排序:哪个先做哪个后做
  3. 评估:做完够不够,要不要继续

没有规划会怎样?

看这个请求:"研究 OpenAI,写一份完整的竞争分析报告"

如果直接让 LLM 处理:

问题表现后果
信息量太大一次性塞进上下文超出上下文窗口,信息丢失
没有结构东一榔头西一棒槌输出杂乱无章
无法追踪用户干等,不知道进度体验差,无法中途调整
重复劳动同一个信息搜索多次浪费 Token

有规划会怎样?

1. 公司基本信息(成立时间、创始人、融资)
2. 产品矩阵(GPT、API、ChatGPT)
3. 技术创新(Transformer、RLHF)
4. 竞争对手分析(Google、Meta)
5. 市场定位与战略
6. 综合报告生成

每个子任务独立可控。第一个做完了,存起来。第二个做完了,存起来。最后综合。

中间某一步出问题了?只重做那一步,不用从头来。


10.2 Planning 的三个核心问题

Planning 模式回答三个问题:

问题核心挑战生产考量
如何分解?把模糊需求变成具体子任务分解粒度、范围边界
如何执行?确定依赖关系和执行顺序并行 vs 串行、失败重试
何时停止?评估当前进度,决定继续还是结束覆盖度阈值、最大迭代

第三个问题最容易被忽视,但在生产环境里最重要。

我见过太多 Agent 在这里翻车——不知道什么时候该停,结果陷入无限循环,把 Token 烧光了,还没产出有用的东西。


10.3 任务分解

分解的输入输出

任务分解是 Planning 的第一步。在 Shannon 里,它通过 LLM Service 完成:

// 分解请求的输入
type DecompositionInput struct {
    Query          string                 // 用户的原始请求
    Context        map[string]interface{} // 上下文信息
    AvailableTools []string               // 可用工具列表
}

// 分解的结果
type DecompositionResult struct {
    Mode              string    // "simple", "standard", "complex"
    ComplexityScore   float64   // 0.0 - 1.0
    Subtasks          []Subtask // 子任务列表
    ExecutionStrategy string    // "parallel", "sequential", "dag"
}

LLM 看完请求后会告诉你:

  • 这个任务有多复杂(0-1 分)
  • 应该拆成几个子任务
  • 子任务之间的执行策略

实现参考 (Shannon): activities/decompose.go - DecomposeTask 函数

子任务结构

每个子任务不只是一句话描述,要带完整的执行约束:

type Subtask struct {
    ID              string
    Description     string
    Dependencies    []string  // 依赖的其他子任务 ID

    // 输入输出声明
    Produces []string  // 这个子任务产生什么
    Consumes []string  // 这个子任务需要什么

    // 执行约束
    SuggestedTools   []string
    SuggestedPersona string

    // 范围边界
    Boundaries *BoundariesSpec
}

这个结构设计得相当精细。我来解释几个关键字段:

Produces/Consumes:声明依赖

Subtask 1: "收集公司基本信息"
  - Produces: ["company_info", "founding_date"]
  - Dependencies: []

Subtask 2: "分析产品矩阵"
  - Consumes: ["company_info"]
  - Produces: ["products", "pricing"]
  - Dependencies: ["subtask-1"]

Subtask 3: "竞争对手分析"
  - Consumes: ["company_info", "products"]
  - Dependencies: ["subtask-1", "subtask-2"]

执行时按拓扑排序:subtask-1 完成后,subtask-2 才能开始(因为它需要 company_info)。

Boundaries:声明范围

这是为了防止子任务之间重叠:

// 研究产品
subtask1 := Subtask{
    Description: "研究公司产品",
    Boundaries: &BoundariesSpec{
        InScope:    []string{"software products", "pricing"},
        OutOfScope: []string{"professional services"},
    },
}

// 研究服务
subtask2 := Subtask{
    Description: "分析专业服务",
    Boundaries: &BoundariesSpec{
        InScope:    []string{"consulting", "implementation"},
        OutOfScope: []string{"software products"},
    },
}

没有边界声明,两个子任务可能都去研究"产品和服务",重复劳动。


10.4 执行策略

分解完任务后,需要决定怎么执行。Shannon 支持三种策略:

策略适用场景特点
parallel子任务互不依赖最快,同时执行
sequential子任务有严格顺序最稳,但最慢
dag子任务有部分依赖灵活,按拓扑排序

怎么选?

// 检测是否有依赖
hasDependencies := false
for _, subtask := range decomp.Subtasks {
    if len(subtask.Dependencies) > 0 {
        hasDependencies = true
        break
    }
}

// 有依赖用 hybrid(DAG),没依赖用 parallel
if hasDependencies {
    executeHybridPattern(...)
} else {
    executeParallelPattern(...)
}

实现参考 (Shannon): strategies/dag.go - DAGWorkflow 函数

拓扑排序

当子任务有依赖时,需要按拓扑顺序执行:

A (无依赖)
    
B (依赖 A)    C (依赖 A)
               
       D (依赖 B, C)

执行顺序:A → [B, C 并行] → D

这不是 LLM 特有的问题,这是标准的图论算法。但 Agent 系统里经常被忽略,导致执行顺序混乱。


10.5 覆盖度评估

这是 Planning 模式最关键的部分。

Planning 不是一次性的。执行完子任务后,你需要问:够不够?需不需要继续?

初始分解
    
    
执行子任务 ───► 评估覆盖度 ───► 覆盖度  85%? ───► 完成
                                  
                                
    └──────── 生成补充查询 ◄────────┘

Planning执行流程

评估结构

type CoverageEvaluationInput struct {
    Query            string
    CurrentSynthesis string  // 当前综合结果
    CoveredAreas     []string
    Iteration        int
    MaxIterations    int
}

type CoverageEvaluationResult struct {
    OverallCoverage   float64         // 0.0 - 1.0
    CriticalGaps      []CoverageGap   // 必须填补的缺口
    OptionalGaps      []CoverageGap   // 可选的改进点
    ShouldContinue    bool
    RecommendedAction string          // "continue", "complete"
}

确定性护栏

这是我特别想强调的一点:LLM 的判断不稳定,必须用规则覆盖。

func EvaluateCoverage(ctx context.Context, input Input) (*Result, error) {
    // LLM 评估
    result := llmEvaluate(input)

    // === 确定性护栏 ===

    // 规则 1: 第一次迭代 + 低覆盖度 → 必须继续
    if input.Iteration == 1 && result.OverallCoverage < 0.5 {
        result.ShouldContinue = true
        result.RecommendedAction = "continue"
    }

    // 规则 2: 存在关键缺口 + 还有次数 → 必须继续
    if len(result.CriticalGaps) > 0 && input.Iteration < input.MaxIterations {
        result.ShouldContinue = true
    }

    // 规则 3: 达到最大迭代次数 → 必须停止
    if input.Iteration >= input.MaxIterations {
        result.ShouldContinue = false
        result.RecommendedAction = "complete"
    }

    // 规则 4: 综合结果太短但声称高覆盖度 → 不可信
    if len(input.CurrentSynthesis) < 500 && result.OverallCoverage > 0.7 {
        result.ConfidenceLevel = "low"
    }

    return result, nil
}

为什么需要这些护栏?

  • LLM 可能说"覆盖度 95%",但综合结果只有 200 字。规则 4 会标记"不可信"。
  • LLM 可能说"可以结束了",但还有关键缺口。规则 2 会强制继续。
  • LLM 可能一直说"还需要继续",无限循环。规则 3 会强制停止。

护栏保证行为可预测。


10.6 补充查询生成

当评估发现缺口,需要生成针对性的补充查询:

type GeneratedSubquery struct {
    ID             string
    Query          string
    TargetGap      string   // 针对哪个缺口
    Priority       string   // "high", "medium", "low"
    SuggestedTools []string
}

生成时的 Prompt 要引导 LLM:

你是一个研究查询生成器。任务是生成针对性的子查询来填补覆盖缺口。

## 目标:
1. 生成直接解决覆盖缺口的查询
2. 优先处理 CRITICAL 缺口
3. 避免生成重复或重叠的查询

## 备选搜索策略(重要!)

当标准搜索找不到信息时,尝试:
1. 直接访问公司域名(web_fetch)
2. 用日语/本地语言搜索亚洲公司
3. 搜索 LinkedIn/Crunchbase

关键是多语言感知。研究日本公司时,用日语搜索效果更好。这是很多 Agent 系统忽略的细节。


10.7 完整流程

把前面的组件串起来:

const MaxIterations = 3

func Research(ctx context.Context, query string) (string, error) {
    // 1. 初始分解
    decomposition := DecomposeTask(ctx, DecompositionInput{
        Query:          query,
        AvailableTools: []string{"web_search", "web_fetch"},
    })

    fmt.Printf("分解为 %d 个子任务,策略: %s\n",
        len(decomposition.Subtasks), decomposition.ExecutionStrategy)

    // 2. 执行子任务(按拓扑顺序)
    var results []Result
    for _, subtask := range topologicalSort(decomposition.Subtasks) {
        result := executeSubtask(ctx, subtask)
        results = append(results, result)
    }

    // 3. 综合初始结果
    synthesis := synthesize(ctx, query, results)

    // 4. 迭代改进循环
    for iteration := 1; iteration <= MaxIterations; iteration++ {
        // 评估覆盖度
        coverage := EvaluateCoverage(ctx, CoverageEvaluationInput{
            Query:            query,
            CurrentSynthesis: synthesis,
            Iteration:        iteration,
            MaxIterations:    MaxIterations,
        })

        fmt.Printf("迭代 %d: 覆盖度 %.0f%%, 继续: %v\n",
            iteration, coverage.OverallCoverage*100, coverage.ShouldContinue)

        if !coverage.ShouldContinue {
            break
        }

        // 生成补充查询
        subqueries := GenerateSubqueries(ctx, SubqueryGeneratorInput{
            Query:         query,
            CoverageGaps:  coverage.CriticalGaps,
            MaxSubqueries: 3,
        })

        // 执行补充查询
        for _, sq := range subqueries {
            result := executeSubquery(ctx, sq)
            results = append(results, result)
        }

        // 重新综合
        synthesis = synthesize(ctx, query, results)
    }

    return synthesis, nil
}

输出示例:

分解为 5 个子任务,策略: sequential
迭代 1: 覆盖度 60%, 继续: true
迭代 2: 覆盖度 82%, 继续: true
迭代 3: 覆盖度 91%, 继续: false
研究完成

10.8 常见的坑

坑 1:过度分解

症状:分解出 20+ 个子任务,每个都很小。

问题:协调成本比执行成本还高。每个子任务都要调用 LLM,都要等待,都可能失败。

// 分解太细
subtasks := []Subtask{
    {Description: "搜索公司名称"},
    {Description: "打开官网"},
    {Description: "找 About 页面"},
    {Description: "读创始人信息"},
    // ... 100 个子任务
}

// 适度粒度
subtasks := []Subtask{
    {Description: "收集公司基本信息(创始人、成立时间、总部)"},
    {Description: "分析产品和服务"},
    {Description: "研究融资历史"},
}

经验法则:一个研究任务,3-7 个子任务就够了。

坑 2:范围重叠

症状:不同子任务产生重复内容。

问题:浪费 Token,综合时还要去重。

// 范围模糊
subtask1 := Subtask{Description: "研究公司产品"}
subtask2 := Subtask{Description: "分析公司服务"}
// "产品"和"服务"可能重叠

// 明确边界
subtask1 := Subtask{
    Description: "研究公司产品",
    Boundaries: &BoundariesSpec{
        InScope:    []string{"software products"},
        OutOfScope: []string{"professional services"},
    },
}

坑 3:无限迭代

症状:迭代 10 次还没停,Token 烧光了。

问题:LLM 总觉得"还可以更好",永远达不到 100%。

// 没有终止条件
for {
    coverage := evaluateCoverage()
    if coverage.OverallCoverage < 1.0 {
        // 永远不会 100%
        generateMoreQueries()
    }
}

// 多重终止条件
for iteration := 1; iteration <= MaxIterations; iteration++ {
    coverage := evaluateCoverage()

    if coverage.OverallCoverage >= 0.85 && len(coverage.CriticalGaps) == 0 {
        break  // 质量达标
    }
    if iteration >= MaxIterations {
        break  // 预算耗尽
    }
}

坑 4:忽略依赖顺序

症状:子任务 B 需要子任务 A 的输出,但 B 先执行了,拿到的是空数据。

// 并行执行有依赖的任务(会失败)
go execute(subtask1)  // 产生 company_info
go execute(subtask2)  // 消费 company_info(会失败!)

// 拓扑排序后执行
ordered := topologicalSort(subtasks)
for _, subtask := range ordered {
    execute(subtask)
}

10.9 什么时候用 Planning?

不是所有任务都需要规划。

任务类型用 Planning?原因
"今天天气怎么样"直接查就行
"帮我写个函数"ReAct 就够
"研究这家公司写报告"需要分解
"分析这个代码库的架构"需要分解
"对比这三个方案的优劣"需要分解

经验法则

  • 单步能完成的 → 不需要
  • 需要多次搜索/分析的 → 需要
  • 输出要有结构的 → 需要
  • 涉及多个维度的 → 需要

还有一个更简单的判断:

如果你让一个实习生做这件事,你会不会先跟他列个大纲?如果会,那 Agent 也需要 Planning。


10.10 其他框架怎么做?

Planning 是通用模式,不是 Shannon 专属。各家都有实现:

框架实现方式特点
LangGraphPlan-and-Execute 节点可视化,流程可控
AutoGPTTask Queue高度自主,但不稳定
CrewAITask 分配偏向团队协作隐喻
OpenAI Assistants内置 Planning(有限)黑盒,不可控

核心逻辑都一样:分解 → 执行 → 评估 → 迭代。

差别在于:

  • 分解的粒度和控制
  • 迭代的触发条件
  • 护栏的实现方式

划重点

  1. Planning 核心:复杂任务先分解,再执行,边做边评估
  2. 任务分解:声明 Produces/Consumes 建立依赖,声明 Boundaries 防止重叠
  3. 执行策略:无依赖用 parallel,有依赖用 DAG(拓扑排序)
  4. 覆盖度评估:LLM 判断 + 确定性护栏,双保险
  5. 终止条件:质量达标、预算耗尽、最大迭代——三选一触发停止

Shannon Lab(10 分钟上手)

本节帮你在 10 分钟内把本章概念对应到 Shannon 源码。

必读(1 个文件)

  • strategies/dag.go:找 DAGWorkflow 函数,看它怎么调用 DecomposeTask 分解任务、根据 hasDependencies 选择执行策略、最后调用 ReflectOnResult 做质量评估

选读深挖(2 个,按兴趣挑)


练习

练习 1:设计分解

把"帮我研究 Anthropic,写一份 2000 字的公司分析"这个任务手动分解:

  • 列出 4-6 个子任务
  • 标注每个子任务的 Produces 和 Consumes
  • 画出依赖图(谁依赖谁)
  • 决定用 parallel 还是 sequential

练习 2:源码阅读

strategies/dag.go 里的 executeHybridPattern 函数:

  1. 它怎么处理有依赖的任务?
  2. DependencyWaitTimeout 是干什么的?
  3. 如果一个依赖任务失败了,后续任务会怎样?

练习 3(进阶):设计护栏

设计一个"Token 预算耗尽"的终止条件:

  • 写出判断逻辑的伪代码
  • 思考:这个条件应该在循环的什么位置检查?
  • 思考:预算耗尽时,应该返回已有结果还是报错?

延伸阅读


下一章预告

到这里,Agent 已经会规划、会执行了。但有个问题:它怎么知道自己做得好不好?

一个不会反思的 Agent,就像一个从不检查代码的程序员——早晚要出事。

下一章我们来聊 Reflection 模式:怎么让 Agent 学会自我审视,评估输出质量,不达标就带反馈重试。

第 11 章见。

引用本文 / Cite
Zhang, W. (2026). 第 10 章:Planning 模式. In AI Agent 架构:从单体到企业级多智能体. https://waylandz.com/ai-agent-book/第10章-Planning模式
@incollection{zhang2026aiagent_第10章_Planning模式,
  author = {Zhang, Wayland},
  title = {第 10 章:Planning 模式},
  booktitle = {AI Agent 架构:从单体到企业级多智能体},
  year = {2026},
  url = {https://waylandz.com/ai-agent-book/第10章-Planning模式}
}