从零造轮子:我亲手搭建了一个Agent框架,彻底明白了LangChain的底层逻辑!
导读:市面上Agent框架那么多,为什么还要从零手写一个?当我真正动手构建HelloAgents框架后,才发现那些成熟框架的"黑盒"里到底藏着什么秘密。今天带你深度剖析Agent框架的设计哲学,以及我是如何从一个"调包侠"蜕变为"架构师"的。
哈喽,大家好~ 翊博来了~~~这一次文章风格比较特别,希望大家可以喜欢~
一、为什么我要"重复造轮子"?
说实话,刚开始看到这个学习任务时,我的内心是抗拒的。
LangChain、LlamaIndex、AutoGen...市面上成熟的框架一大堆,我为什么要花时间从零写一个?这不是浪费时间吗?
但当我真正动手后,才发现了一个残酷的事实:我会用框架,但我根本不懂框架。
🤔 灵魂拷问:你真的理解Agent吗?
当你的ReAct Agent陷入死循环时,你知道底层发生了什么吗?
当Function Calling返回格式错误时,你能快速定位问题吗?
当Memory爆炸导致Context超出限制时,你知道如何优雅降级吗?
答案很可能是:不能。
这就是为什么HelloAgents框架的设计理念如此重要——它不是为了取代LangChain,而是为了让你真正理解Agent的工作原理。
HelloAgents的四个核心设计哲学
1. 轻量级 > 大而全
LangChain为了追求通用性,引入了Chain、Agent、Tool、Memory、Retriever等十几个概念。初学者往往还没开始写代码,就被概念淹没了。
HelloAgents反其道而行之:核心代码控制在你能一次性读懂的规模。
hello-agents/
├── hello_agents/
│ │
│ ├── core/ # 核心框架层
│ │ ├── agent.py # Agent基类
│ │ ├── llm.py # HelloAgentsLLM统一接口
│ │ ├── message.py # 消息系统
│ │ ├── config.py # 配置管理
│ │ └── exceptions.py # 异常体系
│ │
│ ├── agents/ # Agent实现层
│ │ ├── simple_agent.py # SimpleAgent实现
│ │ ├── react_agent.py # ReActAgent实现
│ │ ├── reflection_agent.py # ReflectionAgent实现
│ │ └── plan_solve_agent.py # PlanAndSolveAgent实现
│ │
│ ├── tools/ # 工具系统层
│ │ ├── base.py # 工具基类
│ │ ├── registry.py # 工具注册机制
│ │ ├── chain.py # 工具链管理系统
│ │ ├── async_executor.py # 异步工具执行器
│ │ └── builtin/ # 内置工具集
│ │ ├── calculator.py # 计算工具
│ │ └── search.py # 搜索工具
└──
好的学习框架不是功能越多越好,而是要让学习者能在合理时间内完全理解每一行代码。这才是真正的"教学友好"。
2. 约定优于配置
HelloAgents的自动检测机制让我拍案叫绝:
# 用户只需要在.env中配置
LLM_BASE_URL="地址"
LLM_MODEL_ID="模型名"
# Python代码中零配置
llm = HelloAgentsLLM() # 自动检测为ollama!
框架内部通过_auto_detect_provider方法,按照优先级自动推断服务商:
- 检查特定环境变量(如
MODELSCOPE_API_KEY) - 解析
base_url特征(域名、端口) - 分析API Key格式
我的思考:这才是真正的用户体验思维。框架应该替用户做决策,而不是让用户做选择题。
3. 万物皆为工具
这是HelloAgents最让我震撼的设计理念。
在LangChain中,Memory、RAG、MCP都是独立的模块,需要分别学习。但在HelloAgents中,一切都被统一抽象为Tool。
# Memory不再是独立模块,而是一个Tool
memory_tool = MemoryTool()
registry.register_tool(memory_tool)
# RAG也不是独立模块,而是一个Tool
rag_tool = RAGTool()
registry.register_tool(rag_tool)
这种设计的精妙之处在于,它消除了不必要的抽象层。学习者只需要理解一个核心概念——"Agent调用Tool",就能掌握所有功能。这才是真正的奥卡姆剃刀原理。
4. 基于OpenAI标准API
HelloAgents没有重新发明一套接口,而是完全基于OpenAI标准API构建。
# 无论你用的是OpenAI、ModelScope还是本地Ollama
llm = HelloAgentsLLM(provider="modelscope") # 或 "ollama"
# 调用方式完全一致
response = llm.think(messages)
这就是务实的选择。OpenAI API已经成为行业标准,在这个标准之上构建,既能保证兼容性,又能降低学习成本。当你掌握了HelloAgents,迁移到任何其他框架都是降维打击。
二、HelloAgents的架构设计深度剖析
核心组件拆解
1. HelloAgentsLLM:多供应商统一接口
这是整个框架的"心脏"。让我震惊的是它的扩展性设计:
class HelloAgentsLLM:
def __init__(self, provider="auto", **kwargs):
# 自动检测机制
if provider == "auto":
provider = self._auto_detect_provider()
# 根据provider解析凭证
api_key, base_url = self._resolve_credentials(provider)
# 统一创建OpenAI客户端
self._client = OpenAI(api_key=api_key, base_url=base_url)
💡 深度思考:
这个设计完美诠释了策略模式的精髓。通过provider参数,框架可以在运行时动态切换策略(不同的LLM供应商),而客户端代码完全不需要修改。
如果你想添加对新供应商的支持,只需要:
- 在
_auto_detect_provider中添加检测逻辑 - 在
_resolve_credentials中添加凭证解析 - 搞定!无需修改任何其他代码。
这就是开闭原则的完美体现:对扩展开放,对修改封闭。
2. Agent抽象基类:强制统一的接口
class Agent(ABC):
@abstractmethod
def run(self, input_text: str, **kwargs) -> str:
"""所有Agent必须实现这个方法"""
pass
def add_message(self, message: Message):
"""通用的历史记录管理"""
self._history.append(message)
里的@abstractmethod是整个框架的灵魂。它强制所有子类(SimpleAgent、ReActAgent、ReflectionAgent等)都必须实现run方法,从而保证了接口的一致性。
这意味着什么?意味着无论你的Agent内部逻辑多复杂,外部调用方式永远都是:
agent = SomeAgent(...)
result = agent.run("你的问题")
这就是多态的力量。框架使用者不需要关心具体实现,只需要知道统一的接口。
3. Message类:对内丰富,对外兼容
class Message(BaseModel):
content: str
role: MessageRole # Literal["user", "assistant", "system", "tool"]
timestamp: datetime
metadata: Optional[Dict[str, Any]]
def to_dict(self) -> Dict[str, Any]:
# 转换为OpenAI API格式
return {"role": self.role, "content": self.content}
这个设计太巧妙了!
- 对内:使用Pydantic的
BaseModel,享受类型检查、数据验证、自动文档生成等福利 - 对外:通过
to_dict()方法,完美兼容OpenAI API格式
这就是适配器模式的典型应用。框架内部可以使用丰富的数据结构,但在边界处统一转换为标准格式。
三、四种Agent范式的框架化重构
HelloAgents将第四章的四种Agent范式进行了框架化重构,这个过程让我对设计模式有了更深的理解。
1. SimpleAgent:从简单到强大的演进
第四章的SimpleAgent只能做基础对话,而框架化的版本支持:
- 可选的工具调用
- 流式响应
- 动态工具管理
- 多轮迭代
class MySimpleAgent(SimpleAgent):
def run(self, input_text: str, max_tool_iterations: int = 3, **kwargs) -> str:
# 构建消息列表
messages = self._build_messages(input_text)
# 如果没有工具,直接对话
if not self.enable_tool_calling:
return self.llm.invoke(messages)
# 支持多轮工具调用
return self._run_with_tools(messages, max_tool_iterations)
我的思考:
这里的_run_with_tools方法实现了模板方法模式。基类定义了算法骨架(调用LLM -> 解析工具调用 -> 执行工具 -> 循环),子类可以重写特定步骤,但整体流程不变。
2. ReActAgent:提示词工程的系统化升级
框架化的ReActAgent最大的改进是提示词模板:
MY_REACT_PROMPT = """
## 工作流程
请严格按照以下格式进行回应,**每次只能执行一个步骤**:
Thought: 分析当前问题,思考需要什么信息或采取什么行动。
Action: 选择一个行动,格式必须是以下之一:
- `{{tool_name}}[{{tool_input}}]` - 调用指定工具
- `Finish[最终答案]` - 当你有足够信息给出最终答案时
## 重要提醒
1. 每次回应必须包含Thought和Action两部分
2. 工具调用的格式必须严格遵循:工具名[参数]
3. 只有当你确信有足够信息回答问题时,才使用Finish
对比之前第四章的学习,框架化版本做了三个关键改进:
- 结构化提示词:使用Markdown标题清晰分隔各个部分
- 强调约束:用加粗和编号明确重要规则
- 减少歧义:明确指出"每次只能执行一个步骤"
这就是提示词工程的精髓——好的提示词不是写给人类看的,是写给AI看的。AI需要清晰的格式约束和明确的边界条件。
3. ReflectionAgent:通用化设计
上一章的ReflectionAgent专门针对代码生成优化,而框架化版本采用了通用化设计:
DEFAULT_PROMPTS = {
"initial": "请根据以下要求完成任务:\n\n任务: {task}",
"reflect": "请仔细审查以下回答,并找出可能的问题或改进空间...",
"refine": "请根据反馈意见改进你的回答..."
}
这个设计体现了策略模式和依赖注入的结合。用户可以通过custom_prompts参数注入不同的策略(提示词模板),而Agent的核心逻辑不需要修改。
这就是为什么框架化后的ReflectionAgent可以用于:
- 代码生成(使用第四章的专用提示词)
- 文章写作(使用通用提示词)
- 数据分析(使用自定义提示词)
一套代码,多种场景。
4. PlanAndSolveAgent:从自由文本到结构化输出
之前版本(见上一章节)的Planner输出自由文本,框架化版本强制要求Python列表格式:
DEFAULT_PLANNER_PROMPT = """
你的输出必须是一个Python列表,其中每个元素都是一个描述子任务的字符串。
问题: {question}
请严格按照以下格式输出你的计划:
```python
["步骤1", "步骤2", "步骤3", ...]
四、工具系统:Agent能力的延伸
1. Tool 基类:统一接口的力量
class Tool(ABC):
@abstractmethod
def run(self, parameters: Dict[str, Any]) -> str:
"""所有工具必须实现这个方法"""
pass
@abstractmethod
def get_parameters(self) -> List[ToolParameter]:
"""工具自描述能力"""
pass
这个设计让我想起了命令模式。每个Tool都是一个封装好的命令,Agent不需要知道工具内部如何工作,只需要调用统一的run方法。
更妙的是get_parameters方法。它让工具具备了自描述能力,这为以下功能奠定了基础:
- 自动生成工具文档
- 自动参数验证
- 自动转换为OpenAI Function Calling Schema
2. ToolRegistry:注册表的魔法
class ToolRegistry:
def register_tool(self, tool: Tool):
"""注册Tool对象"""
self._tools[tool.name] = tool
def register_function(self, name: str, description: str, func: Callable):
"""直接注册函数(简便方式)"""
self._functions[name] = {"description": description, "func": func}
ToolRegistry支持两种注册方式,这体现了接口隔离原则:
- Tool对象注册:适合复杂工具,支持完整参数定义
- 函数直接注册:适合简单工具,快速集成
这种设计让用户可以根据场景选择最合适的方式,而不是强制使用一种模式。
3. 工具链:组合的力量
class ToolChain:
def add_step(self, tool_name: str, input_template: str, output_key: str):
"""添加工具执行步骤"""
self.steps.append({...})
def execute(self, registry, initial_input: str, context: Dict):
"""顺序执行工具链"""
for step in self.steps:
tool_input = input_template.format(**context)
result = registry.execute_tool(tool_name, tool_input)
context[output_key] = result
这就是责任链模式的应用。每个工具处理完数据后,将结果传递给下一个工具,形成流水线。
这种设计的威力在于:
- 解耦:每个工具只关心自己的逻辑
- 可复用:工具可以组合成不同的链
- 可扩展:随时添加新步骤
4. 异步工具执行器:并发处理的优雅方案
class AsyncToolExecutor:
async def execute_tools_parallel(self, tasks: List[Dict]) -> List[str]:
"""并行执行多个工具"""
async_tasks = [
self.execute_tool_async(task["tool_name"], task["input_data"])
for task in tasks
]
return await asyncio.gather(*async_tasks)
这段代码完美诠释了asyncio的威力。当多个工具调用互不依赖时(比如同时搜索多个关键词),并行执行可以将总耗时从N * t降低到t。
但要注意,不是所有场景都适合并行:
- 适合:独立搜索、批量计算
- 不适合:有依赖关系的工具链
五、我的核心感悟:从"调包侠"到"架构师"的蜕变
学习曲线对比
| 学习路径 | 上手速度 | 理解深度 | 定制能力 | 调试效率 |
|---|---|---|---|---|
| 直接用LangChain | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐ | ⭐⭐ |
| 从零手写Agent | ⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 先手写再使用框架 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
三大认知跃迁
1. 从"黑盒"到"白盒"
以前用LangChain时,Agent陷入死循环我只能干瞪眼。现在我知道了:
- 问题出在
max_steps没有正确设置 - 或者提示词格式约束不够严格
- 或者工具返回的观察结果让Agent误解了任务状态
理解原理后,调试效率提升10倍。
2. 从"被动接受"到"主动设计"
以前只能接受框架的设计决策,现在我能评估:
- 为什么LangChain要用Chain而不是直接调用?
- 为什么AutoGen要用多Agent对话而不是单Agent?
- HelloAgents的"万物皆为工具"设计是否合理?
有了对比视角,才能真正做出技术选型。
3. 从"功能堆砌"到"架构思维"
以前写代码是"功能越多越好",现在理解了:
- 单一职责原则:每个类只做一件事
- 开闭原则:对扩展开放,对修改封闭
- 依赖倒置原则:依赖抽象,不依赖具体实现
避坑指南
- 不要一开始就追求完美
先让代码跑起来,再优化架构。HelloAgents也是从简单开始逐步完善的。 - 不要忽视提示词工程
Agent的能力70%取决于提示词质量。花80%的时间打磨提示词是值得的。 - 不要害怕犯错
我在实现ReActAgent时,正则解析失败了几十次。但每次调试都让我对模型输出格式有了更深的理解。 - 不要孤立学习
把HelloAgents和LangChain对比学习,你会发现很多设计决策的深层原因。
七、结语:造轮子的真正意义
回到最初的问题:为什么要从零构建Agent框架?
我的答案是:为了获得真正的自由。
当你理解了每一行代码的工作原理:
- 你不再被框架的bug困扰,因为你能自己修复
- 你不再被API变更绑架,因为你能自己适配
- 你不再被黑盒逻辑限制,因为你能自己定制
这才是HelloAgents框架的真正价值——它不是要取代LangChain,而是要让你成为能驾驭任何框架的高手。
最后送给大家一句话:
框架是工具,原理是根基。
只有根基扎实,才能在AI浪潮中立于不败之地。与其做一个"调包侠",不如做一个"造轮者"。
因为真正的创造力,来自于对底层逻辑的深刻理解。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)