在前面的文章中,我们已经讨论了 AI Agent 的记忆系统和工具调用体系。今天,我想分享一下如何实现规划系统,让 AI Agent 能够更智能地完成复杂任务。
从一个真实需求说起
还记得前段时间,我在开发一个代码重构助手时遇到的场景:
plaintext
用户:帮我重构这个项目的日志系统,统一使用结构化日志,添加链路追踪。
助手:好的,我来帮你重构。首先修改 logger.py...
(开始修改代码)
用户:等等,你这样直接改可能会影响到其他模块,能不能先分析一下影响范围?
助手:抱歉,你说得对。让我重新规划一下...
这个场景让我意识到:AI Agent 需要像人类工程师一样,在执行任务前先做好规划。不能一上来就写代码,而是要:
- 分析需求和影响范围
- 制定详细的执行计划
- 按步骤有序地执行
- 及时处理意外情况
规划系统的设计
经过几轮迭代,我设计了一个相对完善的规划系统:
python
from typing import List, Dict, Any
from enum import Enum
from datetime import datetime
from pydantic import BaseModel
class TaskStatus(Enum):
PENDING = "pending"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
FAILED = "failed"
BLOCKED = "blocked"
class TaskStep(BaseModel):
id: str
description: str
status: TaskStatus
dependencies: List[str]
estimated_time: float
actual_time: float = 0
result: Any = None
error: str = None
class Task(BaseModel):
id: str
name: str
description: str
steps: List[TaskStep]
context: Dict[str, Any]
created_at: datetime
updated_at: datetime
status: TaskStatus
class PlanningSystem:
def __init__(
self,
llm,
tool_registry,
memory_system
):
self.llm = llm
self.tool_registry = tool_registry
self.memory_system = memory_system
self.tasks: Dict[str, Task] = {}
async def plan_task(
self,
description: str,
context: Dict[str, Any] = None
) -> Task:
# 1. 分析任务,生成步骤
steps = await self._generate_steps(
description,
context
)
# 2. 创建任务
task = Task(
id=self._generate_id(),
name=self._extract_name(description),
description=description,
steps=steps,
context=context or {},
created_at=datetime.now(),
updated_at=datetime.now(),
status=TaskStatus.PENDING
)
# 3. 存储任务
self.tasks[task.id] = task
return task
async def execute_task(
self,
task_id: str
) -> Task:
task = self.tasks[task_id]
task.status = TaskStatus.IN_PROGRESS
try:
# 1. 获取可执行的步骤
steps = self._get_executable_steps(task)
# 2. 并行执行步骤
results = await asyncio.gather(*[
self._execute_step(task, step)
for step in steps
])
# 3. 更新任务状态
task.updated_at = datetime.now()
if all(r.status == TaskStatus.COMPLETED for r in results):
task.status = TaskStatus.COMPLETED
elif any(r.status == TaskStatus.FAILED for r in results):
task.status = TaskStatus.FAILED
return task
except Exception as e:
task.status = TaskStatus.FAILED
raise
async def _generate_steps(
self,
description: str,
context: Dict[str, Any]
) -> List[TaskStep]:
# 使用 LLM 分析任务,生成步骤
response = await self.llm.plan(
task=description,
context=context,
available_tools=self.tool_registry.list_tools()
)
# 解析响应,创建步骤
steps = []
for step in response.steps:
steps.append(TaskStep(
id=self._generate_id(),
description=step.description,
status=TaskStatus.PENDING,
dependencies=step.dependencies,
estimated_time=step.estimated_time
))
return steps
def _get_executable_steps(
self,
task: Task
) -> List[TaskStep]:
# 找出所有依赖已完成的步骤
executable = []
for step in task.steps:
if step.status != TaskStatus.PENDING:
continue
if all(
self._get_step_by_id(task, dep).status == TaskStatus.COMPLETED
for dep in step.dependencies
):
executable.append(step)
return executable
async def _execute_step(
self,
task: Task,
step: TaskStep
) -> TaskStep:
start_time = datetime.now()
try:
# 1. 准备执行上下文
context = self._prepare_context(task, step)
# 2. 使用 LLM 决定使用哪些工具
tools = await self.llm.select_tools(
step=step,
context=context,
available_tools=self.tool_registry.list_tools()
)
# 3. 执行工具调用
results = []
for tool in tools:
result = await self.tool_registry.get_tool(
tool.name
).execute(**tool.parameters)
results.append(result)
# 4. 更新步骤状态
step.status = TaskStatus.COMPLETED
step.result = results
except Exception as e:
step.status = TaskStatus.FAILED
step.error = str(e)
finally:
step.actual_time = (
datetime.now() - start_time
).total_seconds()
return step
def _prepare_context(
self,
task: Task,
step: TaskStep
) -> Dict[str, Any]:
# 合并任务上下文和前置步骤的结果
context = task.context.copy()
# 添加依赖步骤的结果
for dep_id in step.dependencies:
dep_step = self._get_step_by_id(task, dep_id)
if dep_step.result:
context[f"step_{dep_id}_result"] = dep_step.result
return context
使用示例:
python
# 初始化规划系统
planner = PlanningSystem(
llm=ChatGPT(),
tool_registry=tool_registry,
memory_system=memory_system
)
# 创建任务计划
task = await planner.plan_task(
description="""
重构项目的日志系统:
1. 统一使用结构化日志
2. 添加链路追踪
3. 确保向后兼容
""",
context={
"project_root": "./src",
"current_logger": "logging",
"target_logger": "structlog"
}
)
# 查看生成的步骤
for step in task.steps:
print(f"步骤:{step.description}")
print(f"依赖:{step.dependencies}")
print(f"预计耗时:{step.estimated_time}分钟")
print("---")
# 输出:
# 步骤:分析当前日志系统的使用情况
# 依赖:[]
# 预计耗时:15分钟
# ---
# 步骤:设计新的日志接口
# 依赖:['step_1']
# 预计耗时:30分钟
# ---
# 步骤:实现日志适配器
# 依赖:['step_2']
# 预计耗时:45分钟
# ---
# 步骤:修改日志配置
# 依赖:['step_3']
# 预计耗时:20分钟
# ---
# 步骤:更新依赖项
# 依赖:['step_4']
# 预计耗时:10分钟
# ---
# 步骤:添加单元测试
# 依赖:['step_3', 'step_4']
# 预计耗时:40分钟
# ---
# 步骤:进行集成测试
# 依赖:['step_5', 'step_6']
# 预计耗时:60分钟
# 执行任务
result = await planner.execute_task(task.id)
关键设计决策
在实现这个规划系统时,我做了几个重要的设计决策:
1. 任务分解
python
class TaskAnalyzer:
def __init__(self, llm):
self.llm = llm
async def analyze(
self,
description: str,
context: Dict[str, Any]
) -> List[Dict]:
# 使用 LLM 分析任务
response = await self.llm.analyze(
prompt=self._generate_prompt(
description,
context
)
)
# 验证步骤的合理性
steps = self._validate_steps(response.steps)
# 检查依赖关系
self._check_dependencies(steps)
return steps
def _generate_prompt(
self,
description: str,
context: Dict[str, Any]
) -> str:
return f"""
请分析以下任务,将其分解为具体的执行步骤:
任务描述:
{description}
上下文信息:
{json.dumps(context, indent=2)}
要求:
1. 每个步骤要具体且可执行
2. 明确步骤之间的依赖关系
3. 估计每个步骤的执行时间
4. 考虑可能的风险和回退方案
请以 JSON 格式返回结果。
"""
2. 并行执行
python
class StepExecutor:
def __init__(
self,
tool_registry,
max_concurrent: int = 5
):
self.tool_registry = tool_registry
self.semaphore = asyncio.Semaphore(max_concurrent)
async def execute_steps(
self,
steps: List[TaskStep],
context: Dict[str, Any]
) -> List[TaskStep]:
# 创建执行任务
tasks = [
self._execute_with_semaphore(step, context)
for step in steps
]
# 并行执行
return await asyncio.gather(*tasks)
async def _execute_with_semaphore(
self,
step: TaskStep,
context: Dict[str, Any]
) -> TaskStep:
async with self.semaphore:
return await self._execute_step(step, context)
3. 错误处理
python
class ErrorHandler:
def __init__(self, llm):
self.llm = llm
async def handle_error(
self,
step: TaskStep,
error: Exception,
context: Dict[str, Any]
) -> TaskStep:
# 分析错误
analysis = await self.llm.analyze_error(
step=step,
error=str(error),
context=context
)
if analysis.can_retry:
# 修改参数重试
step.parameters = analysis.fixed_parameters
return await self._retry_step(step)
elif analysis.can_workaround:
# 使用替代方案
return await self._execute_workaround(
step,
analysis.workaround
)
else:
# 标记失败
step.status = TaskStatus.FAILED
step.error = str(error)
return step
实践心得
在实现和使用这个规划系统的过程中,我总结了几点经验:
-
任务分解要合理
- 粒度要适中
- 依赖要清晰
- 考虑并行可能
-
执行要有弹性
- 支持并行执行
- 优雅处理错误
- 允许动态调整
-
状态管理很重要
- 及时更新状态
- 保存执行历史
- 支持断点恢复
写在最后
一个好的规划系统能让 AI Agent 的行为更加可控和高效。它就像是一个项目经理,懂得如何拆分任务、分配资源、控制风险。
在下一篇文章中,我会讲解如何实现 AI Agent 的多轮对话系统。如果你对规划系统的设计有什么想法,欢迎在评论区交流。