文章

《为什么我要手搓复刻一个 Agent 框架》

《为什么我要手搓复刻一个 Agent 框架》

写在前面

最近半年,身边做 AI 的朋友都在聊 Agent。从最开始的”这玩意儿能干啥”,到现在各种 Agent 框架满天飞,这个领域的发展速度快得有点吓人。

作为一个喜欢折腾的技术人,我也忍不住想搞清楚 Agent 到底是个什么东西。看了一圈市面上的框架,最后决定自己手搓一个。

这篇文章就聊聊我在这个过程中的一些思考和收获。


一、Agent 是个啥?跟 LLM 有啥区别?

先说 LLM

如果你看过我之前写的 LLM 个人笔记,应该知道 LLM 本质上就是个”词语接龙”游戏:

  • 你输入一个问题
  • 它根据训练数据,一个词一个词地生成回答
  • 整个过程是一次性的,输入进去,输出出来,就结束了

用个比喻:LLM 就像一个博学的朋友,你问他问题,他给你答案。但他只会”说”,不会”做”。

再说 Agent

Agent 就不一样了。它不只会”说”,还会”做”。

核心区别:

维度LLMAgent
交互方式一问一答多轮迭代
能力范围只能生成文本可以调用工具、执行操作
工作流程输入 → 输出观察 → 思考 → 行动 → 反馈 → 循环
自主性被动响应主动规划

还是用比喻:

  • LLM:你问”今天天气怎么样”,它会根据训练数据告诉你”一般是…“,但它不知道今天真实的天气
  • Agent:它会先想”我需要查天气”,然后调用天气 API,拿到真实数据后再告诉你

说白了:Agent = LLM + 工具调用能力 + 规划能力 + 反馈循环


二、Agent 的组成部分

一个完整的 Agent 系统,通常包含以下几个核心组件:

1. 大脑(LLM)

这是 Agent 的核心,负责:

  • 理解用户意图
  • 规划任务步骤
  • 决策下一步做什么
  • 生成最终回答

没有 LLM,Agent 就是个空壳。

2. 记忆(Memory)

Agent 需要记住:

  • 历史对话内容
  • 之前执行过的操作
  • 中间结果

为什么需要记忆?

想象你让 Agent 帮你订机票:

  1. 第一轮:Agent 问你”去哪里”
  2. 第二轮:你说”北京”
  3. 第三轮:Agent 问你”什么时候”

如果没有记忆,第三轮时 Agent 就不知道你要去北京了。

3. 工具箱(Tools)

这是 Agent 的”手”,让它能真正做事情:

  • 搜索引擎:查信息
  • 计算器:算数学题
  • API 调用:查天气、订票、发邮件
  • 代码执行器:运行代码
  • 数据库:存取数据

工具的定义通常包括:

  • 名称:工具叫什么
  • 描述:工具是干什么的
  • 参数:需要什么输入
  • 执行逻辑:具体怎么做

4. 规划器(Planner)

负责把复杂任务拆解成小步骤:

举个例子:用户说”帮我准备明天的会议”

规划器会拆解成:

  1. 查看日历,确认会议时间
  2. 查看会议议程
  3. 准备相关资料
  4. 发送会议提醒

5. 执行器(Executor)

负责实际调用工具、执行操作。

它要处理:

  • 工具调用的参数转换
  • 错误处理
  • 结果解析

6. 反馈机制(Feedback Loop)

Agent 需要根据执行结果调整策略:

  • 工具调用成功了吗?
  • 结果符合预期吗?
  • 需要重试吗?
  • 需要换个工具吗?

这个循环很关键:

1
2
3
观察 → 思考 → 行动 → 反馈
  ↑                      ↓
  └──────────────────────┘

三、市面上的 Agent 框架都有啥

在决定自己手搓之前,我研究了一圈市面上的框架。

