从零打造 AI Agent (三)—— Plan Mode:复杂任务的安全执行

1. 前言

上篇文章我们为 Agent 加入了记忆系统,但还有一个问题:对于复杂任务,Agent 可能直接动手,导致修改范围过大、风险不可控

本文将实现 Plan Mode(计划模式),让 Agent 在执行前先探索代码库、生成计划,用户批准后再执行。


2. 为什么需要 Plan Mode?

2.1 普通模式的问题

markdown 复制代码
用户: 重构登录模块
Agent: 直接开始修改...
       - 修改了 auth.py
       - 修改了 user.py
       - 修改了 database.py
       - 修改了 ...

用户往往在 Agent 改了一大堆文件后才发现不对劲,但为时已晚。

2.2 Plan Mode 的优势

makefile 复制代码
用户: 重构登录模块
Agent: [进入计划模式]
       1. 探索代码库,了解登录模块结构
       2. 生成重构计划
       3. 展示计划,等待批准
       
用户: 批准
Agent: [开始执行计划]
       - 修改 auth.py
       - 修改 user.py
       仅此而已,风险可控

2.3 模式对比

特性 Normal Mode Plan Mode
执行方式 直接执行 探索 → 计划 → 批准 → 执行
适用场景 简单任务 复杂/多文件修改
用户控制 事后反馈 事前批准
风险控制

3. 状态机设计

Plan Mode 的核心是一个状态机,控制整个流程:

ini 复制代码
                    ┌─────────────┐
                    │    IDLE     │ (普通模式)
                    └──────┬──────┘
                           │ run(plan_mode=True)
                           ▼
                    ┌─────────────┐
              ┌───▶│  EXPLORING  │ ◀──────┐
              │    └──────┬──────┘        │
              │           │ 探索完成       │ RESTART
              │           ▼               │
              │    ┌─────────────┐        │
              │    │  PLANNING   │────────┘
              │    └──────┬──────┘
              │           │ 计划生成
              │           ▼
              │    ┌─────────────┐
              │    │  PENDING    │──APPROVE──┐
              │    │ _APPROVAL   │           │
              │    └─────────────┘           │
              │           │                  │
              │    REJECT │                  │
              │           ▼                  │
              │    ┌─────────────┐           │
              └────│  ABANDONED  │           │
                   └─────────────┘           │
                                             ▼
                                    ┌─────────────┐
                                    │  EXECUTING  │──完成──▶ COMPLETED
                                    └─────────────┘

3.1 PlanState 枚举

python 复制代码
class PlanState(Enum):
    IDLE = "idle"              # 空闲(普通模式)
    EXPLORING = "exploring"    # 探索阶段
    PLANNING = "planning"     # 规划阶段
    PENDING_APPROVAL = "pending"  # 等待批准
    EXECUTING = "executing"   # 执行阶段
    COMPLETED = "completed"    # 完成
    ABANDONED = "abandoned"    # 放弃

4. 核心数据结构

4.1 Plan 数据结构

python 复制代码
@dataclass
class Plan:
    task: str                  # 原始任务
    steps: list[PlanStep]     # 具体步骤
    prerequisites: list[str]  # 前置条件
    risks: list[str]          # 潜在风险
    exploration_findings: str # 探索发现
    reasoning: str            # 规划推理

4.2 PlanStep 数据结构

python 复制代码
@dataclass
class PlanStep:
    id: int
    description: str           # 步骤描述
    file_path: str | None     # 涉及的文件
    action: str | None        # 具体操作

5. 核心实现

5.1 PlanMode 类

python 复制代码
class PlanMode:
    """计划模式管理器"""

    def __init__(self, agent):
        self.agent = agent
        self.state = PlanState.IDLE
        self.current_plan: Plan | None = None
        self.exploration_findings = ""

    def enter_plan_mode(self, task: str):
        """进入计划模式"""
        self.state = PlanState.EXPLORING
        self.current_task = task

    def set_exploring(self):
        self.state = PlanState.EXPLORING

    def set_planning(self):
        self.state = PlanState.PLANNING

    def set_pending_approval(self, plan: Plan):
        self.state = PlanState.PENDING_APPROVAL
        self.current_plan = plan

    def approve(self):
        self.state = PlanState.EXECUTING

    def reject(self):
        self.state = PlanState.ABANDONED

    def complete(self):
        self.state = PlanState.COMPLETED

5.2 工作流程

python 复制代码
def run_plan_mode(self, user_input: str) -> str:
    """运行计划模式"""

    # 1. 进入计划模式
    self.plan_mode.enter_plan_mode(user_input)

    # 2. 探索阶段 - 了解代码库结构
    self.plan_mode.set_exploring()
    exploration_findings = self._explore_codebase(user_input)

    # 3. 规划阶段 - 生成实施计划
    self.plan_mode.set_planning()
    plan = self._generate_plan(user_input, exploration_findings)

    # 4. 展示计划,等待批准
    self.plan_mode.set_pending_approval(plan)
    self._show_plan(plan)
    approved = Confirm.ask("是否批准执行此计划?")

    if not approved:
        self.plan_mode.reject()
        return "计划已取消"

    # 5. 执行计划
    self.plan_mode.approve()
    return self._execute_plan(plan)

