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 从零到一系列文章。