AI Agent Plan Mode实战:复杂任务安全执行全攻略
摘要
上一篇文章我们为Agent配置了记忆系统,算是解决了一个关键痛点。不过细心的读者可能已
上一篇文章我们为Agent配置了记忆系统,算是解决了一个关键痛点。不过细心的读者可能已经注意到:当Agent接到一个复杂任务时,它时常直接动手执行——这好比让一个初级开发人员未经架构评审就直接重构核心模块,还没弄清代码依赖关系就已经改了一堆文件。等你察觉异常,代码库可能已千疮百孔。
这正好引出了今天要讨论的核心机制:Plan Mode(计划模式)。核心思路很简单:让Agent在执行之前先花时间“侦察”——深度探索代码库、生成详细实施计划,等你确认后再动手。表面上看多了一个环节,但在实际工程中,这一环节恰恰是控制风险的关键杠杆。

为什么需要 Plan Mode?
先看一个典型场景。用户说“重构登录模块”,普通模式下的Agent会怎么操作?
用户: 重构登录模块
Agent: 直接开始修改...
- 修改了 auth.py
- 修改了 user.py
- 修改了 database.py
- 修改了 ...
发现问题了吗?问题在于,往往是Agent已经改了大量文件之后,你才意识到哪里不对——但这时候已经来不及纠正。这种“事后反馈”模式,对于简单任务还能勉强接受,可一旦涉及多文件、跨模块的修改,风险就会急速攀升。
那Plan Mode是怎么解决的呢?
用户: 重构登录模块
Agent: [进入计划模式]
1. 探索代码库,了解登录模块结构
2. 生成重构计划
3. 展示计划,等待批准
用户: 批准
Agent: [开始执行计划]
- 修改 auth.py
- 修改 user.py
仅此而已,风险可控
两者的区别一目了然。普通模式是“先斩后奏”,Plan Mode则是“先奏后斩”。
| 特性 | Normal Mode | Plan Mode |
|---|---|---|
| 执行方式 | 直接执行 | 探索 → 计划 → 批准 → 执行 |
| 适用场景 | 简单任务 | 复杂/多文件修改 |
| 用户控制 | 事后反馈 | 事前批准 |
| 风险控制 | 低 | 高 |
状态机设计:让流程可追踪
要实现Plan Mode,核心在于设计一个清晰的状态机。为什么必须用状态机?因为只有明确定义每个阶段的责任和转换条件,Agent的行为才是可预测、可管控的。
┌─────────────┐
│ IDLE │ (普通模式)
└──────┬──────┘
│ run(plan_mode=True)
▼
┌─────────────┐
┌───▶│ EXPLORING │ ◀──────┐
│ └──────┬──────┘ │
│ │ 探索完成 │ RESTART
│ ▼ │
│ ┌─────────────┐ │
│ │ PLANNING │────────┘
│ └──────┬──────┘
│ │ 计划生成
│ ▼
│ ┌─────────────┐
│ │ PENDING │──APPROVE──┐
│ │ _APPROVAL │ │
│ └─────────────┘ │
│ │ │
│ REJECT │ │
│ ▼ │
│ ┌─────────────┐ │
└────│ ABANDONED │ │
└─────────────┘ │
▼
┌─────────────┐
│ EXECUTING │──完成──COMPLETED
└─────────────┘
对应的状态枚举如下:
class PlanState(Enum):
IDLE = "idle" # 空闲(普通模式)
EXPLORING = "exploring" # 探索阶段
PLANNING = "planning" # 规划阶段
PENDING_APPROVAL = "pending" # 等待批准
EXECUTING = "executing" # 执行阶段
COMPLETED = "completed" # 完成
ABANDONED = "abandoned" # 放弃
从IDLE到最终完成,整个流程是线性的,但值得注意的是,如果用户拒绝了计划,Agent会进入ABANDONED状态。当然,也可以从RESTART分支重新开始探索——这给了用户调整任务描述的余地。
核心数据结构:计划长什么样?
有了状态机,还得有承载信息的“容器”。这里定义了Plan和PlanStep两个数据结构。
Plan 数据结构
@dataclass
class Plan:
task: str # 原始任务
steps: list[PlanStep] # 具体步骤
prerequisites: list[str] # 前置条件
risks: list[str] # 潜在风险
exploration_findings: str # 探索发现
reasoning: str # 规划推理
注意这里多了exploration_findings和reasoning两个字段,它们记录的是Agent在探索阶段的发现和规划阶段的推理过程。这些信息对于用户判断计划是否合理至关重要。
PlanStep 数据结构
@dataclass
class PlanStep:
id: int
description: str # 步骤描述
file_path: str | None # 涉及的文件
action: str | None # 具体操作
每个步骤都明确指向了具体的文件和操作,这才是可执行的计划,而不是笼统的“做某事”。
核心实现:PlanMode 类
有了数据和状态,接下来就是管理这一切的PlanMode类。
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
其实核心逻辑非常简洁:每个方法本质上就是在切换状态,同时记录当前正在处理的计划。真正的“重头戏”在于如何驱动这个流程。
工作流程
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)
这个流程实际上就是一个“探索→规划→批准→执行”的闭环。每一步都有明确的目标和产出。
探索阶段:让 Agent 先“踩点”
为了让Agent在执行前充分了解代码库,我们新增了三个探索工具。为什么是这三个?因为它们是代码探索的“铁三角”:搜内容、找文件、批量读。
| 工具 | 功能 |
|---|---|
grep | 正则搜索文件内容 |
find_files | 按名称查找文件 |
read_multiple_files | 批量读取文件 |
工具实现
# 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="nn".join(results))
探索流程
探索阶段的具体实现其实是一个组合策略:先列出项目结构,再根据任务关键词搜索相关文件,最后批量读取关键代码。
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)
规划阶段:从信息到行动
探索结束后,Agent手里已经握有了大量的上下文信息。接下来就是把这些信息转化为可执行的计划。
生成计划
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)
这里的关键在于prompt的设计。它明确要求LLM输出“具体步骤+前置条件+潜在风险”,并且要结构化。这一点非常重要——如果LLM输出的是散文式的描述,后续的程序解析就会非常痛苦。
计划展示
生成计划后,Agent会用Markdown格式将它展示给用户。为什么要用Markdown?因为它的可读性最好,无论是终端还是Web界面都能清晰呈现。
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")
工具集成:让探索成为 Agent 的“本能”
探索工具不是孤立的,它们需要被正式注册到Agent的工具系统中。
# 注册探索工具
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
))
使用方式:两种模式,两种体验
在CLI层面,用户可以通过简单的命令切换模式:
# 普通模式(直接执行)
用户: 添加用户登录功能# 计划模式(先探索再执行)
用户: /plan 添加用户登录功能
在代码层面,只需要传入一个参数即可:
# 进入 Plan Mode
agent = Agent()
response = agent.run("重构登录模块", plan_mode=True)
执行效果预览
实际运行时的效果非常直观:
╭───────────────────────────────────────╮
│ 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: 更新测试
与 Claude Code 的对应关系
如果你用过Claude Code,会发现Plan Mode的设计思路与其非常相似。两者都遵循“先探索、再规划、后执行”的模式。
| 本项目 | Claude Code |
|---|---|
| plan.py | Plan Mode 组件 |
| EXPLORING | Research 阶段 |
| PLANNING | Planning 阶段 |
| PENDING_APPROVAL | Plan 展示给用户 |
| EXECUTING | 执行用户批准的计划 |
架构总结
整体来看,Plan Mode是Agent体系中的一个独立模块,以状态机的形式与核心循环协同工作。
┌─────────────────────────────────────────────────────────────┐
│ Agent │
│ (ReAct 核心循环) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │LLMClient │ │ToolRegistry │ │ContextManager │ │
│ │(LLM 交互) │ │(工具系统) │ │(上下文管理) │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
│ │ │ │ │
│ └────────────────┼─────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ PlanMode │ │
│ │ (状态机) │ │
│ └──────┬──────┘ │
│ │ │
│ ┌────────────────┼────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │EXPLORING │ │ PLANNING │ │ PENDING │ │
│ │(探索) │ │ (规划) │ │ (等待) │ │
│ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
待优化方向
当然,目前的Plan Mode只是一个起点。以下几个方向是值得持续打磨的:
- 支持计划修改(用户指定修改某些步骤)
- 计划持久化(保存/加载计划)
- 增量执行(逐步骤确认)
- 回滚机制(执行失败时回退)
- 探索策略优化(更智能的文件搜索)
下一步
目前我们已经完成了三个核心模块:最小可运行Agent、记忆系统和今天的Plan Mode。下一个里程碑,我们将探索流式输出——让Agent的响应更加实时。这不仅仅是用户体验的提升,更是Agent从“批处理”走向“实时交互”的关键一步。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。