Agent 任务规划器(Planner)

🧠 Agent 任务规划器(Planner)完全解析

一、Planner 到底是什么?

一句话定义

Planner 是 Agent 的"大脑",负责把用户一句模糊需求,变成一组可执行、可验证、可控的步骤

核心职责


用户说:"帮我分析一下这个项目"
    ↓
Planner 输出:
1. 读取 README
2. 分析目录结构
3. 识别技术栈
4. 检查核心模块
5. 分析安全风险
6. 输出报告

关键原则

  • ✅ Planner 只规划,不执行
  • ✅ 每一步必须清晰、可验证
  • ✅ 高风险操作必须显式标记
  • ✅ 输出必须结构化(JSON)

二、Planner 的五层架构(Deep Agent 模型)

┌─────────────────────────────────────────────┐
│  1️⃣ Task Understanding(任务理解)           │
│     把用户输入转换为结构化任务规格           │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│  2️⃣ HTN 层级任务分解                          │
│     Hierarchical Task Networks               │
│     任务 = 方法 → 子任务                      │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│  3️⃣ Tool-Aware Decomposition(工具感知分解) │
│     根据可用工具选择最优实现路径             │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│  4️⃣ Plan Graph Construction(计划图构建)    │
│     生成带依赖关系的 DAG 任务图                │
└─────────────────────────────────────────────┘
                    ↓
┌─────────────────────────────────────────────┐
│  5️⃣ Self-Projection & Refinement(自预测优化)│
│     预判潜在问题,提前修正计划               │
└─────────────────────────────────────────────┘

三、核心数据结构

3.1 Step 定义(带依赖和验证)

type Step = {
  id: string;                    // 步骤唯一 ID
  title: string;                 // 步骤标题
  description: string;           // 详细说明
  dependsOn: string[];           // 依赖的前置步骤 ID
  owner: "researcher" | "implementer" | "tester" | "reviewer";
  tool?: string;                 // 使用的工具名
  verification: string;          // 如何验证这一步完成了
  checkpoint: string;            // 完成后的状态快照
  expectedOutput: string;        // 预期产出
  riskLevel: "low" | "medium" | "high";
  stopCondition?: string;        // 什么情况下停止
};

3.2 Plan 输出格式

{
  "goal": "分析项目代码质量",
  "steps": [
    {
      "id": "step_1",
      "title": "读取项目说明",
      "description": "读取 README 和 package 配置",
      "tool": "read_file",
      "expectedOutput": "项目基本信息",
      "riskLevel": "low"
    },
    {
      "id": "step_2",
      "title": "分析目录结构",
      "tool": "list_files",
      "riskLevel": "low"
    }
  ]
}

3.3 Task Graph(DAG 有向无环图)

一、什么是 DAG?

DAG = Directed Acyclic Graph(有向无环图)

拆开看:

  • 有向(Directed):边有方向,表示"谁依赖谁"
  • 无环(Acyclic):没有循环依赖,不会死锁
  • 图(Graph):节点 + 边的数据结构

直观理解

A → B → C
     ↘
      D → E

箭头表示"必须先完成"
A 完成后才能做 B
B 完成后才能做 C 和 D
D 完成后才能做 E

二、为什么任务规划要用 DAG?

问题场景

用户说:"帮我实现一个用户登录系统"

线性计划(❌ 有问题)

1. 设计数据库
2. 实现注册接口
3. 实现登录接口
4. 写单元测试
5. 写 API 文档
问题:

步骤 4(测试)必须等步骤 2+3 都完成才能开始
步骤 5(文档)其实可以等步骤 1 完成就开始写
线性计划无法表达并行和复杂依赖

DAG 计划(✅ 正确)

        [1. 设计数据库]
              ↓
        [2. 定义数据模型]
         ↙         ↘
[3. 实现注册]    [4. 实现登录]    [5. 写 API 文档]
         ↘         ↙
        [6. 单元测试]
              ↓
        [7. 集成测试]

Task Graph 的优势

优势说明
并行执行 独立节点可同时执行,大幅提速
清晰依赖 一眼看出谁等谁,避免顺序错误
失败隔离 某节点失败不影响无关分支
可恢复 从 checkpoint 恢复,不用重头开始
可解释 人类可读的任务树,方便 Debug
可评测 Progress = 完成节点数 / 总节点数

Task Graph(DAG)的本质