6. 探索阶段

6.1 新增探索工具

为了在执行计划前了解代码库,我们添加了三个探索工具:

工具 功能
grep 正则搜索文件内容
find_files 按名称查找文件
read_multiple_files 批量读取文件

6.2 工具实现

python 复制代码
# grep 搜索
@staticmethod
def grep(params: dict) -> ToolResult:
    """在指定目录下搜索匹配的文件内容"""
    path = params.get("path", ".")
    pattern = params.get("pattern", "")
    file_pattern = params.get("file_pattern", "*")

    # 使用正则搜索文件
    regex = re.compile(pattern)
    matches = []

    for f in Path(path).rglob(file_pattern):
        if f.is_file():
            try:
                content = f.read_text(encoding='utf-8')
                for i, line in enumerate(content.split('\n'), 1):
                    if regex.search(line):
                        matches.append(f"{f}:{i}: {line.rstrip()}")
            except:
                pass

    return ToolResult(
        success=True,
        content=f"找到 {len(matches)} 处匹配:\n" + "\n".join(matches[:50])
    )

# find_files 查找
@staticmethod
def find_files(params: dict) -> ToolResult:
    """按名称查找文件"""
    path = params.get("path", ".")
    name = params.get("name", "*")
    file_type = params.get("type", "f")

    results = []
    for f in Path(path).rglob(name):
        if file_type == "f" and f.is_file():
            results.append(str(f))
        elif file_type == "d" and f.is_dir():
            results.append(str(f) + "/")

    return ToolResult(
        success=True,
        content=f"找到 {len(results)} 个文件:\n" + "\n".join(results[:50])
    )

# read_multiple_files 批量读取
@staticmethod
def read_multiple_files(params: dict) -> ToolResult:
    """批量读取多个文件"""
    paths = params.get("paths", [])

    results = []
    for p in paths:
        path = Path(p)
        if path.exists():
            results.append(f"=== {p} ===\n{path.read_text(encoding='utf-8')}")
        else:
            results.append(f"=== {p} ===\n[文件不存在]")

    return ToolResult(success=True, content="\n\n".join(results))

6.3 探索流程

python 复制代码
def _explore_codebase(self, task: str) -> str:
    """探索代码库,收集相关信息"""

    findings = []

    # 1. 列出项目结构
    structure = self.tools.execute("list_dir", {"path": "."})
    findings.append(f"项目结构:\n{structure}\n")

    # 2. 搜索相关文件
    keywords = self._extract_keywords(task)
    for kw in keywords:
        files = self.tools.execute("find_files", {"path": ".", "name": f"*{kw}*"})
        findings.append(f"相关文件 ({kw}):\n{files}\n")

    # 3. 读取关键代码
    # ... 根据搜索结果决定要读取的文件

    return "\n".join(findings)

7. 规划阶段

7.1 生成计划

python 复制代码
def _generate_plan(self, task: str, exploration_findings: str) -> Plan:
    """基于探索发现生成实施计划"""

    prompt = f"""用户请求: {task}

探索发现:
{exploration_findings}

请生成一个详细的实施计划,包括:
1. 具体步骤(每个步骤涉及哪个文件,做什么修改)
2. 前置条件
3. 潜在风险

以结构化格式返回。"""

    response = self.llm.create_message(
        messages=[{"role": "user", "content": prompt}],
        tools=[],
        system="你是一个规划助手,负责生成详细的实施计划。"
    )

    return self._parse_plan_response(response)

7.2 计划展示

python 复制代码
def _show_plan(self, plan: Plan):
    """以 Markdown 格式展示计划"""

    print("\n" + "=" * 60)
    print("📋 实施计划")
    print("=" * 60)

    print(f"\n📌 任务: {plan.task}\n")

    print("### 步骤\n")
    for i, step in enumerate(plan.steps, 1):
        print(f"{i}. {step.description}")
        if step.file_path:
            print(f"   📁 文件: {step.file_path}")
        if step.action:
            print(f"   ⚡ 操作: {step.action}")
        print()

    if plan.prerequisites:
        print("### 前置条件\n")
        for p in plan.prerequisites:
            print(f"- {p}")
        print()

    if plan.risks:
        print("### 潜在风险\n")
        for r in plan.risks:
            print(f"- ⚠️ {r}")
        print()

    print("=" * 60 + "\n")

8. 工具集成

8.1 工具注册

python 复制代码
# 注册探索工具
registry.register(Tool(
    name="grep",
    description="在目录中搜索匹配正则表达式的内容",
    parameters={
        "path": {"type": "string", "description": "搜索路径", "default": "."},
        "pattern": {"type": "string", "description": "正则表达式"},
        "file_pattern": {"type": "string", "description": "文件模式", "default": "*"},
    },
    handler=ToolHandlers.grep
))

registry.register(Tool(
    name="find_files",
    description="按名称查找文件",
    parameters={
        "path": {"type": "string", "description": "搜索路径", "default": "."},
        "name": {"type": "string", "description": "文件名模式"},
        "type": {"type": "string", "description": "类型: f(文件) 或 d(目录)", "default": "f"},
    },
    handler=ToolHandlers.find_files
))