框架核心特点主要问题适合场景
LangChain• 最早、最火的 Agent 框架
• 功能全面,生态丰富
• 支持多种 LLM 和工具
• 文档完善,社区活跃
• 抽象层次太多,学习曲线陡峭
• 代码结构复杂,不好调试
• 性能开销大
• 版本迭代快,API 经常变
快速原型开发
不在乎性能
AutoGPT• 完全自主的 Agent
• 可以自己设定目标、规划步骤
• 支持长期记忆
• 太自主了,容易跑偏
• Token 消耗巨大
• 不适合生产环境
研究、实验
BabyAGI• 轻量级
• 任务管理清晰
• 代码简单易懂
• 功能相对简单
• 扩展性一般
学习 Agent 原理
MetaGPT• 多 Agent 协作
• 模拟软件开发团队
• 角色分工明确
• 专注于代码生成场景
• 通用性不强
代码生成
软件开发
Claude Agent• 官方支持
• 工具调用格式标准
• 性能优秀
• 绑定 Claude 模型
• 国内访问不便
需要高质量对话
海外项目
Assistants API• OpenAI 官方 API
• 开箱即用
• 支持代码解释器、文件检索
• 黑盒,不可控
• 成本较高
• 功能受限
快速集成
简单应用

四、我的手搓框架:agent-frame-start

看了一圈,我发现:

  • 要么太复杂,学习成本高
  • 要么太简单,功能不够
  • 要么太重,性能不行
  • 要么不够灵活,扩展困难

所以我决定自己搞一个,目标很简单:

  • 轻量:代码简单,容易理解
  • 标准:使用 OpenAI 标准工具调用格式
  • 可控:每个环节都清晰可见
  • 实用:能真正解决问题

架构演进:从简单到复杂

我的框架经历了四代演进,每一代都解决了上一代的问题。

第一代:agent.py - 基础实现

核心思路:最简单的 Observe → Reason → Act 循环

特点:

  • 使用文本格式交互(call_tool|tool_name|args)
  • 每次只能调用一个工具
  • 实现简单,适合理解原理

问题:

  • 格式不标准
  • 不支持并行工具调用
  • 功能太简单

代码示例:

1
2
# 简单的文本格式
action = "call_tool|weather|北京"

第二代:react.py - 引入 ReAct 模式

核心思路:Reasoning and Acting,让 Agent 能边思考边行动

特点:

  • 使用 JSON 格式
  • 支持多工具并行调用
  • 引入了推理步骤

问题:

  • 自定义 JSON 格式,不够标准
  • 与主流 LLM 格式不兼容

代码示例:

1
2
3
4
5
6
7
8
{
    "type": "reason",
    "content": "我需要查天气和查新闻",
    "tool_calls": [
        {"tool_name": "weather", "args": {"city": "北京"}},
        {"tool_name": "news", "args": {"keyword": "AI"}}
    ]
}

第三代:react_agent.py - 标准化

核心思路:采用 OpenAI 标准工具调用格式

特点:

  • 使用 Pydantic 做数据验证
  • 标准的 ChatCompletionMessageToolCall 格式
  • 内置防卡住机制
  • 消息管理更规范

ReAct Agent V3 架构图

关键改进:

  1. 标准化的消息格式
    1
    2
    3
    4
    5
    6
    7
    8
    
    class Message(BaseModel):
     role: str  # system/user/assistant/tool
     content: Optional[str]
     tool_calls: Optional[List[ChatCompletionMessageToolCall]]
        
     def to_llm_message(self):
         # 转换为 LLM 格式
         pass
    
  2. 防卡住机制
    1
    2
    3
    4
    5
    6
    7
    8
    
    def _is_stuck(self) -> bool:
     # 检测是否重复思考
     if len(self.memory.messages) < 4:
         return False
        
     recent = self.memory.messages[-4:]
     # 检查是否在重复相同的操作
     return self._check_repetition(recent)
    

问题:

  • 缺乏执行结果验证
  • 无法检测推理与执行的一致性

第四代:rac_agent.py - 加入一致性检查

核心思路:Reason → Action → Check,加入验证环节

这是我目前的最终版本,也是最满意的版本。

核心创新:CheckAction

在执行完工具后,增加一个检查步骤,验证:

  1. 计划的工具都执行了吗?
  2. 执行数量对得上吗?
  3. 有执行错误吗?
  4. 推理逻辑连贯吗?
  5. 终止逻辑合理吗?