图结构表达任务之间的依赖关系,支持并行执行失败恢复进度追踪

核心价值

  • 🚀 并行执行,提速
  • 📊 清晰依赖,避免错误
  • 🔄 失败隔离,局部重跑
  • 📈 进度可量化,可追踪

四、执行流程(核心循环)

const plan = createPlan("trace replay");
const done = new Set<string>();

while (true) {
  // 1️⃣ 计算下一步可执行的步骤
  const ready = nextReadySteps(plan, done);
  if (ready.length === 0) break;
  
  for (const step of ready) {
    // 2️⃣ 执行步骤
    const result = await runStep(step);
    
    if (result.ok) {
      // 3️⃣ 成功:标记完成 + 保存 checkpoint
      done.add(step.id);
      saveCheckpoint(step.id, result.evidence);
      continue;
    }
    
    // 4️⃣ 失败:决策如何处理
    const decision = decideFailure(step, result);
    if (decision.type === "retry") await retry(step);
    if (decision.type === "replan") {
      // 5️⃣ 重规划:打补丁,不是推倒重来
      plan = applyGraphPatch(plan, decision.patch);
    }
    if (decision.type === "stop") {
      return summarizeBlocked(step, result);
    }
  }
}

五、失败处理策略(4 种)

策略触发条件处理方式
Retry 网络抖动、测试偶发失败 同一步重跑
Replan 发现原假设不成立、需要新增依赖 修改任务图(打补丁)
Fallback 原方案不可行 换更小实现方案
Stop 需要用户确认、权限不足 暂停等待人工输入

关键原则

Replan 是对 task graph 的补丁,不是把历史抹掉重来!


六、HTN 层级任务分解(核心算法)

HTN(Hierarchical Task Networks) 是现代 Deep Agents 的主流框架。

核心思想:任务 = 方法(methods)→ 子任务(subtasks)

为什么 HTN 重要

  • ✅ 可解释:你能看到任务树
  • ✅ 可回滚:某个节点失败 → 只重跑该节点
  • ✅ 便于评测:Progress Rate = 完成节点数 / 总节点数
  • ✅ 支持长期任务:树深越大,支持的动作序列越长

七、任务树生成算法(4 种)

7.1 Top-down 多轮推理

Task → Subtask 1, 2, 3…
Subtask 1 → Sub-subtask 1.1, 1.2…

类似深度优先搜索(DFS)+ 结果修正

7.2 Tree-of-Thought(ToT)多路径展开

展开多条候选路径 → LLM Judge 评分 → 选择最优 plan

7.3 Self-Projection(未来预测)

问 LLM:"假设你按这个计划执行,未来可能遇到什么问题?"

  • 依赖冲突
  • 工具不可用
  • 缺少文件/环境
  • 步骤顺序问题

7.4 约束求解(Constraint Satisfaction)

LLM 生成计划 → 约束求解器检查:
- 任务顺序是否符合依赖
- 工具是否足够执行
- 输出格式是否满足要求

八、风险控制(三级风险)

风险等级操作示例处理方式
Low 读取文件、搜索资料、分析文本 直接执行
Medium 创建文件、修改草稿、调用外部 API 记录日志
High 删除文件、覆盖代码、执行 shell、提交 git 必须人工确认

企业级强化要素

强化点作用
Plan Cache 同类任务不必重新规划,大幅降低成本
Dynamic Replanning 执行中根据上下文变化调整剩余节点
Mixture-of-Agents Planner 用 o3-mini,执行用便宜模型,反思用 PRM
Plan Compression 100 步压缩为 10 层抽象步骤,提升稳定性
任务树可视化 Debug、演示、风控审批(金融/能源必备)

关键设计决策总结

✅ Planner 只负责规划,不直接执行
✅ 计划必须结构化(JSON)
✅ 高风险步骤必须显式标记
✅ Replan 是打补丁,不是推倒重来
✅ 每个 step 必须有验证方式和 checkpoint
✅ 失败时明确选择 retry/replan/fallback/stop
✅ 使用 done 集合 + 依赖关系计算 ready steps

验收标准(Planner 合格的标准)

✅ 能把模糊目标拆成可执行步骤
✅ 能标记工具和风险等级
✅ 能输出结构化计划
✅ 能被校验和复用
✅ 支持依赖关系和并行执行
✅ 支持失败恢复和 replan

 一个简单案例

