首页 / 资讯中心 / 文章详情

Agent 概念、原理与构建模式:从 ReAct 循环到可运行代码深度解析

Agent 概念、原理与构建模式:从 ReAct 循环到可运行代码深度解析 ★ FEATURED ARTICLE
1. 从一次“模型只会聊天”的翻车说起很多人第一次写 Agent都会卡在同一个地方模型能回答问题但不会“动手”。你让它读一个文件、跑一条命令、查一次接口它回你一段看起来很像答案的文字实际什么都没执行。这不是模型不行而是你只给了它一张嘴没给它一双手。Agent 要解决的核心问题就是把“语言模型”升级成“能推理、能决策、能行动”的闭环系统。它不再是单轮问答而是让模型在每一轮里先想清楚要做什么Reasoning再选一个工具去执行Acting拿到真实结果后继续想Observation直到任务完成。这个循环就是 ReActReasoning Acting 的缩写。这篇面向想从零理解 Agent 内部机制的开发者。我会把 ReAct 循环拆成可复制的伪代码再给一个最小可运行示例重点讲清楚三件事Thought / Action / Observation 每一轮到底长什么样、怎么验证输出符合预期、以及单 Agent、多 Agent 协作、工具调用这几种构建模式的代码骨架差异。读完你能自己搭一个能读文件、跑命令、并打印完整推理链的 Agent。在动手之前先解决模型接入这一层。Agent 每一轮都要调模型如果模型来源不稳定调试推理链会非常痛苦。我习惯用一个统一的模型入口来跑这类实验下面先把它配好。2. TaoToken 前置给 Agent 一个稳定的模型入口Agent 和普通聊天最大的区别是调用频率。一个任务跑下来ReAct 循环可能触发 5 到 20 次模型请求每次都要带上完整对话历史。如果模型入口不稳定你会分不清是“推理逻辑写错了”还是“请求失败了”。TaoToken 在这里的角色是统一模型入口它兼容 OpenAI SDK 的调用格式你只要改base_url和api_key就能让 Agent 用同一套代码切换不同模型。对调试 ReAct 循环特别有用因为你可以先用一个便宜快速的模型把循环跑通再换成更强的模型看推理质量。你需要准备两样东西一个 API Key以及确认接入地址。Key 在控制台生成接入地址用https://taotoken.net/api。注意这个地址不带任何查询参数直接作为base_url使用。提示Agent 调试阶段建议把每轮请求的 messages 长度打印出来。ReAct 循环会把历史不断追加token 消耗是随轮次增长的早发现异常能省不少成本。拿到 Key 之后不要硬编码在代码里。用.env文件管理配合python-dotenv读取。这样你分享代码时不会泄露密钥切换环境也方便。下面进入具体配置。3. 可复制配置ReAct Agent 的最小骨架先建项目结构。我习惯把提示词、工具、Agent 主体分开方便单独调试react-agent/ ├── .env ├── agent.py ├── prompt.py └── tools.py.env里只放一行TAOTOKEN_API_KEY你的keytools.py定义工具。工具的本质就是普通 Python 函数Agent 通过函数名来调用它们import subprocess def read_file(file_path: str) - str: 读取文件内容 with open(file_path, r, encodingutf-8) as f: return f.read() def write_to_file(file_path: str, content: str) - str: 写入文件内容 with open(file_path, w, encodingutf-8) as f: f.write(content) return 写入成功 def run_command(command: str) - str: 执行终端命令 result subprocess.run( command, shellTrue, capture_outputTrue, textTrue ) if result.returncode 0: return result.stdout or 执行成功 return f错误{result.stderr}prompt.py是 ReAct 的灵魂。它用 XML 标签约束模型输出格式让每一轮都能被程序解析REACT_PROMPT 你需要解决一个问题把它分解为多个步骤。 每一步先用 thought 思考要做什么再用 action 决定调用哪个工具。 工具执行后你会收到 observation继续思考直到能给出 final_answer。 可用工具 {tools} 严格使用以下格式输出 thought你的思考/thought action工具名(参数)/action observation工具返回结果/observation final_answer最终答案/final_answer agent.py是核心循环。注意这里用base_url指向 TaoToken其余调用方式和 OpenAI SDK 完全一致import os import re from openai import OpenAI from dotenv import load_dotenv from tools import read_file, write_to_file, run_command from prompt import REACT_PROMPT load_dotenv() client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.getenv(TAOTOKEN_API_KEY), ) TOOLS { read_file: read_file, write_to_file: write_to_file, run_command: run_command, } def build_system_prompt(): tool_desc \n.join(f- {name} for name in TOOLS) return REACT_PROMPT.format(toolstool_desc) def parse_action(text): match re.search(raction(.*?)/action, text, re.DOTALL) if not match: return None, None raw match.group(1).strip() name_match re.match(r(\w)\((.*)\), raw, re.DOTALL) if not name_match: return None, None return name_match.group(1), name_match.group(2).strip() def run_agent(task, max_steps8): messages [ {role: system, content: build_system_prompt()}, {role: user, content: fquestion{task}/question}, ] for step in range(max_steps): resp client.chat.completions.create( modelgpt-4o-mini, messagesmessages, ) content resp.choices[0].message.content messages.append({role: assistant, content: content}) thought re.search(rthought(.*?)/thought, content, re.DOTALL) if thought: print(f[Step {step}] Thought: {thought.group(1).strip()}) if final_answer in content: answer re.search( rfinal_answer(.*?)/final_answer, content, re.DOTALL ) return answer.group(1).strip() name, args parse_action(content) if not name: print(未解析到 action终止) break try: observation TOOLS[name](args) except Exception as e: observation f工具执行错误{e} print(f[Step {step}] Action: {name}({args})) print(f[Step {step}] Observation: {observation[:200]}) messages.append( {role: user, content: fobservation{observation}/observation} ) return 达到最大步数未完成 if __name__ __main__: print(run_agent(读取 tools.py 并告诉我里面有几个函数))这段代码就是 ReAct 循环的最小可运行版本。它做了四件事把工具列表注入提示词、每轮请求模型、解析 Thought 和 Action、执行工具并把 Observation 追加回对话历史。循环的终止条件是模型输出final_answer或达到最大步数。4. 验证请求每轮 Thought / Action / Observation 是否符合预期跑起来之后重点不是看最终答案而是看每一轮的中间输出。这才是理解 Agent 内部机制的关键。用上面那个“读取 tools.py 并数函数”的任务正常输出应该长这样[Step 0] Thought: 我需要先读取 tools.py 文件的内容才能知道里面有几个函数。 [Step 0] Action: read_file(tools.py) [Step 0] Observation: import subprocess def read_file(file_path: str) - str: ... [Step 1] Thought: 文件内容已获取我数一下 def 开头的函数定义。 [Step 1] Action: run_command(grep -c def tools.py) [Step 1] Observation: 3 [Step 2] Thought: 已经确认有 3 个函数可以给出最终答案。验证时盯三个点。第一Thought 是否在描述“下一步要做什么”而不是直接给答案。如果模型跳过思考直接输出 final_answer说明提示词约束不够强。第二Action 的函数名是否在工具列表里参数格式是否合法。第三Observation 是否是工具的真实返回而不是模型编造的。第三点最容易出问题因为模型有时会在没有执行工具的情况下“假装”收到了结果。你可以加一个断言来强制校验。在追加 observation 之前检查它确实来自工具执行assert observation is not None, Observation 不能为空 assert name in TOOLS, f未知工具{name}如果发现模型连续两轮调用同一个工具、参数也一样说明它陷入了循环。这时候要么在提示词里加“不要重复调用相同工具”要么在代码里检测重复 action 并强制终止。我试过在run_agent里维护一个seen_actions集合重复就跳出比单纯靠 max_steps 更早发现问题。5. 三种构建模式的代码骨架差异理解了单 Agent 循环再看多 Agent 协作和工具调用就只是骨架的排列组合。单 Agent 就是上面那套一个循环、一份工具列表、一条对话历史。适合任务边界清晰、步骤不多的场景比如“读文件 改内容 跑测试”。多 Agent 协作的核心变化是“谁持有对话历史”。常见做法是拆出一个协调者Orchestrator和若干执行者Worker。协调者不直接调工具而是把子任务分发给 Worker每个 Worker 是独立的 ReAct 循环class Worker: def __init__(self, name, tools): self.name name self.tools tools def run(self, subtask): # 内部就是一个完整的 ReAct 循环 return run_agent(subtask) def orchestrate(task): workers { reader: Worker(reader, {read_file: read_file}), runner: Worker(runner, {run_command: run_command}), } plan client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: f把任务拆成子任务{task}}], ).choices[0].message.content # 按 plan 分发汇总结果 return plan多 Agent 的坑在于上下文传递。Worker 之间不共享对话历史协调者必须把上一个 Worker 的关键输出显式传给下一个否则执行者会“失忆”。这也是为什么多 Agent 更适合子任务之间耦合低的场景。工具调用模式则是把“选工具”这一步交给模型的原生 function calling而不是靠 XML 解析。骨架差异在于你不再解析action标签而是把工具定义成 JSON Schema 传给模型模型返回结构化的tool_calls字段。好处是解析更稳坏处是推理过程Thought不再显式暴露调试时看不到模型的思考链。想看清内部机制还是 XML 标签的 ReAct 更直观。6. 本篇常见错排查报错一KeyError: read_file。模型输出的 action 函数名和工具字典的 key 对不上。常见原因是提示词里工具描述带了括号或参数模型照抄了。检查build_system_prompt里注入的工具名是否干净只保留函数名。报错二模型一直不输出final_answer。要么是提示词没强调终止条件要么是任务本身需要的信息工具给不了。先在提示词里加一句“当你有足够信息时必须输出 final_answer”再检查工具返回是否为空。报错三Observation 被模型忽略。如果模型下一轮 Thought 完全没提上一轮的 Observation通常是消息角色用错了。Observation 要以user角色追加而不是assistant否则模型会以为那是自己说过的话。报错四请求 401 或连接失败。检查.env里的 key 是否被正确加载base_url是否写成了https://taotoken.net/api。注意不要在这个地址后面拼多余的路径SDK 会自动补全/v1/chat/completions。报错五循环停不下来。除了 max_steps 兜底建议在代码里检测连续重复的 action。一旦发现相同工具加相同参数出现两次直接中断并打印当前 messages方便定位是提示词问题还是工具返回有问题。7. 下一步把循环跑通再谈优化Agent 的门槛不在概念而在把第一轮循环跑通。你不需要一上来就搞多 Agent 协作先用单 Agent 加两三个工具把 Thought / Action / Observation 的打印看清楚确认每一轮输出都符合预期再考虑扩展。调试阶段建议固定一个模型把循环跑顺。等推理链稳定了再通过统一入口切换模型对比效果。需要生成 Key 和查看接入方式可以从控制台和 API Keys 页面入手想先直观感受模型在对话里的表现可以用模型对话页面试几轮如果打算把 Agent 长期用在编码或自动化任务上Coding Plan 更适合持续跑循环的场景。接入细节都在接入文档里照着改base_url就能复用现有代码。最后留一个实用习惯每次改完提示词先跑一个只有一步就能完成的任务比如“读取某个文件的第一行”。一步能跑对再逐步加复杂度。Agent 的 bug 大多藏在多轮循环里从最短路径验证起比一上来就跑复杂任务高效得多。
阅读完成 · 觉得有帮助?
咨询建站