Agent底层机制拆解:ReAct循环、工具与记忆的工程实践
单纯把 ChatGPT 接上几个工具离一个真正可用的 Agent 还很远。我在本地跑过很多 demo最常见的情况是模型调用工具时找一个不存在的函数、参数格式反复解析失败或者是链条进了一个死循环同一个搜索动作重复执行十几遍。这些问题不是某个框架的 bug而是我们还没搞懂 Agent 底层那套思考-行动-观察的循环机制。这篇文章我就把 LangChain Agent、ReAct 范式、Copilot-Agent 式的规划执行以及 Manus 这类产品的异步自治思路、Computer Use 的 GUI 控维和记忆机制的工程实现掰开揉碎讲清楚顺便把踩过的坑都摆出来。1. Agent 不是聊天机器人加工具调用先拆开看它到底由什么组成很多人第一次接触 Agent第一反应就是把 OpenAI Function Calling、LangChain 的 Tool 接进 ChatGPT 套壳觉得能调用工具了就是 Agent。这个理解只对了一半。它确实算 Agent 的基础形态但不是标题里说的那种自主行动的 Agent。1.1 Agent 的三个核心模块推理器、工具执行器、记忆我把一个能稳定工作的 Agent 拆成三个部分这个拆法对理解和排查问题都很有用推理器Planner/Reasoner通常是 LLM Chat Model。它负责理解目标、拆解步骤、判断下一步调用哪个工具、解读工具返回的结果。这是 Agent 的大脑。工具执行器Executor负责真正执行动作。它可以是 Function Calling 的参数解析器、LangChain 的 AgentExecutor、或者是 Computer Use 里的视觉-坐标控制模块。它相当于 Agent 的手。记忆Memory负责保存任务过程中的上下文和跨会话的信息。短期记忆对应上下文窗口长期记忆对应向量数据库、摘要缓存或反思总结。它是 Agent 保持连续感的关键。判断一个系统是不是真正的 Agent我有个很简单的测试方法给它一个需要连续多步推理才能完成的开放任务并故意在中间插入一步指令不明确的操作。如果系统能做出一系列的中间决策并根据中间结果不断修正后续动作直到完成目标它是一个 Agent。如果它只是把用户输入丢进一个固定 prompt 调一下 LLM再把结果原样返回那它就是套壳的聊天机器人无论背后接了多少工具。1.2 为什么单独一次 LLM 调用几乎不可能完成复杂任务这里有一个容易被忽略的原因LLM 的上下文在任务执行过程中是逐步变化的。你问它帮我查一下某地区的近三年物价数据然后写一份对比报告模型在第一次调用时只能基于静态的知识来完成。它不知道真实世界里物价数据长什么样、以什么格式存在哪个网站上更没法动态决定先翻页再搜索、看到数据格式不对就换个爬取方式。它如果强行一步到位纯粹是在编造一个看起来合理的答案幻觉率会非常高。真正的 Agent 会把任务拆成多轮每一轮先生成动作再观察结果然后把观察结果拼进上下文里再进行下一轮推理。这就引出了 Agent 领域最基础、也是应用最广的决策范式——ReAct。2. ReAct 推理范式Agent 决策循环的草稿纸机制ReAct 的全称是 Reasoning Acting核心思想就两个字交替。模型一边把推理过程用自然语言写出来Reasoning一边根据推理结果执行具体动作Acting然后观察动作的返回值把这些信息喂回模型再进入下一轮循环。整个过程就像一个人在草稿纸上一步步演算数学题而不是一口报出答案。2.1 Thought / Action / Observation 三段式循环ReAct 的每一轮输出都遵循固定的三段式结构你可以把它理解成 Agent 的思维日志Thought思考模型用自然语言描述自己当前对任务的理解和下一步计划。这一步非常重要它把隐式的推理过程显式化了。Action行动模型选择一个工具并按工具的 JSON Schema 生成调用参数。例如search(2024年某市房价中位数)。Observation观察工具执行后返回结果作为新的输入拼进上下文。这个循环会一直持续直到模型输出了一个标志性的结束动作——在大部分实现里叫final answer表示任务已完成不再需要调用工具。我实际跑 LangChain 时最直观的感受是Thought这一项写得好不好直接决定后面Action的正确率。如果模型在 Thought 阶段就理清了逻辑比如数据源返回的是 PDF 格式我需要先调用解析工具而非搜索工具那么后面的参数生成通常会准确很多。反过来如果 Thought 写得含糊模型大概率会抓一个最像样子的工具乱调。2.2 ReAct 为什么能显著降低幻觉把推理轨迹变成上下文ReAct 能有效的原因没有想象中那么玄学。它把模型的每一步中间推理都写进了上下文后续的生成被这些真实的文本 token 约束住了。举个例子一个 Agent 要判断某公司是否在某个城市设了研发中心。如果直接让 LLM 回答它很可能会基于训练语料里的模糊印象给出一个结论不管语料是否过时。但如果走 ReAct 流程它先搜到公司官网的信息Observation 里明确写着该城市并无相关研发岗位那么下一轮生成就会基于这个真实观察来进行推理而不是靠猜。这个机制用我在工程里常用的比喻就是推理轨迹相当于给模型一张草稿纸。草稿纸不保证答案一定对但它能有效防止模型跳步骤。跳步骤正是推理类任务里幻觉率最高的行为。这也是为什么后来各种 Agent 框架都会在 prompt 里刻意强调show your thought step by step。2.3 一个完整的 ReAct 决策示例这里用伪代码展示一下典型的 ReAct 执行流程方便理解每一轮循环发生的消息传递。实际项目中可以基于 OpenAI Function Calling 或 LangChain 快速实现from langchain.agents import AgentExecutor, create_react_agent from langchain.tools import Tool from langchain_openai import ChatOpenAI tools [ Tool( namesearch, funcsearch_function, description搜索公开网页内容返回最新信息。输入为查询关键词字符串。, ), Tool( namecalc, funccalc_function, description执行数学表达式计算。输入为合法的数学表达式字符串。, ), ] llm ChatOpenAI(modelgpt-4o-mini, temperature0) agent create_react_agent(llm, tools, system_promptREACT_SYSTEM_PROMPT) agent_executor AgentExecutor( agentagent, toolstools, max_iterations8, # 防止死循环 verboseTrue, # 打开后可以看到 Thought/Action 的完整轨迹 ) result agent_executor.invoke({input: 某城市去年的GDP增速是多少比前年高还是低}) print(result[output])运行之后verboseTrue会把类似下面的轨迹打出来Thought: 我需要先找到某城市去年的GDP数据再对比前年数据。 Action: search(某城市 去年 GDP 增速) Observation: 返回了一条新闻摘要提及该市去年GDP同比增长7.2%。 Thought: 新闻摘要中提到了去年增速但没有前年数据。我需要搜索前年的数据。 Action: search(某城市 前年 GDP 增速) Observation: 返回的数据显示前年增速为6.5%。 Thought: 现在两者都有了去年7.2%高于前年6.5%可以给出最终答案。 Final Answer: 该市去年GDP增速为7.2%相比前年的6.5%提高了0.7个百分点。整个过程中有一段完整的推理-行动-观察轨迹每步都有据可查。这也是排错的关键如果最终结果错了看轨迹就能知道是哪一步搜索给错了数据或者 Thought 阶段理解错了方向。3. LangChain Agent 落地实践Executor 机制和五个常见坑讲完 ReAct 的基本型我们来看看在 LangChain 生态里实际落地会碰到的细节。很多人以为 LangChain Agent 只是vectorstore 连 LLM 再加个检索其实它的核心是 executor 的调度逻辑。Executor 内部是一个不断循环的模型调用 工具调用过程而这个过程里有不少地方容易踩坑。3.1 BaseAgentExecutor 的循环机制以及它为什么容易迷路LangChain 里AgentExecutor的循环本质上是这样的把用户的输入、Agent 的历史轨迹、工具的描述全部组装成 prompt。调用 LLM解析输出。解析方式取决于agent类型比如create_react_agent会切出Thought/Action/Action Input三段create_openai_functions_agent则解析 OpenAI Function Calling 的原生 JSON。如果模型输出里带Final Answer直接返回结果给用户。否则根据模型指定的工具名和参数调用真实的函数。把 Observation 追加回历史轨迹回到第 1 步。听起来很顺滑但问题出在模型有可能失败这个谁也躲不掉的事实上。实际工程里最常出现的情况是模型在第三步连续多次输出不规范的格式比如Action Input缺失或者工具名里多了个空格导致解析器抛异常。很多教程不会写这一层但这是几乎所有 Agent 框架默认带max_iterations限制的原因——没有上限的话一个解析错误会让 Agent 陷入调用模型→解析失败→调用模型的死循环白烧 token 还拿不到结果。我在实践中通常把max_iterations设置为 6 到 10同时把handle_parsing_errors设为True让解析异常时的反馈也能进入下一轮模型调用帮助模型自我修复。3.2 工具描述的质量直接决定模型会不会选错工具这是 LangChain Agent 项目里最影响效果、也最容易被低估的环节。工具的描述就像一个商品的详情页LLM 完全靠描述来决定该不该用这个工具。我踩过一次很典型的坑有一个搜索工具我给它起的名字叫lookup_news描述里为了偷懒写了 Lookup relevant info from web search engine。另一个计算工具叫math_calculator。结果模型在需要算平均数的时候半天不调用math_calculator反复去调lookup_news因为它的描述里包含了 lookup 这个太泛的词模型觉得任何查找类操作都该用它。后来我把工具描述改成包含明确边界和输入输出示例的完整版本例如名称: math_calculator 描述: 计算数学表达式支持加减乘除、百分比和平方根。输入必须是可直接求值的字符串 如 20% 或 (128)/2。仅在需要数值计算时使用不要用于数据查询。模型选工具的成功率一下子好了很多。工具越接近一个职责清楚、边界明确的小函数Agent 的决策质量越高。这也是为什么我给团队的默认建议是工具宁可少而精也不要一次堆二十个让模型挑花眼。当工具数量超过 15 个模型选择准确率会明显下降尤其在新手项目里十个工具里面真正会被调用的往往只有四五个。3.3 记忆集成到 Agent 时最容易忽视的上下文膨胀问题很多新手给 LangChain Agent 加记忆就是把ConversationBufferMemory一股脑塞进去。结果任务一长每个工具返回的 Observation 和所有中间推理都在上下文里越攒越多很快就把窗口撑爆或者把早期的重要信息「挤」到模型已经不太关注的位置。我用过一个比较稳的做法对指令类 Agent用ConversationSummaryMemory压缩历史对需要精确数据的步骤把每步工具返回的原始结果只保留关键字段。举一个实际场景模型要连续抓取十个网页并汇总同一个字段每个网页返回 2000 token 原始 HTML。如果全量塞进上下文不用五轮窗口就爆了。我的做法是自定义一个summarize_observation函数每次把返回文本截断为 300 token并让推荐模型在下一轮只需关注字段名不需要看全文。工具返回内容截断版: 页面A: 城市Y房价挂牌价2.8w/平2024年6月 页面B: 城市Y房价挂牌价3.1w/平2025年1月把中间过程压缩成这样模型的决策负担小很多幻觉率和失败率都会降。3.4 结构化输出的解析策略从正则到强制 JSON 到函数调用早期试 LangChain 的 ReAct 第一步就会遇到模型输出格式不标准的问题。ReAct 模板要求模型输出Thought/Action/Action Input三段但模型有时不配合把 Action 和 Thought 写在同一行或者 Action Input 写成了一个 JSON 数组而不是字符串。这一度是 LangChain Agent 最大的痛点。后来我改用create_openai_functions_agent和 OpenAI 原生 Function Calling这个问题明显改善因为函数调用的参数格式由模型结构化保证解析不再依赖正则。不过 Function Calling 方案也有它的局限它要求底层的 LLM 服务支持函数调用协议。如果接的是开源模型或自部署模型支持程度参差不齐我还是会准备一套正则解析的兜底逻辑。核心就一句话生产环境不要假设模型输出永远是完美的解析层必须有明确的失败反馈通道把解析错误信息交给模型去自我修正而不是直接抛异常。3.5 Agent 执行失败路径的设计超时、预算与告警这里要给所有打算把 Agent 放到真实业务流程里的人提个醒。Agent 的失败模式比传统接口丰富得多死循环、反复申请重试、工具返回异常、模型突然开始胡说八道……如果只靠 max_iterations 兜底那也只是终止不是善终。我通常会为关键 Agent 配三个防御步数上限硬性限制max_iterations避免 token 烧爆。时间预算给整个 AgentExecutor 包一层超时比如单次任务 60 秒无完成就结束返回部分结果。落盘审计日志把每轮 Thought、Action、Observation 都写入日志。线上出问题时不至于两眼一抹黑。有了这三层Agent 诡异行为至少是可知的、可复现的不会直接拖垮业务链路。4. 从边想边做到先想再做Copilot-Agent 模式与 Manus 的异步自治ReAct 是边想边做。它灵活但代价是慢——每做一步都要调一次 LLM 等一次网络往返中间还容易绕弯。后来业界逐渐演化出了 Copilot-Agent 模式和 Manus 这类异步自治产品它们的核心差异在于把规划和执行两个阶段分开。4.1 Copilot-Agent 模式Plan-and-Execute 的进化Copilot-Agent 模式通常会先由一个 Planner 模型接收用户目标输出一份完整的执行计划比如第 1 步搜索 A第 2 步解析页面 B第 3 步用数据生成表格。然后执行器按计划去调用工具中间不需要频繁回到 Planner 决策。这种模式的好处很明显LLM 推理的次数大幅降低延迟更低token 成本更低而且在任务边界清晰时效果更稳。但代价是当执行过程中遇到计划之外的突发情况比如页面结构变化、数据缺失执行器没有 Plan-B 的推理能力。所以 Copilot-Agent 本质上是把一个 LLM 的能力用在计划阶段而不是每一轮。如果任务本身是可预测的多步流水线比如每天定时抓取天气、处理成简报、发到指定渠道Copilot-Agent 比 ReAct 好用得多。如果任务是一个完全未知的开放式探索比如帮我研究一下某个冷门领域的前沿方向并写一份综述ReAct 更合适因为它可以随时根据搜到的内容调整方向。4.2 Manus 类异步自治 Agent云端大脑、任务队列与沙箱执行Manus 这类产品给我的启发更大。它的模式我理解成三层任务接收层用户提交一个大目标不用在交互过程中持续盯着。云端调度层一个任务会被自动拆成多个子任务生成依赖关系再把子任务按优先级分发给不同模型或工具链执行。沙箱执行层实际运行代码、操作网页的独立环境。任务完成后把结果打包异步通知用户。这其实就是把 Agent 从同步对话变成了异步项目制。用户不需要守在聊天窗口前等模型一步一步发挥而是像提交一个项目需求一样等交付。它的核心价值是把自主提到最高——目标明确后Agent 自己决定路径用户只验收结果。如果想要复刻这样一个异步自治 Agent工程上最难的不在模型选择而在任务拆解的正确性和沙箱安全。任务拆解错了后面每一步都是白干。我通常会让规划器输出一个带依赖的 JSON 结构而不是让模型自由写一串计划文本。例如{ plan: [ {id: 1, task: 搜索报告来源, depends_on: [], tools: [web_search]}, {id: 2, task: 提取数据表格, depends_on: [1], tools: [page_parser]}, {id: 3, task: 生成总结, depends_on: [2], tools: [llm_write]} ] }这样一个结构化的 plan 可以被程序校验比如检查是否存在循环依赖、是否有缺失依赖。相比纯文本计划它更容易做异常处理——某个子任务失败了可以只重跑该节点而不用把整个 Agent 推到重来。4.3 Computer Use当 Agent 的工具从 API 变成 GUIComputer Use 这类技术是个分水岭。它不再局限于调用结构化 API而是让 Agent 直接操作计算机界面观察屏幕截图、决定点击哪里、输入什么内容以完成原本需要人工 GUI 操作的任务。它的实现大致要过三关视觉理解模型需要从截图里识别出按钮、输入框、菜单知道登录按钮在右上角黄色区域。操作生成模型输出结构化操作序列比如click(x1240, y380)、type(text...)、scroll()。状态反馈每次操作后系统截取新的屏幕状态让模型判断操作是否生效、下一步做什么。Computer Use 的难点在于 GUI 没有稳定的 API 文档界面渲染有一点变化可能就会导致整个流程崩掉。它更适合场景固定、界面变化不频繁的企业内部系统比如把旧系统上一个重复的手工 RPA 流程迁移成 Agent 自动操作。需要注意的一点是不要拿 Computer Use 去替代那些已经有完善 API 的系统那样做的开发成本和失败率都物非所值。能调 API 的优先调 API调不到的才考虑让 Agent看屏点击。4.4 几种 Agent 模式的选型对比从任务特征出发用一张表来总结我对这几种模式的选择逻辑方便抄作业模式核心特征拟合场景短板ReActLangChain 默认边想边做每步决策开放式探索、目标模糊调用次数多成本高容易绕弯Copilot-AgentPlan-and-Execute先规划后执行少推理流程固定、步骤明确的多步任务突发情况适应力差Manus 式异步自治异步执行、子任务调度用户只需验收结果的大任务工程复杂度高排障成本大Computer Use操作 GUI无 API 的遗留系统、界面稳定对视觉和界面变化敏感选型时优先看用户对过程有没有参与需求。如果用户想一步步看 Agent 干活选 ReAct如果用户只要结果选异步自治或 Plan-and-Execute如果操作对象是网页 GUI 且流程固定Computer Use 值得考虑。5. 记忆机制Agent 的记忆远不是向量数据库检索很多 Agent 项目把记忆简单理解成把历史对话存到向量数据库再检索相关片段——这个认知坑了不少人。我做了几个跑长周期任务的 Agent 之后才明白记忆至少包含三个层次每层的技术方案和坑各不相同。5.1 短期记忆、工作记忆与长期记忆的分工我自己给 Agent 做记忆系统时会分得比较细短期记忆当前对话窗口里的上下文。它对应模型的 context window是真正参与每步推理的内容。它的核心问题是物理长度有限且注意力会被长文本稀释。工作记忆当前任务执行过程中产生的中间结果比如工具返回的 Observation、中间推理步骤。这些通常在任务结束后就可以丢弃所以 LangChain 的AgentExecutor默认有return_intermediate_steps选项。长期记忆跨会话的知识沉淀比如用户偏好、过去的错误经验、领域知识摘要。它必须持久化可以放向量数据库但更需要结构化的更新逻辑。5.2 从缓存角度看记忆命中率比存储更重要记忆系统的关键指标不是存了多少而是该记的有没有在需要时被取出来。有一个类比非常贴切短期记忆像 CPU 缓存工作记忆像内存长期记忆像磁盘。CPU 缓存的命中率决定了程序性能Agent 的记忆系统也一样——如果系统每次取回的都是无关紧要的旧对话片段那么记忆不但没帮忙反而污染了当前推理。我在实践中常用的短期记忆优化方案是不仅存原始对话还同时存一份摘要记忆。摘要由 LLM 周期性生成比如每 10 轮对话后把关键结论压缩成一段话。这样长期运行的任务不至于把每个中间动作都压进上下文。实测这类方案对多轮工具调用的准确率提升非常明显。5.3 反思与经验沉淀让 Agent 从错误中学习这是长期记忆里最有价值、也最少人做的一块。Agent 每完成一个大任务后让它自己对执行轨迹做一次反思总结哪些步骤有效、哪些步骤踩了坑、下一次同类任务应该怎么调整。把这些反思结果结构化存下来下次遇到类似任务时作为系统提示词的一部分注入。这个机制有点类似人在团队里写复盘文档。对于重复性较强的 Agent 场景——比如定时抓数据、定时写报告——这套反思机制能让成功率逐步爬升因为模型会越来越熟悉这类任务的常见坑在哪里。5.4 关键禁忌长期记忆更新的版本控制问题这里想重点提醒一件事长期记忆的写入必须有版本控制或时间戳否则会出大问题。我经历过一个真实案例某个 Agent 的长期记忆里存了一条旧结论而这个结论后来被新任务推翻了但因为向量检索匹配到的是旧的一条它在新任务里被当成事实用了结果整个报告方向都跑偏。所以我的长期记忆表里一定会有几个字段内容摘要、写入时间、来源任务、置信度。当新信息与旧信息冲突时旧信息需要被标记为已过期或降权而不是两只都放进向量库里一起检索。这个细节看着不起眼实际上决定了一个长周期 Agent 是越用越聪明还是越用越乱。6. 工程实战中的五个真实教训跑通 demo 之后才面临的考验最后这部分不聊框架概念只说我跑真实 Agent 项目时踩过的坑和用下来的心得。每一个都是花了真金白银的 token 和时间换来的。6.1 教训一无限循环不是偶尔发生而是必然发生第一次跑一个需要调用外部 API 的 Agent 时我以为 max_iterations 够大就万事大吉。结果它在一个搜索→发现内容太旧→再次搜索→又发现内容太旧的循环里跑了 30 多步token 费烧了一笔不小的钱。后来我意识到单靠步数上限不够应该给 Agent 一个明确的任务完成判据并且在 prompt 里强调只要拿到足够信息就立即给 Final Answer不要追求尽善尽美。工程里我给 Agent 的默认配置是max_iterations8同时在请求里加一个额外指令如果第三步之后还没有新信息就基于已有信息回答。这个小改动把无效调用次数降了差不多 70%。6.2 教训二工具数量翻倍并不意味着能力翻倍我之前设计过一个全能型Agent一口气挂了 12 个工具从搜索、计算、翻译到网页截图审查什么都有。测试之后发现模型经常在相似工具之间犹豫不决或者调错其中一个。后来我把功能相近的小工具合并成大工具比如把搜索新闻和搜索网页合并为一个web_search把计算和转换单位合并成calc_and_convert等问题。工具数量降到 6 个之后成功率反而上去了。这验证了一句话给模型的每一个选择都是有成本的。工具描述不够清晰时工具越多决策准确率越差。6.3 教训三评测一个 Agent要看轨迹而不是只看最终结果有段时间我用自动化指标评估 Agent比如正确率、Rouge 分数发现数字都很好。但人工抽查时发现它的最终答案经常是在最后一步直接从搜索摘要里复制了一整段话而中间步骤里它根本没调用过指定的统计分析工具。因为最终文本和参考摘要高度相似自动评分给了高分。后来我把评判标准从只看最终输出改成中间步骤合理性检查工具调用合规性检查比如计算类任务是否真的使用过计算工具数据类任务是否保留了对源网页的引用。这个改动才是真正让 Agent 评测变得更真实的关键。6.4 教训四上下文膨胀比任何 bug 都隐蔽、都致命Agent 跑的时间越长上下文里堆积的旧观察和无效推理就越多。模型并不是真的忘记了前面的内容但它的注意力会被后面更多无关 token 稀释。我处理这个问题的方法就是前面提到的工具返回结果强制截断摘要、历史对话周期性压缩、关键数据提取成结构化字段。有一点很值得注意不要把所有中间结果都交给模型总结后放入记忆。应当让模型只保留和当前决策点强相关的内容。否则摘要里塞进去十个字段真正需要用到的三个反而被埋了。6.5 教训五防御性设计是 Agent 上生产环境的入场券最后一个建议是任何 Agent 要从 demo 走向生产一定要提前做这些防御性设计超时控制、步骤上限、审计日志、异常退出时向用户返回部分结果而不是空白。除此之外我还建议给 Agent 配置一个安全出口当模型连续两轮输出的 Action 完全相同可以判定为卡死自动终止。这类判断逻辑用不到多高深的算法但对线上稳定性的提升非常明显。另外生产环境里不要用temperature1我一般配 0.2 左右温度越高模型越容易在推理链条里灵机一动跑偏。以上这些坑几乎都是在我把 Agent 从 Jupyter Notebook 搬进正式业务流程之后才陆续浮出来的。所以如果你正在跑一个 Agent 项目别被 demo 阶段的效果迷惑尽早把记忆策略、工具数量和失败路径设计当成一等公民来对待。我给自己团队定的默认方案是ReAct 做基座Copilot-Agent 做固定流程记忆用短期窗口长期反思摘要的组合工具个数通常控制在 6 到 8 个评测一定看轨迹。这套配置未必是最前沿的但它是目前让我睡得最安稳的。