Model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp3.TaskModel
{
    public enum AiTaskStatus
    {
        /// <summary>
        /// 等待中
        /// </summary>
        Pending,    // 等待中
        Ready,      // 依赖已满足,可执行
        Running,    // 执行中
        Done,       // 已完成
        Failed,     // 失败
        Blocked,    // 被阻塞
        Skipped     // 已跳过
    }

    //风险等级
    public enum RiskLevel
    {
        Low,//
        Medium,//普通
        High //
    }


    public class TaskNode
    {
        /// <summary>
        /// 任务ID
        /// </summary>
        public string Id { get; set; } = Guid.NewGuid().ToString("N")[..8];
        /// <summary>
        /// 任务标题
        /// </summary>
        public string Title { get; set; } = "";
        /// <summary>
        /// 任务描述
        /// </summary>
        public string Description { get; set; } = "";
        /// <summary>
        /// 任务依赖项--可能依赖多个任务
        /// </summary>
        public List<string> DependsOn { get; set; } = new();
        /// <summary>
        /// 该任务调用的工具名称
        /// </summary>
        public string? ToolName { get; set; }
        /// <summary>
        /// 任务状态-默认等待执行
        /// </summary>
        public AiTaskStatus Status { get; set; } = AiTaskStatus.Pending;
        /// <summary>
        /// 风险登记 默认低风险
        /// </summary>
        public RiskLevel RiskLevel { get; set; } = RiskLevel.Low;

        /// <summary>
        /// 任务的拥有者        
        /// Researcher 🔍    调研、分析、收集信息    "现状是什么?"    搜索、读取、分析
        ///Implementer 🔨    实现、编码、创建内容    "怎么做出来?"    写代码、创建文件
        ///Tester 🧪    验证、测试、检查质量    "做得对吗?"    运行测试、校验
        ///Reviewer 👁️    审查、评估、把关    "做得好吗?"    代码审查、文档审核
        /// </summary>
        public string? Owner { get; set; }
        /// <summary>
        ///  // 如何验证这一步完成了
        /// </summary>
        public string Verification { get; set; } = "";
        /// <summary>
        ///  // 完成后的状态快照
        /// </summary>
        public string Checkpoint { get; set; } = "";
        public object? Outputs { get; set; }
        /// <summary>
        /// 错误信息
        /// </summary>
        public string? Error { get; set; }
        /// <summary>
        /// 重试次数
        /// </summary>
        public int RetryCount { get; set; } = 0;
        /// <summary>
        /// 允许的最大重试次数
        /// </summary>
        public int MaxRetries { get; set; } = 3;
        /// <summary>
        /// 可执行任务时的状态要求  等待 或 依赖已满足,可执行
        /// </summary>
        public bool CanExecute => Status == AiTaskStatus.Ready || Status == AiTaskStatus.Pending;
        /// <summary>
        /// 可结束任务时的状态要求  已完成 或 已跳过 
        /// </summary>
        public bool IsComplete => Status == AiTaskStatus.Done || Status == AiTaskStatus.Skipped;
    }


    /// <summary>
    /// 任务图(Task Graph)
    /// 表示一个完整的任务计划,由多个相互依赖的任务节点组成
    /// 使用有向无环图(DAG)结构管理任务依赖关系
    /// </summary>
    public class TaskGraph
    {
        /// <summary>
        /// 任务的最终目标
        /// 例如:"实现用户登录系统"、"分析项目代码质量"
        /// </summary>
        public string Goal { get; set; } = "";

        /// <summary>
        /// 任务节点字典
        /// Key: 节点 ID(唯一标识)
        /// Value: 任务节点对象(包含任务详情、状态、依赖等)
        /// 
        /// 使用 Dictionary 的原因:
        /// - 通过 ID 快速查找节点 O(1)
        /// - 便于依赖关系引用(dependsOn 存储的是 ID 列表)
        /// - 方便增删改操作
        /// </summary>
        public Dictionary<string, TaskNode> Nodes { get; set; } = new();

        /// <summary>
        /// 任务图创建时间
        /// 用于追踪计划生成时间,便于日志和调试
        /// </summary>
        public DateTime CreatedAt { get; set; } = DateTime.Now;

        /// <summary>
        /// 任务图最后更新时间
        /// 每次修改任务节点(状态变更、增删节点等)后应更新此字段
        /// 用于:
        /// - 判断计划是否过期
        /// - 追踪执行进度历史
        /// - 持久化时检测并发修改
        /// </summary>
        public DateTime UpdatedAt { get; set; } = DateTime.Now;

        /// <summary>
        /// 用户的原始输入提示词
        /// 例如:"帮我实现一个用户登录系统,包括注册、登录、密码找回功能"
        /// 
        /// 用途:
        /// - 重新规划时参考原始意图
        /// - 日志记录和问题排查
        /// - 用户查看历史任务时了解上下文
        /// </summary>
        public string? OriginalPrompt { get; set; }

        /// <summary>
        /// 总节点数量(只读计算属性)
        /// 等价于 Nodes.Count
        /// 
        /// 用途:
        /// - 计算进度百分比
        /// - 评估任务规模
        /// - 日志输出和 UI 展示
        /// </summary>
        public int TotalNodes => Nodes.Count;

        /// <summary>
        /// 已完成的节点数量(只读计算属性)
        /// 统计所有状态为 Done 或 Skipped 的节点
        /// 
        /// 用途:
        /// - 计算进度百分比
        /// - 判断任务是否全部完成
        /// - UI 进度条展示
        /// </summary>
        public int CompletedNodes => Nodes.Values.Count(n => n.IsComplete);

        /// <summary>
        /// 失败的节点数量(只读计算属性)
        /// 统计所有状态为 Failed 的节点
        /// 
        /// 用途:
        /// - 判断任务是否部分失败
        /// - 触发失败处理逻辑(retry/replan/stop)
        /// - 生成执行报告
        /// </summary>
        public int FailedNodes => Nodes.Values.Count(n => n.Status == AiTaskStatus.Failed);

        /// <summary>
        /// 任务执行进度百分比(只读计算属性)
        /// 计算公式:(已完成节点数 / 总节点数) × 100
        /// 
        /// 返回值范围:0.0 ~ 100.0
        /// - 0.0: 尚未开始
        /// - 50.0: 完成一半
        /// - 100.0: 全部完成
        /// 
        /// 边界处理:
        /// - 总节点数为 0 时返回 0,避免除零异常
        /// 
        /// 用途:
        /// - UI 进度条展示
        /// - 日志输出执行状态
        /// - 判断是否可以结束执行循环
        /// </summary>
        public double Progress => TotalNodes > 0
            ? (double)CompletedNodes / TotalNodes * 100
            : 0;

        /// <summary>
        /// 克隆当前任务图(深拷贝)
        /// 
        /// 用途:
        /// - Replan 时保留原始计划作为参考
        /// - 持久化前创建快照
        /// - 并行执行多个计划变体
        /// - 回滚到之前的状态
        /// 
        /// 拷贝内容:
        /// - Goal: 任务目标
        /// - Nodes: 节点字典(浅拷贝,节点对象本身不复制)
        /// - OriginalPrompt: 原始提示词
        /// - CreatedAt: 保持原创建时间
        /// - UpdatedAt: 设置为当前时间(表示新副本的创建时间)
        /// 
        /// 注意事项:
        /// - Nodes 是浅拷贝(Dictionary 复制,但 TaskNode 对象引用不变)
        /// - 如需完全深拷贝,需要遍历 Nodes 并克隆每个 TaskNode
        /// 
        /// 返回:新的 TaskGraph 实例
        /// </summary>
        public TaskGraph Clone()
        {
            return new TaskGraph
            {
                Goal = this.Goal,
                Nodes = new Dictionary<string, TaskNode>(this.Nodes),
                OriginalPrompt = this.OriginalPrompt,
                CreatedAt = this.CreatedAt,
                UpdatedAt = DateTime.Now  // 副本的更新时间设为当前
            };
        }
    }



    /// <summary>
    /// 任务执行结果
    /// 封装单次任务或整个任务图的执行结果,包含成功状态、输出数据、错误信息等
    /// 
    /// 设计模式:Result Pattern(结果模式)
    /// 优点:
    /// - 避免使用异常控制流程
    /// - 明确区分成功/失败两种状态
    /// - 携带丰富的上下文信息(证据、最终状态等)
    /// - 便于链式调用和模式匹配
    /// </summary>
    public class ExecutionResult
    {
        /// <summary>
        /// 执行是否成功
        /// true: 任务成功完成
        /// false: 任务失败(可能部分完成、完全失败、被阻塞等)
        /// 
        /// 用途:
        /// - 快速判断执行结果
        /// - 决定是否继续后续步骤
        /// - 生成最终报告
        /// </summary>
        public bool Success { get; set; }

        /// <summary>
        /// 执行产出物
        /// 类型:object?(可以是任意类型)
        /// 
        /// 可能的值:
        /// - TaskGraph: 完整的任务图(执行完成后)
        /// - string: 文本结果(如分析报告)
        /// - byte[]: 二进制数据(如生成的文件)
        /// - 匿名对象:结构化数据
        /// 
        /// 注意:
        /// - 成功时通常包含有效输出
        /// - 失败时可能包含部分结果(如已完成的中间产物)
        /// - 调用方需要根据实际场景进行类型转换
        /// </summary>
        public object? Output { get; set; }

        /// <summary>
        /// 执行证据
        /// 用于证明任务已完成或失败的文本描述
        /// 
        /// 内容示例:
        /// - "完成 5/5 个任务,所有测试通过"
        /// - "已生成报告文件:/path/to/report.md"
        /// - "API 响应:200 OK,耗时 120ms"
        /// - "测试覆盖率:85%"
        /// 
        /// 用途:
        /// - Checkpoint 保存(用于任务恢复)
        /// - 日志记录和审计
        /// - 向用户展示执行摘要
        /// - Planner 决策参考(replan 时分析失败原因)
        /// </summary>
        public string? Evidence { get; set; }

        /// <summary>
        /// 错误信息
        /// 仅在 Success = false 时有效
        /// 
        /// 内容:
        /// - 异常消息
        /// - 失败原因描述
        /// - 建议的解决方案
        /// 
        /// 示例:
        /// - "工具不存在:web_search"
        /// - "部分任务未完成:测试失败、代码审查未通过"
        /// - "用户未批准高风险操作"
        /// 
        /// 用途:
        /// - 日志记录
        /// - 向用户报告失败原因
        /// - FailureDecision 的输入(决定 retry/replan/stop)
        /// </summary>
        public string? Error { get; set; }

        /// <summary>
        /// 最终状态快照
        /// 执行结束时所有任务节点的状态字典
        /// 
        /// Key: 节点 ID
        /// Value: 任务节点对象(包含最终状态、错误信息、产出等)
        /// 
        /// 用途:
        /// - 调试和排查问题(查看每个节点的最终状态)
        /// - 持久化后恢复执行
        /// - 生成详细执行报告
        /// - Replan 时分析哪些节点需要调整
        /// 
        /// 注意:
        /// - 仅在完整任务图执行完成后设置
        /// - 单次节点执行时通常为 null
        /// </summary>
        public Dictionary<string, TaskNode>? FinalState { get; set; }

        /// <summary>
        /// 创建成功结果(静态工厂方法)
        /// 
        /// 用法示例:
        /// <code>
        /// var result = ExecutionResult.Ok(
        ///     output: graph,
        ///     evidence: "完成 10/10 个任务"
        /// );
        /// </code>
        /// 
        /// 参数说明:
        /// - output: 执行产出物(可选)
        /// - evidence: 执行证据(可选)
        /// 
        /// 返回:Success = true 的 ExecutionResult 实例
        /// </summary>
        public static ExecutionResult Ok(object? output = null, string? evidence = null)
            => new() { Success = true, Output = output, Evidence = evidence };

        /// <summary>
        /// 创建失败结果(静态工厂方法)
        /// 
        /// 用法示例:
        /// <code>
        /// var result = ExecutionResult.Fail(
        ///     error: "测试失败:3 个用例未通过",
        ///     output: partialGraph  // 可选:携带部分结果
        /// );
        /// </code>
        /// 
        /// 参数说明:
        /// - error: 错误描述(必填)
        /// - output: 部分产出物(可选,如已完成的中间结果)
        /// 
        /// 返回:Success = false 的 ExecutionResult 实例
        /// </summary>
        public static ExecutionResult Fail(string error, object? output = null)
            => new() { Success = false, Output = output, Error = error };
    }

    /// <summary>
    /// 失败处理决策
    /// 当任务节点执行失败时,由 Planner 生成的处理决策
    /// 
    /// 决策流程:
    /// 1. 节点执行失败 → 捕获异常
    /// 2. 调用 LLM 分析失败原因 → 生成 FailureDecision
    /// 3. 根据 Type 执行相应处理(retry/replan/fallback/stop)
    /// 
    /// 设计原则:
    /// - 不是所有失败都要重试
    /// - 根据失败类型选择最优策略
    /// - 避免无限重试循环
    /// </summary>
    public class FailureDecision
    {
        /// <summary>
        /// 决策类型
        /// 可选值:
        /// - "retry": 重试当前步骤(适用于偶发错误、网络抖动)
        /// - "replan": 重新规划(适用于原假设不成立、需要调整计划)
        /// - "fallback": 降级方案(适用于原方案不可行,换简单方案)
        /// - "stop": 停止执行(适用于需要用户确认、权限不足)
        /// 
        /// 决策依据:
        /// - 错误类型(可恢复 vs 不可恢复)
        /// - 重试次数(是否已达上限)
        /// - 任务关键性(是否阻塞后续步骤)
        /// - 用户配置(是否允许自动重试)
        /// </summary>
        public string Type { get; set; } = ""; // "retry" | "replan" | "fallback" | "stop"

        /// <summary>
        /// 决策理由
        /// LLM 生成的自然语言解释,说明为什么选择这个策略
        /// 
        /// 示例:
        /// - "网络超时错误,建议重试(retry)"
        /// - "发现缺少前置依赖,需要新增步骤(replan)"
        /// - "原方案需要管理员权限,建议降级为只读模式(fallback)"
        /// - "需要用户确认是否删除文件,暂停等待批准(stop)"
        /// 
        /// 用途:
        /// - 日志记录
        /// - 向用户解释决策原因
        /// - 调试时理解 Planner 的思考过程
        /// </summary>
        public string Reason { get; set; } = "";

        /// <summary>
        /// 图补丁(仅当 Type = "replan" 时有效)
        /// 包含对 TaskGraph 的修改指令
        /// 
        /// 修改类型:
        /// - AddNodes: 新增任务节点
        /// - RemoveNodes: 删除任务节点
        /// - UpdateDependencies: 修改依赖关系
        /// 
        /// 示例场景:
        /// 原计划:A → B → C
        /// 执行 B 时发现需要先做 D
        /// Patch: AddNodes = [D], UpdateDependencies = {B: [A, D]}
        /// 
        /// 注意:
        /// - Patch 是增量修改,不是替换整个图
        /// - 保持历史节点的 checkpoint 不丢失
        /// - 应用 Patch 后需要重新计算可执行节点
        /// </summary>
        public GraphPatch? Patch { get; set; }
    }

    /// <summary>
    /// 任务图补丁
    /// 用于对 TaskGraph 进行增量修改(replan 时使用)
    /// 
    /// 设计思想:
    /// - 不推倒重来,只修改必要的部分
    /// - 保留已完成节点的状态和 checkpoint
    /// - 最小化重规划的成本
    /// 
    /// 应用场景:
    /// - 执行中发现缺少前置步骤
    /// - 某步骤失败需要插入修复步骤
    /// - 用户变更需求,调整部分任务
    /// - 发现并行机会,优化依赖关系
    /// </summary>
    public class GraphPatch
    {
        /// <summary>
        /// 新增节点列表
        /// 需要添加到 TaskGraph 中的新任务节点
        /// 
        /// 示例场景:
        /// 原计划没有"安装依赖"步骤,执行时发现需要先安装
        /// AddNodes = [new TaskNode { Id = "install_deps", Title = "安装项目依赖" }]
        /// 
        /// 注意事项:
        /// - 新增节点的 DependsOn 必须引用已存在的节点 ID
        /// - 新增节点的状态应为 Pending
        /// - 需要在应用补丁后重新计算可执行节点
        /// </summary>
        public List<TaskNode> AddNodes { get; set; } = new();

        /// <summary>
        /// 删除节点 ID 列表
        /// 需要从 TaskGraph 中移除的任务节点 ID
        /// 
        /// 示例场景:
        /// 用户变更需求,不再需要"生成 PDF 报告"步骤
        /// RemoveNodes = ["generate_pdf_report"]
        /// 
        /// 注意事项:
        /// - 删除节点前需检查是否有其他节点依赖它
        /// - 已完成的节点通常不建议删除(可标记为 Skipped)
        /// - 删除节点后需要更新后续节点的依赖关系
        /// </summary>
        public List<string> RemoveNodes { get; set; } = new();

        /// <summary>
        /// 更新依赖关系
        /// Key: 需要修改依赖关系的节点 ID
        /// Value: 新的依赖节点 ID 列表
        /// 
        /// 示例场景:
        /// 原计划:test → implement
        /// 发现需要先安装依赖:test → [implement, install_deps]
        /// UpdateDependencies = { "test": ["implement", "install_deps"] }
        /// 
        /// 注意事项:
        /// - 新的依赖列表必须全部是图中存在的节点 ID
        /// - 不能创建循环依赖(会破坏 DAG 结构)
        /// - 修改依赖后需要重新拓扑排序
        /// 
        /// 用途:
        /// - 添加新的前置依赖
        /// - 移除不必要的依赖(优化并行度)
        /// - 调整执行顺序
        /// </summary>
        public Dictionary<string, List<string>> UpdateDependencies { get; set; } = new();
    }
}

