1. 为什么我要从零开始构建AI Agent
作为一名长期从事系统开发的工程师,我最近陷入了某种"框架疲劳"——LangChain、AutoGen、CrewAI等AI Agent框架层出不穷,每个框架都宣称自己是最佳解决方案。但当我真正使用时,却发现这些框架就像黑盒子:我知道怎么调用API,却不清楚内部究竟如何运作。
这种认知失调最终促使我做出了一个反直觉的决定:抛开所有现成框架,用最基础的Python代码从零构建一个AI Agent。这不是为了重复造轮子,而是要通过亲手实现,真正理解Agent系统的核心机制。
提示:在技术领域,当我们说"理解"某个系统时,通常意味着能够回答三个层次的问题:它做什么(What)、怎么做(How),以及为什么这样做(Why)。大多数教程只解决了第一个问题。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 最小可行Agent的设计哲学
2.1 定义Agent的边界
在开始编码前,我强迫自己明确四个基本问题:
-
输入规范:仅接受自然语言任务描述,暂不考虑多模态输入。这看似限制了能力,实则划定了明确的问题边界。
-
状态设计:采用显式的状态机模型,包含任务目标(objective)、执行计划(plan)、中间结果(scratchpad)和历史记录(history)。
-
执行流程:严格划分为理解→规划→执行→评估四个阶段,每个阶段都有明确的输入输出。
-
终止条件:预设三种停止情形:任务完成、达到最大迭代次数、触发安全规则。
2.2 状态机的核心地位
我最初以为Agent的核心是LLM的prompt工程,但实践后发现状态管理才是系统的脊柱。一个好的状态设计应该像飞机的黑匣子,能够完整记录决策过程。以下是我的状态类实现:
python复制class AgentState:
def __init__(self, objective):
self.objective = objective # 原始任务描述
self.plan = [] # 执行步骤列表
self.current_step = 0 # 当前执行步骤
self.done = False # 完成标志
self.history = [] # 完整执行记录
def update(self, action, result):
self.history.append({
'step': self.current_step,
'action': action,
'result': result
})
这个简单的数据结构确保了系统的可观测性——任何时候我们都能通过检查state对象了解Agent的完整执行上下文。
3. 实现基础执行循环
3.1 最简化的while-loop架构
抛开所有花哨的功能,一个Agent本质上就是一个受控的循环。我的第一个版本仅有不到50行代码:
python复制def run_agent(task_description, max_steps=5):
state = AgentState(task_description)
# 硬编码的初始计划
state.plan = [
"理解任务需求",
"拆解执行步骤",
"执行具体操作",
"总结最终结果"
]
while not state.done and state.current_step < max_steps:
current_action = state.plan[state.current_step]
# 模拟LLM的思考过程
if current_action == "理解任务需求":
result = f"已理解任务:{state.objective}"
elif current_action == "拆解执行步骤":
result = "步骤已拆解:" + str(state.plan)
else:
result = f"已完成:{current_action}"
state.update(current_action, result)
# 简单的完成判断
if state.current_step == len(state.plan) - 1:
state.done = True
else:
state.current_step += 1
return state
这个实现虽然"愚蠢",但它清晰地展示了Agent的核心模式:
- 维护执行状态
- 按计划逐步执行
- 评估是否继续
3.2 执行流程的可视化
通过添加简单的日志输出,我们可以观察Agent的思考过程:
code复制==== 执行开始 ====
[步骤0] 理解任务需求 → 已理解任务:撰写产品介绍
[步骤1] 拆解执行步骤 → 步骤已拆解:['理解任务需求', '拆解执行步骤', '执行具体操作', '总结最终结果']
[步骤2] 执行具体操作 → 已完成:执行具体操作
[步骤3] 总结最终结果 → 已完成:总结最终结果
==== 执行结束 ====
这种透明度是使用现成框架时难以获得的。当出现问题时,我们可以精确定位到是哪个步骤的逻辑出现了偏差。
4. 关键设计决策与取舍
4.1 为什么选择显式状态管理
在早期实验中,我曾尝试让LLM通过prompt隐式维护状态。这种方式虽然代码更简单,但带来了两个严重问题:
- 调试困难:当Agent行为异常时,无法确定是prompt问题还是状态丢失
- 一致性风险:LLM可能在不同轮次中对同一任务产生不一致的理解
显式状态管理虽然增加了代码量,但带来了以下优势:
- 确定性:状态变更完全由代码控制
- 可观测性:随时可以检查完整状态
- 可持久化:状态对象可以序列化保存
4.2 执行循环的设计考量
在循环结构上,我特别关注了以下几个关键点:
- 终止条件检查:放在循环开头,避免多余执行
- 步骤隔离:每个步骤有独立的输入输出,减少副作用
- 历史记录:完整记录每个步骤的输入输出
这种设计使得Agent的行为完全可预测,也为后续添加重试机制、回滚等功能奠定了基础。
5. 常见问题与调试技巧
5.1 Agent陷入无限循环
即使是最简单的Agent也可能因为状态管理不当而无法终止。以下是几个排查方向:
- 检查终止条件:确保done标志在所有完成路径都被正确设置
- 验证步进逻辑:确认current_step在非完成情况下确实会递增
- 添加防护机制:强制设置max_steps参数
python复制# 改进后的终止检查
def should_stop(state, max_steps):
if state.done:
return True
if state.current_step >= max_steps:
state.done = True # 自动标记完成
return True
return False
5.2 状态不一致问题
当Agent的多个部分同时修改状态时,可能出现竞态条件。建议:
- 封装状态修改:通过方法而非直接属性访问
- 添加验证逻辑:在更新前检查状态合法性
- 实现快照功能:便于回滚到之前的状态
python复制class AgentState:
# ... 其他代码 ...
def validate(self):
if self.current_step >= len(self.plan):
raise ValueError("当前步骤超出计划范围")
def create_snapshot(self):
return deepcopy(self.__dict__)
6. 从简单到复杂的演进路径
现在这个基础Agent虽然功能有限,但它提供了清晰的扩展点:
- 动态规划:用LLM替代硬编码的plan生成
- 工具调用:在执行步骤中集成外部API
- 反思机制:在评估阶段加入自我改进逻辑
- 记忆系统:持久化历史记录供后续任务参考
例如,要添加动态规划能力,只需修改plan生成部分:
python复制def generate_plan(objective, llm_client):
prompt = f"""基于以下任务目标,生成执行步骤:
任务:{objective}
要求:
1. 每个步骤用简洁的动词开头
2. 步骤数量不超过5个
3. 输出格式为JSON列表"""
response = llm_client.complete(prompt)
return json.loads(response)
这种渐进式的扩展方式确保了我们始终对系统保持充分的理解和控制。
7. 工程师视角的AI开发心得
通过这个项目,我总结了几个与传统软件开发不同的AI系统特点:
- 非确定性管理:LLM的输出具有随机性,系统设计需要包含足够的确定性边界
- 观察优先于控制:相比精确控制每个输出,建立完善的观测体系更为重要
- 失败设计:必须明确界定什么是失败,以及系统应该如何应对
这些认知将指导我在后续开发中逐步添加更复杂的功能,如工具调用、多Agent协作等。整个开发过程已在GitHub公开,我会持续更新这个项目的进展。