registry.register(Tool(
    name="read_multiple_files",
    description="批量读取多个文件",
    parameters={
        "paths": {"type": "array", "description": "文件路径列表"},
    },
    handler=ToolHandlers.read_multiple_files
))

9. 使用方式

9.1 CLI 命令

bash 复制代码
# 普通模式(直接执行)
用户: 添加用户登录功能

# 计划模式(先探索再执行)
用户: /plan 添加用户登录功能

9.2 代码调用

python 复制代码
# 进入 Plan Mode
agent = Agent()
response = agent.run("重构登录模块", plan_mode=True)

9.3 执行效果

markdown 复制代码
╭───────────────────────────────────────╮
│      Plan Mode - 复杂任务安全执行      │
╰───────────────────────────────────────╯

👤 User: /plan 重构登录模块

📋 正在探索代码库...
   - 列出项目结构
   - 搜索相关文件
   - 读取关键代码

📋 正在生成计划...

============================================================
📋 实施计划
============================================================

📌 任务: 重构登录模块

### 步骤

1. 提取认证逻辑到独立模块
   📁 文件: auth.py
   ⚡ 操作: 重构

2. 更新用户模型引用
   📁 文件: models/user.py
   ⚡ 操作: 修改

3. 更新相关测试
   📁 文件: tests/test_auth.py
   ⚡ 操作: 修改

### 前置条件

- 备份现有代码
- 确保测试用例覆盖登录功能

### 潜在风险

- ⚠️ 可能影响依赖旧接口的其他模块
- ⚠️ 需要同步更新 API 文档

============================================================

是否批准执行此计划? [y/N]: y

🤖 Agent: 开始执行计划...
   ✅ 步骤 1/3: 提取认证逻辑
   ✅ 步骤 2/3: 更新用户模型
   ✅ 步骤 3/3: 更新测试

10. 与 Claude Code 的对应关系

本项目 Claude Code
plan.py Plan Mode 组件
EXPLORING Research 阶段
PLANNING Planning 阶段
PENDING_APPROVAL Plan 展示给用户
EXECUTING 执行用户批准的计划

11. 架构总结

scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                        Agent                                │
│                      (ReAct 核心循环)                        │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │LLMClient    │  │ToolRegistry │  │ContextManager       │  │
│  │(LLM 交互)    │  │(工具系统)    │  │(上下文管理)           │  │
│  └──────┬──────┘  └──────┬──────┘  └──────────┬──────────┘  │
│         │                │                     │            │
│         └────────────────┼─────────────────────┘            │
│                          │                                  │
│                          ▼                                  │
│                   ┌─────────────┐                           │
│                   │  PlanMode   │                           │
│                   │ (状态机)     │                           │
│                   └──────┬──────┘                           │
│                          │                                  │
│         ┌────────────────┼────────────────┐                 │
│         ▼                ▼                ▼                 │
│   ┌──────────┐     ┌──────────┐     ┌──────────┐            │
│   │EXPLORING │     │ PLANNING │     │ PENDING  │            │
│   │(探索)     │     │ (规划)   │     │ (等待)    │            │
│   └──────────┘     └──────────┘     └──────────┘            │
└─────────────────────────────────────────────────────────────┘

12. 待优化方向

  • 支持计划修改(用户指定修改某些步骤)
  • 计划持久化(保存/加载计划)
  • 增量执行(逐步骤确认)
  • 回滚机制(执行失败时回退)
  • 探索策略优化(更智能的文件搜索)

13. 下一步

我们已经实现了:

  • ✅ 最小可运行 Agent
  • ✅ 记忆系统
  • ✅ Plan Mode

下一个里程碑我们将实现 流式输出,让 Agent 的响应更加实时。


如果对你有帮助,欢迎点赞、评论、转发。 关注我,持续更新 AI Agent 从零到一系列文章。

相关推荐
颜进强2 小时前
Claude Code -16 文件引用与加载机制完整实践:从 CLAUDE.md 到 Skills 与 Subagents
前端·后端·ai编程
LienJack2 小时前
《Re0 Build Harness》第四章 Harness 基础定义:模型外部的控制系统
agent
悟空码字2 小时前
当 AI 遇到真正的编程痛点,Codex 攻克 5 类核心难题总结
aigc·openai·ai编程
小小神仙2 小时前
ECC:怎么让 Claude Code 变成你的全栈搭档
程序员·aigc·ai编程
mengfei00532 小时前
RAGFlow:深度文档理解驱动的开源 RAG 引擎
ai编程
HelloDong2 小时前
Codex CLI 实战指南:5 月连发 6 次更新,把 GPT-5.5 装进终端的完整工作流(含跟 Claude Code 搭配方案)
ai编程
蛤密呱2 小时前
LangGraph:工具调用与条件边 - 附简单ReAct代码示例
agent
canyu2 小时前
从零设计一个自适应挖需的 AI 提示词系统:多轮对话 + 动态维度
agent