main 入口

using AiTest;
using ConsoleApp3.TaskModel;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.ChatCompletion;
using Microsoft.SemanticKernel.Connectors.OpenAI;
using System.ComponentModel;

namespace ConsoleApp3
{
    internal class Program
    {
        static async Task Main(string[] args)
        {
            Console.WriteLine("╔════════════════════════════════════════╗");
            Console.WriteLine("║    SK 1.78.0 任务规划器简化示例        ║");
            Console.WriteLine("╚════════════════════════════════════════╝\n");

            // 1️⃣ 创建 Kernel(使用阿里云 DashScope)
            var kernel = CreateKernel();

            // 2️⃣ 注册自定义插件
            var simplePlugin = new SimplePlugin();
            var plugin = KernelPluginFactory.CreateFromObject(simplePlugin, pluginName: "simple");
            kernel.Plugins.Add(plugin);

            // 3️⃣ 创建简单任务图
            var graph = CreateSimpleGraph();
            PrintGraph(graph);

            // 4️⃣ 执行任务
            Console.WriteLine("\n🚀 开始执行任务...\n");
            var result = await ExecuteGraphAsync(kernel, graph);

            // 5️⃣ 输出结果
            Console.WriteLine($"\n{(result.Success ? "" : "")} {result.Evidence ?? result.Error}");
            Console.WriteLine($"最终进度:{graph.Progress:F1}%");
        }

