导读:市面上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方法,按照优先级自动推断服务商:

  1. 检查特定环境变量(如MODELSCOPE_API_KEY
  2. 解析base_url特征(域名、端口)
  3. 分析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供应商),而客户端代码完全不需要修改。

如果你想添加对新供应商的支持,只需要:

  1. _auto_detect_provider中添加检测逻辑
  2. _resolve_credentials中添加凭证解析
  3. 搞定!无需修改任何其他代码。

这就是开闭原则的完美体现:对扩展开放,对修改封闭。

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

对比之前第四章的学习,框架化版本做了三个关键改进:

  1. 结构化提示词:使用Markdown标题清晰分隔各个部分
  2. 强调约束:用加粗和编号明确重要规则
  3. 减少歧义:明确指出"每次只能执行一个步骤"

这就是提示词工程的精髓——好的提示词不是写给人类看的,是写给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. 从"功能堆砌"到"架构思维"

以前写代码是"功能越多越好",现在理解了:

  • 单一职责原则:每个类只做一件事
  • 开闭原则:对扩展开放,对修改封闭
  • 依赖倒置原则:依赖抽象,不依赖具体实现

避坑指南

  1. 不要一开始就追求完美
    先让代码跑起来,再优化架构。HelloAgents也是从简单开始逐步完善的。
  2. 不要忽视提示词工程
    Agent的能力70%取决于提示词质量。花80%的时间打磨提示词是值得的。
  3. 不要害怕犯错
    我在实现ReActAgent时,正则解析失败了几十次。但每次调试都让我对模型输出格式有了更深的理解。
  4. 不要孤立学习
    把HelloAgents和LangChain对比学习,你会发现很多设计决策的深层原因。

七、结语:造轮子的真正意义

回到最初的问题:为什么要从零构建Agent框架?

我的答案是:为了获得真正的自由。

当你理解了每一行代码的工作原理:

  • 你不再被框架的bug困扰,因为你能自己修复
  • 你不再被API变更绑架,因为你能自己适配
  • 你不再被黑盒逻辑限制,因为你能自己定制

这才是HelloAgents框架的真正价值——它不是要取代LangChain,而是要让你成为能驾驭任何框架的高手。

最后送给大家一句话

框架是工具,原理是根基。
只有根基扎实,才能在AI浪潮中立于不败之地。

与其做一个"调包侠",不如做一个"造轮者"。
因为真正的创造力,来自于对底层逻辑的深刻理解。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