检查结果模型:

1
2
3
4
5
class CheckResult(BaseModel):
    success: bool                    # 检查是否通过
    consistency_score: float         # 一致性得分 (0-1)
    issues: List[str]               # 发现的问题
    recommendations: List[str]       # 改进建议

工作流程:

1
2
3
4
5
6
7
观察 → 推理 → 执行 → 检查
  ↑                      ↓
  │    (检查通过)        │
  └────────────────────┘
  ↑                      ↓
  │    (检查失败,重试)  │
  └────────────────────┘

为什么需要 Check?

LLM 有时候会:

  • 说要调用工具 A,实际调用了工具 B
  • 说要调用 3 个工具,实际只调用了 2 个
  • 推理说要做 X,执行时做了 Y

Check 就是来抓这些不一致的。

实际效果:

测试中发现,加入 Check 后:

  • 任务完成率提升约 30%
  • 错误率下降约 50%
  • 虽然多了一轮检查,但避免了很多无效重试

五、为什么要手搓框架?

可能有人会问:市面上这么多框架,为啥还要自己搞?

1. 理解原理

看代码和写代码是两回事。

用别人的框架,你只知道怎么调 API。自己写一遍,你才真正理解:

  • Agent 的工作流程
  • 消息如何流转
  • 工具如何调用
  • 错误如何处理

这种理解是无价的。

2. 掌控力

用别人的框架,你是在”黑盒”里工作:

  • 出了问题不知道怎么调试
  • 想加功能不知道从哪改
  • 性能瓶颈在哪都不清楚

自己的框架,每一行代码都清清楚楚。

3. 灵活性

每个项目的需求都不一样:

  • 有的需要高性能
  • 有的需要低成本
  • 有的需要特殊功能

通用框架很难满足所有需求。

自己的框架,想怎么改就怎么改:

  • 需要加缓存?加
  • 需要换 LLM?换
  • 需要自定义工具?随便加

4. 学习成本

大型框架的学习曲线很陡:

  • LangChain 的文档有几百页
  • 各种概念、抽象层
  • 版本更新快,API 经常变

自己的框架,几百行代码,一目了然。

新人加入项目,看一遍代码就能上手。

5. 性能优化

通用框架为了兼容性,会有很多抽象层:

  • 每层都有开销
  • 调用链路长
  • 难以优化

自己的框架,可以针对性优化:

  • 去掉不需要的功能
  • 减少中间层
  • 针对特定场景优化

我的框架在相同任务下,比 LangChain 快 2-3 倍。

6. 成本控制

Agent 的成本主要是 Token 消耗:

  • 每次调用 LLM 都要花钱
  • 消息历史越长,成本越高

自己的框架,可以精细控制:

  • 压缩消息历史
  • 缓存重复查询
  • 选择性保留上下文

我通过优化,把 Token 消耗降低了 40%。

7. 技术积累

写框架的过程中,你会学到:

  • Prompt Engineering
  • LLM 工具调用机制
  • 异步编程
  • 错误处理
  • 状态管理

这些都是宝贵的经验。

8. 满足感

说实话,看着自己写的 Agent 一步步完成任务,那种成就感是无法替代的。

就像搭积木,自己搭的才有感情。


六、手搓过程中的一些坑

坑 1:消息格式不统一

问题:不同 LLM 的消息格式不一样

  • OpenAI 是一种格式
  • Claude 是另一种格式
  • 国产模型又是另一种

解决:定义统一的内部消息格式,然后做转换层

1
2
3
4
5
6
7
8
9
10
class Message(BaseModel):
    # 内部统一格式
    
    def to_openai(self):
        # 转换为 OpenAI 格式
        pass
    
    def to_claude(self):
        # 转换为 Claude 格式
        pass

坑 2:工具调用参数解析

问题:LLM 生成的参数不一定符合工具定义

  • 类型不对(字符串 vs 数字)
  • 缺少必需参数
  • 多了不需要的参数