        /// <summary>
        /// 创建 Kernel(使用阿里云 DashScope)
        /// </summary>
        static Kernel CreateKernel()
        {
            var builder = Kernel.CreateBuilder();

            builder.AddOpenAIChatCompletion(
                modelId: ConstParm.modelId,
                apiKey: ConstParm.apiKey,
                endpoint: new Uri(ConstParm.endpoint));

            return builder.Build();
        }

        /// <summary>
        /// 创建简单任务图(3 个节点演示)
        /// </summary>
        static TaskGraph CreateSimpleGraph()
        {
            var graph = new TaskGraph
            {
                Goal = "分析项目结构",
                OriginalPrompt = "帮我分析一下当前目录的 C# 项目结构"
            };

            // 节点 1: 读取项目文件(无依赖)
            graph.Nodes["read_project"] = new TaskNode
            {
                Id = "read_project",
                Title = "读取项目文件",
                Description = "读取 .csproj 文件了解项目配置",
                Owner = "researcher",
                ToolName = "simple",
                DependsOn = new List<string>(),
                RiskLevel = RiskLevel.Low
            };

            // 节点 2: 分析代码结构(依赖节点 1)
            graph.Nodes["analyze_code"] = new TaskNode
            {
                Id = "analyze_code",
                Title = "分析代码结构",
                Description = "分析项目中的类和方法",
                Owner = "researcher",
                ToolName = "simple",
                DependsOn = new List<string> { "read_project" },
                RiskLevel = RiskLevel.Low
            };

            // 节点 3: 生成报告(依赖节点 2)
            graph.Nodes["generate_report"] = new TaskNode
            {
                Id = "generate_report",
                Title = "生成分析报告",
                Description = "生成项目结构分析报告",
                Owner = "implementer",
                ToolName = "simple",
                DependsOn = new List<string> { "analyze_code" },
                RiskLevel = RiskLevel.Low
            };

            return graph;
        }

