从零手写 Agent-01搭建最小内核ReAct 模式先抛开所有像langchain,AutoGen 框架这是所有 Agent 的祖宗。我们先实现 Reasoning Acting (ReAct)。不管多复杂的 AgentAutoGPT、DeerFlow物理上只有这三样东西大脑 (LLM Client)能接收文本吐出文本或 JSON。手脚 (Tools)Python 函数读文件、搜网页、算数。循环 (Loop)把 1 和 2 连起来的 while循环。我们的目标写一个 while循环让 LLM 自己决定调用哪个 Python 函数。第 1 步准备 Agent 的工具 ——tools.py这一部分定义 Agent 能真正执行的 Python 函数。大模型本身不会打开文件也不会真的运行代码它只会“说自己想做什么”。真正动手的是这里的工具函数。 tools.py —— Agent 的“手和脚” 大模型本身只会读文字、写文字不能真的打开文件或计算。 所以我们把 Python 函数包装成工具让 Agent 想做事时可以“借用”这些手脚。 # 先定义一个读文件工具Agent 想看本地文件时就会用它。defread_file(path:str):# 这句说明这个函数的用途给它一个路径它会把文件内容读出来。读取本地文件# 读文件可能失败比如路径写错了所以先准备一个“兜底网”。try:# 打开指定文件encodingutf-8 表示按中文友好的方式读取。withopen(path,r,encodingutf-8)asf:# 把文件里的所有文字一次性读出来再交还给 Agent。returnf.read()exceptExceptionase:returnstr(e)# 再定义一个计算器工具Agent 想算数学表达式时就会用它。defcalculator(expression:str):计算数学表达式try:# eval 会把字符串当成 Python 表达式执行这里适合教学演示正式项目不要直接执行用户输入。returneval(expression)exceptExceptionase:returnstr(e)# 这是工具“通讯录”大模型说出工具名字后Python 就靠它找到真正要调用的函数。AVAILABLE_TOOLS{# 当大模型说 Action 是 read_file就调用上面的 read_file 函数。read_file:read_file,# 当大模型说 Action 是 calculator就调用上面的 calculator 函数。calculator:calculator}# 这是给大模型看的“工具说明书”告诉它有哪些工具、每个工具要什么参数。TOOL_SCHEMA[{name:read_file,description:读取指定路径的文件内容,parameters:{path:string}},{name:calculator,description:计算数学表达式如 1 2 * 3,parameters:{expression:string}}]这一阶段做了什么写了read_file()让 Agent 可以读取文件。写了calculator()让 Agent 可以计算表达式。用AVAILABLE_TOOLS建立“工具名 → 函数”的对应关系。用TOOL_SCHEMA写了一份给大模型看的工具说明书。第 2 步给大模型写规则书 ——prompt.py这一部分负责告诉大模型你必须怎么回答。你可以调用哪些工具。你调用工具时必须用什么格式写出来。如果没有这份规则书大模型可能会自由发挥Python 代码就很难解析它到底想干什么。 prompt.py —— 给 Agent 大脑的“行为规则书” 这段系统提示词会在对话一开始发给大模型告诉它必须按固定格式说话。 只有格式稳定后面的 Python 代码才能准确解析出它想调用哪个工具。 importjsonfromtoolsimportAVAILABLE_TOOLS,TOOL_SCHEMA# SYSTEM_PROMPT 就像写给大模型的“考场规则”必须按这个格式一步步回答。SYSTEM_PROMPTf 你是一个智能助手。你必须严格按照以下格式回答 Thought: 你现在在想什么 Action: 要调用的工具名必须是{list(AVAILABLE_TOOLS.keys())}之一 Action Input: 工具的参数JSON格式 Observation: 这里不用写留给系统填充 ...重复 Thought/Action/Action Input/Observation 直到你得出结论 Final Answer: 给用户的最终答案 可用工具列表{json.dumps(TOOL_SCHEMA,indent2)}这一阶段做了什么用SYSTEM_PROMPT规定大模型的输出格式。把AVAILABLE_TOOLS放进提示词让大模型知道工具名不能乱写。把TOOL_SCHEMA转成 JSON 文本让大模型知道每个工具需要什么参数。第 3 步创建 Agent 主程序 ——rootagent.py这一部分是整个模块的核心。它负责把三件事串起来调用大模型让它思考。解析大模型想调用的工具。执行工具并把结果交还给大模型。 rootagent.py —— 最简 Agent 的“主程序” 这里把三样东西串起来大模型是“大脑”tools.py 里的函数是“手脚”循环是“导演”。 程序会让大模型先想一想再决定要不要调用工具拿到工具结果后继续思考直到给出最终答案。 importjsonimportos# 引入 OpenAI 客户端阿里百炼提供的是 OpenAI 兼容接口所以可以用同一套 SDK 调用。fromopenaiimportOpenAIfrompromptimportSYSTEM_PROMPTfromtoolsimportAVAILABLE_TOOLS# SimpleAgent 是我们亲手搭出来的最小 Agent把“大脑、手脚、循环”装进一个类里。classSimpleAgent:# 初始化方法每创建一个 Agent就先准备好大模型客户端和对话记忆。def__init__(self): history: 保存对话上下文agent 的记忆本。 # BASE_URL 是大模型服务的地址就像“大脑办公室”的门牌号。BASE_URLhttps://dashscope.aliyuncs.com/compatible-mode/v1# client 是和大模型通话的“电话”API Key 从环境变量里拿self.clientOpenAI(base_urlBASE_URL,api_keyos.getenv(AI_DASHSCOPE_API_KEY))# 初始对话先放 system 消息相当于开场前先告诉大模型游戏规则。# SYSTEM_PROMPT 会告诉它有哪些工具以及必须用 Thought/Action 这种格式回答。self.history[{role:system,content:SYSTEM_PROMPT}]# run 是 Agent 的主循环用户给任务后它会反复“思考 → 动手 → 看结果”。defrun(self,user_task:str,max_steps10): :param user_task: 用户交给 Agent 的任务比如“读取 test.txt 并计算数字之和”。 :param max_steps: 最多让 Agent 思考几轮防止它一直绕圈停不下来。 :return: 这个演示版直接打印过程不额外返回结果。 # 把用户刚说的话存进“记忆本”这样大模型下一次调用时能看到任务。self.history.append({role:user,content:user_task})# 开始循环让 Agent 自己决定每一步要不要用工具、用哪个工具。forstepinrange(max_steps):# 打印当前是第几轮方便我们像看侦探剧一样追踪 Agent 的思路。print(f\n--- Step{step1}---)# 第 1 步调用 LLM让“大脑”根据历史记录继续思考。responseself.client.chat.completions.create(# 指定要使用的模型这里用阿里百炼里的 qwen3.6-plus。modelqwen3.6-plus,# 把完整记忆本交给大模型它才能知道前面发生过什么。messagesself.history,# temperature0 表示尽量稳定输出少一点随机发挥方便我们解析格式。temperature0)# OpenAI 兼容协议阿里百炼 / 通义也完全一致接口返回标准结构。# 如果想调试完整返回报文可以把下面两行取消注释。# json_str json.dumps(response.model_dump(), ensure_asciiFalse, indent2)# print(f返回报文{json_str})# 从返回结果里取出大模型真正说的文字内容。contentresponse.choices[0].message.content# 把大模型这一轮的想法打印出来方便观察它决定了什么。print(f大模型\n{content},end\n---------------------\n)# 把大模型刚才的回复也存进“记忆本”下一轮它能接着自己的思路往下走。self.history.append({role:assistant,content:content})# 第 2 步如果大模型已经写出 Final Answer说明它认为任务完成了。ifFinal Answer:incontent:# 打印完成提示然后跳出循环不再继续调用工具。print(\nTask Completed!)# break 就像按下停止键结束这次 Agent 任务。break# 第 3 步从大模型输出里提取 Action 和 Action Input。# Action 是它想用哪个工具Action Input 是它想给工具传什么参数。action,action_inputself._parse_action(content)# 如果这一轮没有解析出工具名就先跳过进入下一轮让大模型继续说清楚。ifnotaction:# continue 表示“这轮先到这儿直接开始下一轮”。continue# 第 4 步执行工具关键点是工具由 Python 执行不是大模型自己执行。observationself._execute_tool(action,action_input)# 打印工具返回结果Observation 就像工具递回来的“小纸条”。print(fObservation:\n{observation})# 第 5 步把工具结果塞回记忆本让大模型看到“刚才动手后的结果”。self.history.append({# 这里用 user 角色模拟外部世界告诉 Agent工具执行结果来了。role:user,# Observation 后面加一句“请继续思考”引导大模型进入下一轮推理。content:fObservation:{observation}\n请继续思考。})# _parse_action 负责从大模型的一大段文字里抠出“工具名”和“工具参数”。def_parse_action(self,text:str):# 这是一个简易解析器如果连 Action 都没有说明大模型没要求调用工具。ifAction:notintext:# 返回两个 None告诉主循环“这轮没有可执行动作”。returnNone,None# 把大模型回复按行切开后面就能一行一行找关键词。linestext.split(\n)# action 先设为空等找到 Action: 那行再填进去。actionNone# action_input 也先设为空等找到 Action Input: 那行再解析 JSON。action_inputNone# 遍历每一行就像在一张纸上逐行找“工具名”和“参数”。forlineinlines:# 如果这一行以 Action: 开头就说明后面跟着工具名。ifline.startswith(Action:):# 去掉 Action: 标签只留下真正的工具名并清理前后空格。actionline.replace(Action:,).strip()# 如果这一行以 Action Input: 开头就说明后面跟着工具参数。ifline.startswith(Action Input:):# 参数应该是 JSON但大模型偶尔可能写歪所以这里也加兜底。try:# 去掉标签后把 JSON 字符串解析成 Python 字典。action_inputjson.loads(line.replace(Action Input:,).strip())# 如果 JSON 解析失败就给一个空字典避免程序直接崩掉。except:# 空字典表示“没有拿到可用参数”。action_input{}# 把解析到的工具名和参数交还给主循环。returnaction,action_input# _execute_tool 负责真正调用 Python 函数也就是让 Agent 的“手脚”开始干活。def_execute_tool(self,action:str,action_input:dict):# 先检查工具名在不在通讯录里防止大模型瞎编一个不存在的工具。ifactionnotinAVAILABLE_TOOLS:# 如果工具不存在就返回错误文字让大模型知道它选错工具了。returnfError: Tool {action} not found.# 从工具通讯录里拿到真正的 Python 函数。tool_funcAVAILABLE_TOOLS[action]# 真正执行工具时也可能出错所以再加一层兜底。try:# **action_input 会把字典拆成关键字参数好比把包裹拆开后逐件交给函数。resulttool_func(**action_input)# 工具结果统一转成字符串方便塞回大模型的上下文。returnstr(result)# 如果函数执行失败就抓住异常。exceptExceptionase:# 把执行错误返回给大模型让它有机会根据错误继续调整。returnfExecution Error:{e}# 只有直接运行这个文件时下面的示例才会执行被别的文件导入时不会自动跑。if__name____main__:# 创建一个 SimpleAgent相当于请出一个会思考、会用工具的小助手。agentSimpleAgent()# 测试让 Agent 读取当前目录下的 test.txt然后计算文件中数字的和。agent.run(请读取当前目录下的 test.txt 文件并计算出文件中数字的和。)这一阶段做了什么__init__()准备了大模型客户端和对话历史。run()负责启动 Agent 的主循环。_parse_action()从大模型回复里找出工具名和参数。_execute_tool()根据工具名调用真实 Python 函数。if __name__ __main__提供了一个可以直接运行的测试入口。第 4 步把三份代码连三个文件之间的关系可以这样理解tools.py ↓ 提供工具函数和工具说明 prompt.py ↓ 把工具说明写进系统提示词 rootagent.py ↓ 调用大模型、解析工具请求、执行工具、继续循环 最终得到 Agent 的回答也可以用一句话总结tools.py负责“能做什么”prompt.py负责“怎么告诉大模型”rootagent.py负责“让它们一轮轮配合起来”。第 5 步运行agent.run(请读取当前目录下的 test.txt 文件并计算出文件中数字的和。)大致过程是第 1 轮大模型决定读取 test.txt 第 2 轮大模型拿到文件内容后决定调用 calculator 第 3 轮大模型拿到计算结果后输出 Final Answer第 6 步本模块的最小闭环01RootAgent最重要的不是代码多复杂而是它已经具备 Agent 的最小闭环用户任务 ↓ 大模型思考 Thought ↓ 选择工具 Action ↓ 给出参数 Action Input ↓ Python 执行工具 ↓ 返回 Observation ↓ 大模型继续思考 ↓ Final Answer这就是最基础的 Agent 工作方式。之后会逐步升级添加功能…源码: githubgitee