解决:使用 Pydantic 做参数验证

1
2
3
4
5
6
7
8
9
class ToolArgs(BaseModel):
    city: str
    date: Optional[str] = None
    
    @validator('city')
    def validate_city(cls, v):
        if not v:
            raise ValueError("城市不能为空")
        return v

坑 3:无限循环

问题:Agent 有时候会陷入死循环

  • 一直调用同一个工具
  • 一直重复同样的思考

解决:

  1. 设置最大步数
  2. 检测重复操作
  3. 强制终止机制
1
2
3
4
5
def run(self, max_steps=50):
    for step in range(max_steps):
        if self._is_stuck():
            self._add_focus_prompt()
        # ...

坑 4:错误处理

问题:工具调用可能失败

  • 网络错误
  • API 限流
  • 参数错误

解决:完善的错误处理和重试机制

1
2
3
4
5
6
7
8
try:
    result = tool.execute(args)
except Exception as e:
    result = ActionResult(
        success=False,
        error=str(e)
    )
    # 记录错误,让 Agent 知道

坑 5:Token 消耗

问题:消息历史越来越长,Token 消耗暴增

解决:

  1. 限制消息历史长度
  2. 压缩旧消息
  3. 只保留关键信息
1
2
3
4
5
6
7
class Memory:
    def __init__(self, max_messages=20):
        self.max_messages = max_messages
    
    def add_message(self, msg):
        if len(self.messages) >= self.max_messages:
            self._compress_old_messages()

七、实际使用效果

我用这个框架做了几个实验:

实验 1:天气查询助手

任务:查询多个城市的天气,并给出出行建议

结果:

  • 成功率:95%
  • 平均耗时:3 秒
  • Token 消耗:约 1000

对比 LangChain:

  • 速度快 2 倍
  • Token 少 30%

实验 2:数据分析助手

任务:读取 CSV 文件,分析数据,生成图表

结果:

  • 成功率:90%
  • 能正确处理各种数据格式
  • 生成的代码可以直接运行

实验 3:代码审查助手

任务:读取代码文件,找出潜在问题

结果:

  • 能发现大部分明显问题
  • 给出的建议比较靠谱
  • 但对复杂逻辑的理解还不够

八、未来的改进方向

虽然现在的框架已经能用了,但还有很多可以改进的地方:

1. 多 Agent 协作

目前是单 Agent,未来想支持:

  • 多个 Agent 分工合作
  • Agent 之间通信
  • 任务分配和协调

2. 长期记忆

目前只有短期记忆(对话历史),未来想加入:

  • 向量数据库存储
  • 知识图谱
  • 个性化记忆

3. 更智能的规划

目前的规划比较简单,未来想加入:

  • 任务优先级排序
  • 并行任务执行
  • 动态调整计划

4. 更好的错误恢复

目前错误处理还比较粗糙,未来想:

  • 自动重试策略
  • 降级方案
  • 更智能的错误分析

5. 可视化界面

目前只有命令行,未来想做:

  • Web 界面
  • 实时显示思考过程
  • 可视化工具调用链路

九、写在最后

手搓一个 Agent 框架,对我来说是一次很有价值的经历。

我学到了:

  • Agent 的工作原理
  • LLM 的能力边界
  • 工程实践的重要性

我也明白了:

  • 没有完美的框架
  • 适合自己的才是最好的
  • 技术是为了解决问题,不是为了炫技

如果你也对 Agent 感兴趣,我建议:

  1. 先用用现成的框架,了解基本概念
  2. 然后试着自己实现一个简单版本
  3. 在实践中不断改进

代码在这里:https://github.com/byte-wrangler/agent-frame-start

欢迎 Star,欢迎提 Issue,欢迎一起讨论。


参考资料


最后说一句:

技术的本质是解决问题。不要为了用 Agent 而用 Agent,先想清楚你要解决什么问题,然后选择合适的工具。

有时候,一个简单的脚本就够了。

但如果你真的需要 Agent,那就大胆去搞。毕竟,折腾才是程序员的浪漫。

本文由作者按照 CC BY 4.0 进行授权