        /// <summary>
        /// 执行任务图(简化版)
        /// </summary>
        static async Task<ExecutionResult> ExecuteGraphAsync(Kernel kernel, TaskGraph graph)
        {
            var completed = new HashSet<string>();
            var chatService = kernel.GetRequiredService<IChatCompletionService>();
            int maxIterations = graph.TotalNodes * 2;
            int iteration = 0;

            while (iteration < maxIterations)
            {
                iteration++;

                // 1️⃣ 找出可执行的节点(依赖已满足)
                var readyNodes = graph.Nodes.Values
                    .Where(n => n.CanExecute && n.DependsOn.All(d => completed.Contains(d)))
                    .ToList();

                if (readyNodes.Count == 0)
                    break;

                // 2️⃣ 执行每个节点
                foreach (var node in readyNodes)
                {
                    Console.WriteLine($"  → 执行:{node.Title}");
                    node.Status = AiTaskStatus.Running;

                    try
                    {
                        // 调用 SK 执行
                        var prompt = $"""
                        请完成以下任务:
                        标题:{node.Title}
                        描述:{node.Description}
                        
                        请给出简洁的执行结果摘要(50 字以内)。
                        """;
                        var executionSettings = new OpenAIPromptExecutionSettings
                        {
                            Temperature = 0.7f,      // 创造性 (0~1)
                            MaxTokens = 1000,        // 最大输出 token
                            TopP = 0.9f              // 核采样
                        };

                        var response = await chatService.GetChatMessageContentAsync(
                        new ChatHistory(prompt),
                        executionSettings: executionSettings,
                        kernel  );
                        // 成功
                        node.Status = AiTaskStatus.Done;
                        node.Outputs = response.Content;
                        node.Checkpoint = $"完成于 {DateTime.Now:HH:mm:ss}";
                        completed.Add(node.Id);

                        Console.WriteLine($"  ✓ 完成:{node.Title}");
                    }
                    catch (Exception ex)
                    {
                        node.Status = AiTaskStatus.Failed;
                        node.Error = ex.Message;
                        node.RetryCount++;

                        Console.WriteLine($"  ✗ 失败:{node.Title} - {ex.Message}");

                        // 简单重试逻辑
                        if (node.RetryCount >= node.MaxRetries)
                        {
                            return ExecutionResult.Fail($"节点失败:{node.Title}", graph);
                        }
                    }
                }
            }

            // 检查结果
            var allDone = graph.Nodes.Values.All(n => n.IsComplete);

            return allDone
                ? ExecutionResult.Ok(graph, $"完成 {graph.CompletedNodes}/{graph.TotalNodes} 个任务")
                : ExecutionResult.Fail("部分任务未完成", graph);
        }

