构建自己的Agent框架这章的重点是从“使用现成框架”转向“自己实现一个基础 Agent 框架”。这一章不再只是调用 AutoGen、AgentScope、CAMEL 或 LangGraph而是尝试抽象出一个通用的 Agent 技术底座统一 LLM 调用、统一消息格式、统一 Agent 接口、统一工具系统。这样做的意义在于后续无论是实现 ReAct、Reflection、Plan-and-Solve还是接入搜索、计算、RAG、Memory都不需要重新写一套代码而是在统一接口上扩展。1. 为什么要自建 Agent 框架现成框架虽然功能强但也存在一些问题抽象层过厚、版本变化快、底层逻辑黑盒化、难以精确控制执行过程。对于学习 Agent 原理来说如果只会调用框架 API很容易知道“怎么用”但不清楚“为什么这样设计”。因此我们自己构建一个轻量级框架 HelloAgents。它不追求一开始就很复杂而是先实现 Agent 系统最基础的几块能力LLM 接口 消息系统 配置管理 Agent 基类 Agent 范式实现 工具系统2. LLM 接口概括LLM 接口是整个 Agent 框架的底层入口。无论上层是 SimpleAgent、ReActAgent还是 Plan-and-Solve本质上都需要通过统一接口调用大语言模型。本章构建了HelloAgentsLLM它的作用是屏蔽不同模型供应商的差异。也就是说上层 Agent 不需要关心底层到底是 OpenAI、DeepSeek、ModelScope、Ollama还是本地模型只需要调用统一方法即可。理想状态下上层代码只写llmHelloAgentsLLM()responsellm.invoke(messages)至于 API Key 从哪里读、base_url 是什么、provider 是谁都交给 LLM 接口内部处理。这种设计的价值是模型可以替换但 Agent 逻辑不需要大改。对于一个框架来说这是非常关键的解耦。3. 框架接口实现这一节是第七章的核心基础。它主要实现三个文件message.py config.py agent.py这三个组件分别解决三个问题Message框架内部如何表示消息 Config框架配置如何集中管理 Agent所有智能体应该遵循什么统一接口Message统一消息格式Agent 系统里最基本的数据就是消息。用户输入是一条消息模型回复是一条消息工具结果也是一条消息。如果消息格式不统一后续做历史记录、上下文管理、工具调用、日志追踪都会很混乱。因此框架中设计了一个Message类fromtypingimportOptional,Dict,Any,LiteralfromdatetimeimportdatetimefrompydanticimportBaseModel MessageRoleLiteral[user,assistant,system,tool]classMessage(BaseModel):content:strrole:MessageRole timestamp:datetimeNonemetadata:Optional[Dict[str,Any]]Nonedef__init__(self,content:str,role:MessageRole,**kwargs):super().__init__(contentcontent,rolerole,timestampkwargs.get(timestamp,datetime.now()),metadatakwargs.get(metadata,{}))defto_dict(self)-Dict[str,Any]:return{role:self.role,content:self.content}这个类的重点不是复杂而是规范。它规定了一条消息至少包含两个核心字段content消息内容 role消息角色其中role被限制为四种user assistant system tool这样做有两个好处。第一和 OpenAI API 的消息格式保持兼容。通过to_dict()方法框架内部的Message对象可以直接转成大模型接口需要的字典格式。第二为后续扩展留空间。timestamp可以用于日志和调试metadata可以存储工具名、token 数量、来源信息、置信度等额外内容。可以理解为Message Agent 框架中的最小通信单位它虽然简单但后续的多轮对话、上下文工程、工具调用记录都会依赖这个基础结构。Config统一配置管理Config类负责把框架运行时需要的配置集中管理起来。比如模型温度、最大 token 数、日志级别、历史消息长度等都不应该散落在各个文件中。示例代码如下classConfig(BaseModel):default_model:strgpt-3.5-turbodefault_provider:stropenaitemperature:float0.7max_tokens:Optional[int]Nonedebug:boolFalselog_level:strINFOmax_history_length:int100classmethoddeffrom_env(cls)-Config:returncls(debugos.getenv(DEBUG,false).lower()true,log_levelos.getenv(LOG_LEVEL,INFO),temperaturefloat(os.getenv(TEMPERATURE,0.7)),max_tokensint(os.getenv(MAX_TOKENS))ifos.getenv(MAX_TOKENS)elseNone,)这个类体现的是“配置和代码分离”的思想。默认值保证框架可以直接运行环境变量又允许用户在不同运行环境中调整配置。例如开发环境可以设置DEBUGtrue TEMPERATURE0.7生产环境可以设置DEBUGfalse LOG_LEVELWARNING这样就不需要改代码。Config的价值在项目变大之后会更明显。没有统一配置类时参数会散落在 Agent、LLM、Tool、测试脚本中后期很难维护。有了Config后各个模块只需要依赖同一个配置对象。Agent 抽象基类Agent基类是整个框架最重要的抽象。它规定了所有智能体都应该具备的共同结构。fromabcimportABC,abstractmethodclassAgent(ABC):def__init__(self,name:str,llm:HelloAgentsLLM,system_prompt:Optional[str]None,config:Optional[Config]None):self.namename self.llmllm self.system_promptsystem_prompt self.configconfigorConfig()self._history:list[Message][]abstractmethoddefrun(self,input_text:str,**kwargs)-str:passdefadd_message(self,message:Message):self._history.append(message)defclear_history(self):self._history.clear()defget_history(self)-list[Message]:returnself._history.copy()这个设计的关键在于所有具体 Agent 都必须实现run()方法。也就是说无论是SimpleAgent ReActAgent ReflectionAgent PlanAndSolveAgent FunctionCallAgent它们对外暴露的执行入口都是agent.run(input_text)这样上层使用者不需要关心具体 Agent 内部怎么工作。SimpleAgent 可能只是直接问模型ReActAgent 可能会多轮调用工具ReflectionAgent 可能会自我反思多次但它们对外都是统一接口。这就是框架化的意义。框架还在基类中统一管理历史记录add_message() clear_history() get_history()这样每个 Agent 不需要重复实现对话历史管理。子类只需要专注自己的推理范式。4 Agent 范式的框架化实现这一节是在第四章已有 Agent 范式的基础上进行框架化重构。重点不是重新发明这些范式而是把它们统一纳入Agent基类体系中。框架化重构主要带来三个变化1. 统一初始化参数 2. 统一 run() 执行入口 3. 统一历史记录和工具接口也就是说之前每个 Agent 可能是独立脚本现在它们都变成框架中的标准组件。SimpleAgent基础对话 AgentSimpleAgent是最基础的智能体。它的核心逻辑就是接收用户输入构造消息列表调用 LLM返回结果。它还加入了两个扩展点可选工具调用 流式响应fromtypingimportOptional,Iteratorfromhello_agentsimportSimpleAgent,HelloAgentsLLM,Config,MessageclassMySimpleAgent(SimpleAgent): 重写的简单对话Agent 展示如何基于框架基类构建自定义Agent def__init__(self,name:str,llm:HelloAgentsLLM,system_prompt:Optional[str]None,config:Optional[Config]None,tool_registry:Optional[ToolRegistry]None,enable_tool_calling:boolTrue):super().__init__(name,llm,system_prompt,config)self.tool_registrytool_registry self.enable_tool_callingenable_tool_callingandtool_registryisnotNoneprint(f✅{name}初始化完成工具调用:{启用ifself.enable_tool_callingelse禁用})如果启用了工具调用它会把可用工具描述加入系统提示词中让模型知道当前可以使用哪些工具。例如你可以使用以下工具 - calculator执行数学计算 - search执行网络搜索然后约定工具调用格式[TOOL_CALL:tool_name:parameters]当模型输出类似[TOOL_CALL:calculator:15 * 8 32]Agent 就会解析出工具名和参数调用对应工具把工具结果重新放回对话再让模型基于结果生成最终答案。这说明 SimpleAgent 已经不只是“问答机器人”而是具备了基础的工具增强能力。ReActAgent推理与行动结合ReActAgent 的核心是Thought → Action → Observation → Thought → ...它让模型先思考再选择行动。如果需要外部信息就调用工具如果信息足够就输出最终答案。框架化后的 ReActAgent 主要改进在两个方面。第一提示词格式更严格。它要求模型每轮都按照固定格式输出Thought: 分析当前问题 Action: 工具名[参数] 或 Finish[最终答案]这样程序才能稳定解析模型输出。第二工具调用不再写死而是统一通过ToolRegistry执行。observationself.tool_registry.execute_tool(tool_name,tool_input)这意味着 ReActAgent 不需要知道具体有哪些工具也不需要关心工具内部如何实现。它只负责决定“调用哪个工具、传入什么参数”真正执行由工具注册表完成。ReActAgent 的基本流程可以理解为输入问题 ↓ 构建 ReAct Prompt ↓ LLM 输出 Thought / Action ↓ 如果 Action 是工具调用执行工具并记录 Observation ↓ 继续下一轮 ↓ 如果 Action 是 Finish返回最终答案为了防止无限循环框架中会设置max_steps。如果模型一直不结束达到最大步数后就返回失败提示。ReflectionAgent执行—反思—优化ReflectionAgent 的核心思想是让模型先生成一个初始答案然后再对自己的答案进行审查最后根据反馈进行修改。它的流程是Initial Answer ↓ Reflect ↓ Refine ↓ Final Answer第七章中的框架化版本把提示词抽象成三类initial生成初始答案 reflect审查当前答案 refine根据反馈改进答案这种设计比写死代码更灵活。比如默认情况下它可以用于文章生成、分析任务、总结任务如果换成代码相关提示词它就可以变成代码生成与代码审查 Agent。例如code_prompts{initial:你是Python专家请编写函数:{task},reflect:请审查代码的算法效率:\n任务:{task}\n代码:{content},refine:请根据反馈优化代码:\n任务:{task}\n反馈:{feedback}}这说明框架化后的 ReflectionAgent 不再绑定某一个具体任务而是可以通过自定义 prompt 适配不同场景。它适合用于文本润色 代码优化 方案改进 论文摘要修订 回答质量提升PlanAndSolveAgent先规划再执行Plan-and-Solve 的核心思想是复杂任务不要直接回答先拆解成步骤再逐步解决。流程是用户问题 ↓ Planner 生成计划 ↓ Executor 逐步执行每个子任务 ↓ 汇总最终答案框架化版本的一个重要改进是要求 Planner 输出 Python 列表格式。例如[计算周一销量,计算周二销量,计算周三销量,求三天总和]这样程序可以稳定解析计划而不是从一段自然语言里猜测步骤。执行阶段则由 Executor 根据当前步骤、历史结果和原始问题生成该步骤的答案。它不是一次性解决整个问题而是逐步完成。这个范式适合处理数学应用题 复杂推理题 多步骤分析任务 项目规划 实验方案设计它的优势是过程更清楚但也依赖 Planner 的计划质量。如果第一步计划拆错后面执行也会被带偏。FunctionCallAgent原生函数调用 AgentFunctionCallAgent 是基于 OpenAI 原生 function calling 机制实现的 Agent。它和 ReActAgent 最大的区别是ReActAgent 通过提示词约束模型输出工具调用格式而 FunctionCallAgent 使用模型原生的工具调用能力。两者可以简单对比ReActAgent 依赖 prompt 格式解析例如 工具名[参数] FunctionCallAgent 依赖模型原生 tools / function calling schemaFunctionCallAgent 会把工具转换成 OpenAI function calling schema然后交给模型决定是否调用工具。这样比纯 prompt 解析更稳因为参数通常是结构化 JSON不容易出现格式错乱。它适合用于支持原生工具调用的大模型。如果模型本身支持 function calling那么这种方式通常比手写工具调用格式更可靠。5. 工具系统工具系统是 Agent 能力扩展的关键。LLM 本身只擅长语言理解和生成但它不能直接计算、搜索、访问数据库或调用外部系统。工具系统的作用就是让 Agent 具备“行动能力”。第七章中的工具系统主要包括四层Tool 抽象基类 ToolParameter 参数定义 ToolRegistry 工具注册表 高级工具机制工具链、异步执行、多源搜索Tool 基类统一工具接口工具系统首先需要一个统一的抽象。所有工具都应该有相同的基本接口这样 Agent 才能以统一方式调用它们。classTool(ABC):def__init__(self,name:str,description:str):self.namename self.descriptiondescriptionabstractmethoddefrun(self,parameters:Dict[str,Any])-str:passabstractmethoddefget_parameters(self)-List[ToolParameter]:pass这个设计的重点是两个方法run()执行工具 get_parameters()描述工具需要哪些参数run()让所有工具都有统一执行入口。无论是计算器、搜索工具、文件读取工具还是数据库查询工具对 Agent 来说都是tool.run(parameters)get_parameters()则让工具具备自描述能力。Agent 或框架可以通过这个方法知道工具需要什么参数、参数类型是什么、是否必填。ToolParameter工具参数描述ToolParameter用来描述工具参数。classToolParameter(BaseModel):name:strtype:strdescription:strrequired:boolTruedefault:AnyNone它解决的问题是工具不能只告诉 Agent “我叫 calculator”还应该告诉 Agent “我需要一个 expression 参数它是字符串类型用来表示数学表达式”。例如计算器工具可能需要name: expression type: string description: 要计算的数学表达式 required: true这样做的好处是参数更清楚 更容易做类型校验 更容易生成工具文档 更容易转换成 function calling schema这也是工具系统走向工程化的重要一步。ToolRegistry工具注册与发现ToolRegistry是工具系统的管理中心。它负责注册工具、查找工具、执行工具、生成工具描述。它支持两种注册方式。第一种是注册完整 Tool 对象适合复杂工具registry.register_tool(calculator_tool)第二种是直接注册普通函数适合简单工具registry.register_function(namesearch,description搜索信息,funcsearch_function)这两种方式都很有用。完整 Tool 对象更规范适合需要参数定义、校验、状态维护的工具。普通函数注册更轻量适合快速把已有函数接入框架。ToolRegistry还有一个非常重要的方法get_tools_description()它会把所有工具整理成描述文本然后放入 Agent 的提示词中。这样模型才知道自己有哪些可用工具。例如可用工具 - calculator执行数学计算 - search搜索互联网信息对于基于 prompt 的工具调用来说这一步非常关键。模型只有看到工具说明才可能正确选择工具。OpenAI Schema 转换为了支持 FunctionCallAgent工具系统还需要能把工具定义转换为 OpenAI function calling schema。也就是说工具不仅要能被 prompt 描述还要能被结构化地交给模型。转换后的 schema 大致包含工具名称 工具描述 参数 properties 必填参数 required这样模型就可以按照结构化参数调用工具而不是在自然语言里写一个不稳定的工具调用字符串。这也是工具系统从“提示词调用”走向“原生函数调用”的关键。自定义工具计算器与高级搜索第七章中还展示了如何实现自定义工具。计算器工具适合演示基础工具开发因为它输入简单、结果明确。更复杂的是高级搜索工具。它把多个搜索源整合到一个工具中例如 Tavily 和 SerpAPI。工具内部会按顺序尝试不同搜索源如果一个失败就继续尝试下一个。这种设计体现了工具系统的工程化思维多个后端 自动降级 异常捕获 结果统一返回Agent 不需要知道底层使用了哪个搜索服务。它只需要调用advanced_search具体是 Tavily 返回结果还是 SerpAPI 返回结果由工具内部决定。这就是封装的价值。工具链多个工具顺序执行有些任务不是一个工具能完成的而是需要多个工具串联。例如搜索资料 → 提取关键信息 → 计算结果 → 生成总结第七章中设计了ToolChain用于把多个工具按步骤组织起来。每一步可以指定tool_name调用哪个工具 input_template输入模板 output_key把结果保存到哪个变量执行时工具链会维护一个上下文context前一步的输出可以被后一步使用。例如第 1 步search({input}) → search_result 第 2 步calculator(根据 {search_result} 计算) → calculation_result这种设计已经很接近 LangGraph 中的节点流转思想只不过这里是工具层面的顺序编排。异步工具执行有些工具很耗时比如网络搜索、数据库查询、文件解析。如果顺序执行多个工具整体速度会很慢。因此第七章还实现了异步工具执行器AsyncToolExecutor。它通过线程池并行执行多个工具任务。适合并行的场景包括同时搜索多个关键词 同时调用多个数据源 同时处理多个文件 同时执行多个互不依赖的计算任务这种设计可以明显提高复杂 Agent 的响应速度。但前提是任务之间没有强依赖。如果后一个工具必须使用前一个工具的输出就不能简单并行而应该使用工具链顺序执行。
hello-agents学习笔记--构建自己的Agent框架
发布时间:2026/6/30 23:05:00
构建自己的Agent框架这章的重点是从“使用现成框架”转向“自己实现一个基础 Agent 框架”。这一章不再只是调用 AutoGen、AgentScope、CAMEL 或 LangGraph而是尝试抽象出一个通用的 Agent 技术底座统一 LLM 调用、统一消息格式、统一 Agent 接口、统一工具系统。这样做的意义在于后续无论是实现 ReAct、Reflection、Plan-and-Solve还是接入搜索、计算、RAG、Memory都不需要重新写一套代码而是在统一接口上扩展。1. 为什么要自建 Agent 框架现成框架虽然功能强但也存在一些问题抽象层过厚、版本变化快、底层逻辑黑盒化、难以精确控制执行过程。对于学习 Agent 原理来说如果只会调用框架 API很容易知道“怎么用”但不清楚“为什么这样设计”。因此我们自己构建一个轻量级框架 HelloAgents。它不追求一开始就很复杂而是先实现 Agent 系统最基础的几块能力LLM 接口 消息系统 配置管理 Agent 基类 Agent 范式实现 工具系统2. LLM 接口概括LLM 接口是整个 Agent 框架的底层入口。无论上层是 SimpleAgent、ReActAgent还是 Plan-and-Solve本质上都需要通过统一接口调用大语言模型。本章构建了HelloAgentsLLM它的作用是屏蔽不同模型供应商的差异。也就是说上层 Agent 不需要关心底层到底是 OpenAI、DeepSeek、ModelScope、Ollama还是本地模型只需要调用统一方法即可。理想状态下上层代码只写llmHelloAgentsLLM()responsellm.invoke(messages)至于 API Key 从哪里读、base_url 是什么、provider 是谁都交给 LLM 接口内部处理。这种设计的价值是模型可以替换但 Agent 逻辑不需要大改。对于一个框架来说这是非常关键的解耦。3. 框架接口实现这一节是第七章的核心基础。它主要实现三个文件message.py config.py agent.py这三个组件分别解决三个问题Message框架内部如何表示消息 Config框架配置如何集中管理 Agent所有智能体应该遵循什么统一接口Message统一消息格式Agent 系统里最基本的数据就是消息。用户输入是一条消息模型回复是一条消息工具结果也是一条消息。如果消息格式不统一后续做历史记录、上下文管理、工具调用、日志追踪都会很混乱。因此框架中设计了一个Message类fromtypingimportOptional,Dict,Any,LiteralfromdatetimeimportdatetimefrompydanticimportBaseModel MessageRoleLiteral[user,assistant,system,tool]classMessage(BaseModel):content:strrole:MessageRole timestamp:datetimeNonemetadata:Optional[Dict[str,Any]]Nonedef__init__(self,content:str,role:MessageRole,**kwargs):super().__init__(contentcontent,rolerole,timestampkwargs.get(timestamp,datetime.now()),metadatakwargs.get(metadata,{}))defto_dict(self)-Dict[str,Any]:return{role:self.role,content:self.content}这个类的重点不是复杂而是规范。它规定了一条消息至少包含两个核心字段content消息内容 role消息角色其中role被限制为四种user assistant system tool这样做有两个好处。第一和 OpenAI API 的消息格式保持兼容。通过to_dict()方法框架内部的Message对象可以直接转成大模型接口需要的字典格式。第二为后续扩展留空间。timestamp可以用于日志和调试metadata可以存储工具名、token 数量、来源信息、置信度等额外内容。可以理解为Message Agent 框架中的最小通信单位它虽然简单但后续的多轮对话、上下文工程、工具调用记录都会依赖这个基础结构。Config统一配置管理Config类负责把框架运行时需要的配置集中管理起来。比如模型温度、最大 token 数、日志级别、历史消息长度等都不应该散落在各个文件中。示例代码如下classConfig(BaseModel):default_model:strgpt-3.5-turbodefault_provider:stropenaitemperature:float0.7max_tokens:Optional[int]Nonedebug:boolFalselog_level:strINFOmax_history_length:int100classmethoddeffrom_env(cls)-Config:returncls(debugos.getenv(DEBUG,false).lower()true,log_levelos.getenv(LOG_LEVEL,INFO),temperaturefloat(os.getenv(TEMPERATURE,0.7)),max_tokensint(os.getenv(MAX_TOKENS))ifos.getenv(MAX_TOKENS)elseNone,)这个类体现的是“配置和代码分离”的思想。默认值保证框架可以直接运行环境变量又允许用户在不同运行环境中调整配置。例如开发环境可以设置DEBUGtrue TEMPERATURE0.7生产环境可以设置DEBUGfalse LOG_LEVELWARNING这样就不需要改代码。Config的价值在项目变大之后会更明显。没有统一配置类时参数会散落在 Agent、LLM、Tool、测试脚本中后期很难维护。有了Config后各个模块只需要依赖同一个配置对象。Agent 抽象基类Agent基类是整个框架最重要的抽象。它规定了所有智能体都应该具备的共同结构。fromabcimportABC,abstractmethodclassAgent(ABC):def__init__(self,name:str,llm:HelloAgentsLLM,system_prompt:Optional[str]None,config:Optional[Config]None):self.namename self.llmllm self.system_promptsystem_prompt self.configconfigorConfig()self._history:list[Message][]abstractmethoddefrun(self,input_text:str,**kwargs)-str:passdefadd_message(self,message:Message):self._history.append(message)defclear_history(self):self._history.clear()defget_history(self)-list[Message]:returnself._history.copy()这个设计的关键在于所有具体 Agent 都必须实现run()方法。也就是说无论是SimpleAgent ReActAgent ReflectionAgent PlanAndSolveAgent FunctionCallAgent它们对外暴露的执行入口都是agent.run(input_text)这样上层使用者不需要关心具体 Agent 内部怎么工作。SimpleAgent 可能只是直接问模型ReActAgent 可能会多轮调用工具ReflectionAgent 可能会自我反思多次但它们对外都是统一接口。这就是框架化的意义。框架还在基类中统一管理历史记录add_message() clear_history() get_history()这样每个 Agent 不需要重复实现对话历史管理。子类只需要专注自己的推理范式。4 Agent 范式的框架化实现这一节是在第四章已有 Agent 范式的基础上进行框架化重构。重点不是重新发明这些范式而是把它们统一纳入Agent基类体系中。框架化重构主要带来三个变化1. 统一初始化参数 2. 统一 run() 执行入口 3. 统一历史记录和工具接口也就是说之前每个 Agent 可能是独立脚本现在它们都变成框架中的标准组件。SimpleAgent基础对话 AgentSimpleAgent是最基础的智能体。它的核心逻辑就是接收用户输入构造消息列表调用 LLM返回结果。它还加入了两个扩展点可选工具调用 流式响应fromtypingimportOptional,Iteratorfromhello_agentsimportSimpleAgent,HelloAgentsLLM,Config,MessageclassMySimpleAgent(SimpleAgent): 重写的简单对话Agent 展示如何基于框架基类构建自定义Agent def__init__(self,name:str,llm:HelloAgentsLLM,system_prompt:Optional[str]None,config:Optional[Config]None,tool_registry:Optional[ToolRegistry]None,enable_tool_calling:boolTrue):super().__init__(name,llm,system_prompt,config)self.tool_registrytool_registry self.enable_tool_callingenable_tool_callingandtool_registryisnotNoneprint(f✅{name}初始化完成工具调用:{启用ifself.enable_tool_callingelse禁用})如果启用了工具调用它会把可用工具描述加入系统提示词中让模型知道当前可以使用哪些工具。例如你可以使用以下工具 - calculator执行数学计算 - search执行网络搜索然后约定工具调用格式[TOOL_CALL:tool_name:parameters]当模型输出类似[TOOL_CALL:calculator:15 * 8 32]Agent 就会解析出工具名和参数调用对应工具把工具结果重新放回对话再让模型基于结果生成最终答案。这说明 SimpleAgent 已经不只是“问答机器人”而是具备了基础的工具增强能力。ReActAgent推理与行动结合ReActAgent 的核心是Thought → Action → Observation → Thought → ...它让模型先思考再选择行动。如果需要外部信息就调用工具如果信息足够就输出最终答案。框架化后的 ReActAgent 主要改进在两个方面。第一提示词格式更严格。它要求模型每轮都按照固定格式输出Thought: 分析当前问题 Action: 工具名[参数] 或 Finish[最终答案]这样程序才能稳定解析模型输出。第二工具调用不再写死而是统一通过ToolRegistry执行。observationself.tool_registry.execute_tool(tool_name,tool_input)这意味着 ReActAgent 不需要知道具体有哪些工具也不需要关心工具内部如何实现。它只负责决定“调用哪个工具、传入什么参数”真正执行由工具注册表完成。ReActAgent 的基本流程可以理解为输入问题 ↓ 构建 ReAct Prompt ↓ LLM 输出 Thought / Action ↓ 如果 Action 是工具调用执行工具并记录 Observation ↓ 继续下一轮 ↓ 如果 Action 是 Finish返回最终答案为了防止无限循环框架中会设置max_steps。如果模型一直不结束达到最大步数后就返回失败提示。ReflectionAgent执行—反思—优化ReflectionAgent 的核心思想是让模型先生成一个初始答案然后再对自己的答案进行审查最后根据反馈进行修改。它的流程是Initial Answer ↓ Reflect ↓ Refine ↓ Final Answer第七章中的框架化版本把提示词抽象成三类initial生成初始答案 reflect审查当前答案 refine根据反馈改进答案这种设计比写死代码更灵活。比如默认情况下它可以用于文章生成、分析任务、总结任务如果换成代码相关提示词它就可以变成代码生成与代码审查 Agent。例如code_prompts{initial:你是Python专家请编写函数:{task},reflect:请审查代码的算法效率:\n任务:{task}\n代码:{content},refine:请根据反馈优化代码:\n任务:{task}\n反馈:{feedback}}这说明框架化后的 ReflectionAgent 不再绑定某一个具体任务而是可以通过自定义 prompt 适配不同场景。它适合用于文本润色 代码优化 方案改进 论文摘要修订 回答质量提升PlanAndSolveAgent先规划再执行Plan-and-Solve 的核心思想是复杂任务不要直接回答先拆解成步骤再逐步解决。流程是用户问题 ↓ Planner 生成计划 ↓ Executor 逐步执行每个子任务 ↓ 汇总最终答案框架化版本的一个重要改进是要求 Planner 输出 Python 列表格式。例如[计算周一销量,计算周二销量,计算周三销量,求三天总和]这样程序可以稳定解析计划而不是从一段自然语言里猜测步骤。执行阶段则由 Executor 根据当前步骤、历史结果和原始问题生成该步骤的答案。它不是一次性解决整个问题而是逐步完成。这个范式适合处理数学应用题 复杂推理题 多步骤分析任务 项目规划 实验方案设计它的优势是过程更清楚但也依赖 Planner 的计划质量。如果第一步计划拆错后面执行也会被带偏。FunctionCallAgent原生函数调用 AgentFunctionCallAgent 是基于 OpenAI 原生 function calling 机制实现的 Agent。它和 ReActAgent 最大的区别是ReActAgent 通过提示词约束模型输出工具调用格式而 FunctionCallAgent 使用模型原生的工具调用能力。两者可以简单对比ReActAgent 依赖 prompt 格式解析例如 工具名[参数] FunctionCallAgent 依赖模型原生 tools / function calling schemaFunctionCallAgent 会把工具转换成 OpenAI function calling schema然后交给模型决定是否调用工具。这样比纯 prompt 解析更稳因为参数通常是结构化 JSON不容易出现格式错乱。它适合用于支持原生工具调用的大模型。如果模型本身支持 function calling那么这种方式通常比手写工具调用格式更可靠。5. 工具系统工具系统是 Agent 能力扩展的关键。LLM 本身只擅长语言理解和生成但它不能直接计算、搜索、访问数据库或调用外部系统。工具系统的作用就是让 Agent 具备“行动能力”。第七章中的工具系统主要包括四层Tool 抽象基类 ToolParameter 参数定义 ToolRegistry 工具注册表 高级工具机制工具链、异步执行、多源搜索Tool 基类统一工具接口工具系统首先需要一个统一的抽象。所有工具都应该有相同的基本接口这样 Agent 才能以统一方式调用它们。classTool(ABC):def__init__(self,name:str,description:str):self.namename self.descriptiondescriptionabstractmethoddefrun(self,parameters:Dict[str,Any])-str:passabstractmethoddefget_parameters(self)-List[ToolParameter]:pass这个设计的重点是两个方法run()执行工具 get_parameters()描述工具需要哪些参数run()让所有工具都有统一执行入口。无论是计算器、搜索工具、文件读取工具还是数据库查询工具对 Agent 来说都是tool.run(parameters)get_parameters()则让工具具备自描述能力。Agent 或框架可以通过这个方法知道工具需要什么参数、参数类型是什么、是否必填。ToolParameter工具参数描述ToolParameter用来描述工具参数。classToolParameter(BaseModel):name:strtype:strdescription:strrequired:boolTruedefault:AnyNone它解决的问题是工具不能只告诉 Agent “我叫 calculator”还应该告诉 Agent “我需要一个 expression 参数它是字符串类型用来表示数学表达式”。例如计算器工具可能需要name: expression type: string description: 要计算的数学表达式 required: true这样做的好处是参数更清楚 更容易做类型校验 更容易生成工具文档 更容易转换成 function calling schema这也是工具系统走向工程化的重要一步。ToolRegistry工具注册与发现ToolRegistry是工具系统的管理中心。它负责注册工具、查找工具、执行工具、生成工具描述。它支持两种注册方式。第一种是注册完整 Tool 对象适合复杂工具registry.register_tool(calculator_tool)第二种是直接注册普通函数适合简单工具registry.register_function(namesearch,description搜索信息,funcsearch_function)这两种方式都很有用。完整 Tool 对象更规范适合需要参数定义、校验、状态维护的工具。普通函数注册更轻量适合快速把已有函数接入框架。ToolRegistry还有一个非常重要的方法get_tools_description()它会把所有工具整理成描述文本然后放入 Agent 的提示词中。这样模型才知道自己有哪些可用工具。例如可用工具 - calculator执行数学计算 - search搜索互联网信息对于基于 prompt 的工具调用来说这一步非常关键。模型只有看到工具说明才可能正确选择工具。OpenAI Schema 转换为了支持 FunctionCallAgent工具系统还需要能把工具定义转换为 OpenAI function calling schema。也就是说工具不仅要能被 prompt 描述还要能被结构化地交给模型。转换后的 schema 大致包含工具名称 工具描述 参数 properties 必填参数 required这样模型就可以按照结构化参数调用工具而不是在自然语言里写一个不稳定的工具调用字符串。这也是工具系统从“提示词调用”走向“原生函数调用”的关键。自定义工具计算器与高级搜索第七章中还展示了如何实现自定义工具。计算器工具适合演示基础工具开发因为它输入简单、结果明确。更复杂的是高级搜索工具。它把多个搜索源整合到一个工具中例如 Tavily 和 SerpAPI。工具内部会按顺序尝试不同搜索源如果一个失败就继续尝试下一个。这种设计体现了工具系统的工程化思维多个后端 自动降级 异常捕获 结果统一返回Agent 不需要知道底层使用了哪个搜索服务。它只需要调用advanced_search具体是 Tavily 返回结果还是 SerpAPI 返回结果由工具内部决定。这就是封装的价值。工具链多个工具顺序执行有些任务不是一个工具能完成的而是需要多个工具串联。例如搜索资料 → 提取关键信息 → 计算结果 → 生成总结第七章中设计了ToolChain用于把多个工具按步骤组织起来。每一步可以指定tool_name调用哪个工具 input_template输入模板 output_key把结果保存到哪个变量执行时工具链会维护一个上下文context前一步的输出可以被后一步使用。例如第 1 步search({input}) → search_result 第 2 步calculator(根据 {search_result} 计算) → calculation_result这种设计已经很接近 LangGraph 中的节点流转思想只不过这里是工具层面的顺序编排。异步工具执行有些工具很耗时比如网络搜索、数据库查询、文件解析。如果顺序执行多个工具整体速度会很慢。因此第七章还实现了异步工具执行器AsyncToolExecutor。它通过线程池并行执行多个工具任务。适合并行的场景包括同时搜索多个关键词 同时调用多个数据源 同时处理多个文件 同时执行多个互不依赖的计算任务这种设计可以明显提高复杂 Agent 的响应速度。但前提是任务之间没有强依赖。如果后一个工具必须使用前一个工具的输出就不能简单并行而应该使用工具链顺序执行。