        /// <summary>
        /// 打印任务图
        /// </summary>
        static void PrintGraph(TaskGraph graph)
        {
            Console.WriteLine($"📊 任务图:{graph.Goal}");
            Console.WriteLine($"   节点数:{graph.TotalNodes}\n");

            foreach (var node in graph.Nodes.Values)
            {
                var deps = node.DependsOn.Count > 0
                    ? $" [依赖:{string.Join(", ", node.DependsOn)}]"
                    : "";

                Console.WriteLine($"   • {node.Title}{deps}");
            }
        }
    }

    /// <summary>
    /// 简单插件示例
    /// </summary>
    public class SimplePlugin
    {
        [KernelFunction("read_file"), Description("读取文件内容")]
        public async Task<string> ReadFile([Description("文件路径")] string path)
        {
            if (!File.Exists(path))
                throw new FileNotFoundException($"文件不存在:{path}");

            return await File.ReadAllTextAsync(path);
        }

        [KernelFunction("list_files"), Description("列出目录文件")]
        public string ListFiles([Description("目录路径")] string path)
        {
            if (!Directory.Exists(path))
                throw new DirectoryNotFoundException($"目录不存在:{path}");

            var files = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories);
            return $"找到 {files.Length} 个 C# 文件";
        }

        [KernelFunction("analyze_code"), Description("分析代码结构")]
        public async Task<string> AnalyzeCode([Description("项目路径")] string path)
        {
            await Task.Delay(100); // 模拟分析
            return "分析完成:发现 5 个类,20 个方法";
        }
    }
}
View Code

 

posted @ 2026-08-05 10:50  天才卧龙  阅读(38)  评论(0)    收藏  举报