尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

ReAct模式深度解析:从工具调用到真正的智能体设计

发布时间:2026/9/16 13:24:52

资讯中心
01
ARTICLE

ReAct模式深度解析:从工具调用到真正的智能体设计

ReAct模式深度解析:从工具调用到真正的智能体设计
做了几年 Agent 开发我见过太多把会调用工具当成智能体的项目。demo 演示的时候模型准确调起了天气 API、计算器、数据库查询观众席一片惊叹但代码走查完我就知道这只是个套了层自然语言外壳的函数路由脚本。真正让我改变看法的是那篇 ReAct 论文让模型在每一步行动之前先推理再把推理结果和行动结果一起喂回模型循环往复直到任务收敛。这套思路看起来很简单但它才是智能体和自动化脚本之间的真正分水岭。这篇文章我想把 ReAct 的原理、实现方式和踩坑经验完整拆开讲清楚适合正在做 Agent 开发、或者准备从工具调用转向真正智能体设计的朋友参考。1. 先想明白一个问题工具调用和智能体差在哪1.1 工具调用本质上是路由不是思考先把结论放在前面会调用工具只是智能体的必要条件不是充分条件。Function Calling 这个能力刚出来的时候业内确实兴奋了一阵但冷静下来看它做的事情就是把用户的自然语言输入映射到一个函数签名上。模型的任务是解析意图、抽取参数、选择候选函数本质上跟你写一个if (input.includes(天气)) { getWeather(input.city); }没有本质区别只是把规则引擎换成了概率模型。我用一个实际的例子说明。假设用户说帮我查一下北京明天的气温工具调用做的事情是模型输出getWeather(city北京, date明天)系统执行函数把结果返回给模型模型组织语言告诉用户北京明天 25 度。这个流程快、准、稳定但它有一个致命缺陷它不会根据执行结果调整下一步动作。打个比方。如果你让一个实习生去查资料你告诉他去档案室找 A 文件他找到了就回来找不到就告诉你没有。这是工具调用。但你如果告诉他我需要 A 文件里的数据来写一份报告如果没有 A 文件你看看 B 文件有没有相关数据再不行就问一下档案室的人他会在执行过程中不断调整策略这是 Agent。后者的核心不是调用工具而是根据观察结果重新决策。1.2 为什么能调工具会被误认为很智能这个现象我在很多项目里都见过。一个 Agent demo 看起来很强能查天气、能算数学题、能查数据库、能编排任务一问一答非常流畅。但把系统拆开看所有工具调用路径都是预设好的模型只是在多个 API 之间做路由。这就像电话自动应答系统菜单层级再多也只是个交互式脚本。真正露馅的场景往往是这样工具返回的结果出了问题或者需要根据中间结果做二次决策。比如用户要统计上季度各区域的销售数据找出下滑最严重的区域并且猜测可能的原因。工具调用模式会规规矩矩地把 SQL 跑完把数据列出来但如果 SQL 查出来某个区域数据为空脚本就不知道怎么处理了。ReAct 模式会怎么做模型看到某个区域没有数据会想数据为空可能是表结构里区域名称不同换个名称查一下或者用同期的其他维度数据做替代分析然后把新的行动反馈到循环里。换句话说工具调用负责把活干完ReAct 负责知道下一步该干什么。前者是执行力后者才是决策力。这个区别就是标题里说的自动化脚本和真正的智能体之间的那条线。我并不否认工具调用的价值它仍然是 Agent 感知和操作世界的手和脚但如果没有 ReAct 这类推理机制做大脑手脚再灵活也只是个提线木偶。2. ReAct 的核心机制推理与行动的交替循环2.1 论文里的核心思想一句话可以讲清楚ReAct 的论文标题是Reasoning and Acting in Language Models注意这里的 ReAct 是 Reason Act不是前端框架 React这俩名字经常被搞混网上搜React 面试题出来的全是前端内容如果你找的是 Agent 方向的东西记得搜 ReAct 全称。论文的核心贡献是把两条本来分开的技术路线合到了一起。一条是 Chain-of-Thought (CoT) 提示词让模型在回答前先输出思考过程但 CoT 不跟外部世界交互纯靠模型内部知识推演知识截止之后的事情它一概不知另一条是行动路线让模型调用外部工具获取信息但早期这种模式只做单轮 tool call缺少推理链条模型不知道为什么调这个工具、调完结果怎么用。ReAct 的思路特别朴素让模型在一个循环里交替输出三种内容——Thought当前这一步的思考、Action决定调用哪个工具、传入什么参数、Observation工具返回的结果。然后把 Observation 拼到上下文里让模型继续输出下一步的 Thought 和 Action直到模型认为问题已经解决输出 Final Answer。循环的每一步都是基于前一步的真实结果而不是模型自己脑补出来的假设。2.2 一个完整的 ReAct 循环长什么样我这里用一个实际跑过的场景来拆解。假设任务是对比一下 Python 和 Go 在并发处理上的社区热度并给出我的学习建议。一个典型 ReAct 循环的对话轨迹大致是Thought 1: 我需要先获取两个语言的社区活跃度数据。GitHub 的 star 数和 Stack Overflow 的讨论量是比较直观的指标。 Action 1: search_web(queryPython vs Go GitHub stars 2025) Observation 1: 返回一组 GitHub 仓库 star 数据Python 相关项目平均 30kGo 相关项目平均 18k…… Thought 2: star 数只代表项目热度我需要看语言本身的讨论趋势。再搜索一下 Stack Overflow 的年度开发者调查数据。 Action 2: search_web(queryStack Overflow developer survey language popularity 2025) Observation 2: 返回调查中 Python 和 Go 在最常用语言和最想学习的语言两项中的排名数据。 Thought 3: 现在数据足够回答了。Python 在社区规模和生态资源上领先Go 在云原生和并发场景的热度增长更快。基于这些信息我的建议是…… Final Answer: ……注意这个流程里的关键点Thought 2 的产生不是因为代码里写死了查完 A 再查 B而是模型看到了 Observation 1 的数据之后判断出这些数据不足以回答问题还需要补充另一个维度的信息。这就是 ReAct 和脚本的分水岭——决策是动态的由上下文里的真实信息驱动。我在实际项目里做 Agent 效果评估时有一个简单的判断标准如果工具返回的内容发生改变后续的行动序列会不会跟着变。如果不会变说明你的Agent只是按预设剧本走的脚本如果会变说明推理链路真正参与到了决策里。3. 从 Function Calling 到 ReAct动手落地一个真实 Agent3.1 方案一自己写一个最简 ReAct 循环理论讲完了直接上实操。很多人觉得 ReAct 很玄乎其实核心代码可以很短几百行以内就能跑通。我下面用一个最小实现来演示没有用 LangChain 这类框架是为了让每一步都看得清清楚楚。先看完整代码然后逐段解释import json import re from openai import OpenAI client OpenAI(base_urlYOUR_API_BASE, api_keyYOUR_API_KEY) # 定义两个模拟工具 def get_weather(city: str) - str: 模拟天气查询实际可接天气API weather_data { 北京: 晴25°C湿度40%, 上海: 多云27°C湿度65%, 广州: 小雨29°C湿度80%, } return weather_data.get(city, f暂无{city}的天气数据) def calculate(expression: str) - str: 安全计算器只允许数字和四则运算 if not re.fullmatch(r[0-9\-*/().\s], expression): return 错误表达式包含非法字符 try: return str(eval(expression)) except Exception as e: return f计算错误{str(e)} TOOLS { get_weather: get_weather, calculate: calculate, } # ReAct 提示词模板 SYSTEM_PROMPT 你是一个能推理并调用工具完成任务的智能体。 请严格按以下格式输出 Thought: 你对当前情况的思考和下一步计划 Action: 工具名称必须从 [get_weather, calculate] 中选择 Action Input: 传给工具的 JSON 格式参数 Observation: 工具返回的结果系统提供不需要你生成 你需要交替输出 Thought 和 Action直到收集足够信息后用以下格式结束 Thought: 我已经获得足够信息 Final Answer: 对用户的最终回答 注意Observation 由系统返回你不能自己编造。一次只输出一个 Action。 def run_react(user_query: str, max_steps: int 8): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_query}, ] for step in range(max_steps): print(f\n Step {step 1} ) resp client.chat.completions.create( modelYOUR_MODEL_NAME, messagesmessages, temperature0.0, ) ai_text resp.choices[0].message.content print(f[模型输出]\n{ai_text}) # 检测是否结束 if Final Answer: in ai_text: print(\n ai_text.split(Final Answer:)[-1].strip()) return # 解析 Action 和 Action Input action_match re.search(rAction:\s*(\w), ai_text) input_match re.search(rAction Input:\s*(\{.*?\}), ai_text, re.DOTALL) if not action_match or not input_match: # 模型没按格式输出给它一个纠错提示 messages.append({role: assistant, content: ai_text}) messages.append({ role: user, content: 你的输出格式不正确必须包含 Action 和 Action Input。请重新输出。, }) continue action_name action_match.group(1) if action_name not in TOOLS: messages.append({role: assistant, content: ai_text}) messages.append({ role: user, content: f工具 {action_name} 不存在可用工具[get_weather, calculate], }) continue try: action_args json.loads(input_match.group(1)) except json.JSONDecodeError: messages.append({role: assistant, content: ai_text}) messages.append({ role: user, content: Action Input 必须是合法的 JSON 格式请重新输出。, }) continue # 执行工具 tool_result TOOLS[action_name](**action_args) print(f[工具执行] {action_name}({action_args}) {tool_result}) # 把 Observation 拼回上下文 messages.append({role: assistant, content: ai_text}) messages.append({ role: user, content: fObservation: {tool_result}, }) print(\n[达到最大步数强制结束])这段代码的关键点有几个。第一循环次数必须设上限我在生产环境里一般设 8 到 15 步防止模型陷入死循环烧 token。第二模型输出必须保留在 messages 里不然上下文链条就断了模型看不到自己之前说过什么。第三工具结果以Observation的形式作为 user 消息拼回去模拟论文里执行工具 - 观察结果 - 继续推理的循环。3.2 几个必须注意的坑输出格式、上下文管理和停止条件上面这段代码能在大多数支持 OpenAI 接口的模型上跑通但实际项目里光这样还不够。我逐个说坑。第一个坑是模型不按格式输出。这个概率比你想象的高尤其是用小参数模型的时候。它可能会直接输出一句话回答而不走工具或者把 Action 和 Action Input 放在同一行。解决方案有两个方向一是像上面代码里那样做格式校验和纠错重试二是在 system prompt 里给一个 few-shot 示例。我自己试过给一个示例比不加示例的错误率能降低一半以上。第二个坑是最容易被新手忽略的——模型生成的Observation有时会被模型自己脑补。注意代码里 tool_result 是真实执行的返回值而不是模型生成的文本。在真实项目里这也意味着你绝对不能把模型的输出直接当工具结果回填一定得在代码层做边界隔离。你可以把观察结果当成环境反馈只能来自真实的工具执行或外部 API 返回。模型一旦在 Observation 的位置开始编造结果这个 Agent 就彻底失控了。我见过不止一次线上事故原因就是有人图省事把工具结果拼接成了模型续写文本。第三个坑是停止条件。上面的代码只在文本里检测Final Answer:四个字但实际模型经常变体输出比如Final Answer中文:或者最终答案:。更稳的做法是设置两个条件命中任何一个就终止步数达到上限或者模型输出的文本里包含明确的结束标记。我个人的经验是在最终答案之前强迫模型先输出一个类似Thought: 已经可以回答用户的问题的思考句然后才输出 Final Answer这种两步结束法可以显著降低提前结束的概率。3.3 方案二用现成的 Agent 框架快速搭建如果你不想从头写循环LangChain、Dify、Coze 这些框架和平台都已经内置了 ReAct 策略。拿 Dify 举例你在编排应用时把Agent 策略选成 Function Calling 或 ReAct然后挂上各种工具平台会自动帮你完成循环调用模型 - 解析 Action - 执行工具 - 回填 Observation这一步。用平台的好处除了省事还有一个非常实在的点它们会把上下文管理、token 截断、并发控制这些工程问题提前处理好。自研 ReAct 循环时遇到长任务很容易把上下文撑爆平台一般会做历史消息的压缩或截断这在你自己的代码里是要从头实现的。但平台也有平台的问题。最大的问题是灵活度受限比如你希望工具执行时注入自定义逻辑或者希望在每一步推理里插入一个人工审批节点平台不一定支持。我的建议是验证想法、做 demo、快速迭代用平台上生产、做复杂的定制化 Agent还是得自己掌握底层循环逻辑技术团队至少要有完全自己实现一遍 ReAct 的能力。4. ReAct 实战中反复踩到的坑和排查实录4.1 Agent 陷入死循环token 成本暴涨这是我第一次接智能体项目时遇到的最头疼的问题。模型在某个问题上反复调用同一个工具每次输入参数都差不多结果也差不多但循环就是停不下来。核心原因往往是模型认为我需要更多信息才能回答但工具返回的信息已经足够模型却看不懂。排查路径有两步。第一步看日志里模型的 Thought 内容判断它是信息不足需要继续查还是不会总结。前者可能是工具返回结果格式太乱、信息量低模型读不出关键内容后者则需要换更强的模型或者把工具返回的结果先做一层格式化预处理。第二步是硬性手段——最大步数限制和预算控制。我线上线下所有的 ReAct 实现里都会加这两个东西最大步数比如 10 步和最大 token 消耗比如 2 万 token任一个达到上限就直接终止并输出当前结果。代码里的max_steps8就是干这个的。那些花大几百块跑一个任务的惨案基本都是少了这两个保险丝。4.2 工具返回的 JSON 太大把上下文塞爆了有一次我给 Agent 接了数据库查询工具一张表有几十个字段查询结果一次性全部返回几千行数据直接塞进上下文。后果是模型响应变慢、输出质量下降甚至开始胡言乱语。解决方案是工具结果手术。在把工具结果回填到 Observation 之前先做三件事字段过滤只保留回答当前问题需要的列行数截断只保留前 20 条记录如果数据量仍然大用一个小模型先把数据做摘要把摘要结果作为 Observation 回填。这个工具结果摘要层是生产级 Agent 的标配但很多教程不会提到。4.3 模型开始脑补Observation撒谎比说实话多之前说过 Observation 必须来自真实工具返回但在某些情况下模型还是会编造。比较危险的一种情况是模型作为 ReAct 循环的执行者它输出的 Action 是一个不存在的工具名代码里TOOLS字典查不到报错后如果直接把报错信息丢给模型让它重试有时候模型会开始假装自己调用成功了、编造一个结果出来。我现在的做法是一旦工具调用失败或者工具不存在返回一个标准化的错误 Observation比如Error: tool not found. Available tools: [...]同时要求模型必须看到Error开头的 Observation 时重新规划 Action不能直接给出最终答案。这个策略在多个模型上都验证过能有效减少幻觉式的工具结果。4.4 工具调用嵌套的参数格式问题这个坑很隐蔽多见于一个工具的输出作为另一个工具输入的时候。比如第一个工具返回的结果是一个嵌套 JSON模型要把{data: {list: [{name: 北京, value: 25}]}}里的列表项取出来作为第二个工具的输入这时模型在生成Action Input的时候很容易产生转义错误、字段名猜测错误。我在生产环境里遇到这类问题处理思路是尽量在代码层做转换不要依赖模型自己理解结构——比如用jsonpath先把嵌套结构拍平再传给模型让它选择。工具与工具之间的数据流转能用代码处理的就不要让模型翻译。4.5 ReAct 的安全边界不要把所有工具都交给模型ReAct 给了模型越来越大的自主权意味着风险边界也在扩大。我吃过的一个大亏是模型在连续推理过程中自动调用了一个带删除操作的数据库工具差点造成数据丢失。事后总结工具注册表要做权限分级只读型工具模型可以自由调用任意参数型工具比如执行 SQL、发送邮件、删除文件必须在调用前加人工审批节点。另外工具描述里也要写明副作用比如删除之后不可恢复仅在用户明确确认后执行模型对这种提示的遵循程度比想象中好。5. 完成了 ReAct 之后还有个更进阶的问题等着你5.1 ReAct 的短板高延迟、高 token 消耗、单线程决策ReAct 不是银弹它有几个非常明显的短板。第一是延迟高因为循环里每一步都要做一次完整的大模型推理一个复杂任务跑十几步 Loop响应时间可能从秒级进到分钟级。第二是 token 消耗高每一步都要把所有历史 Observation 重新送入模型随着循环加深成本是二次方增长的——这是 ReAct 被诟病最多的工程问题。第三是单线程决策模型每一步只能做一个动作但对于需要并行并发调用的场景比如同时对比三个方案的成本、风险和收益ReAct 会非常笨拙。5.2 从 ReAct 到 Plan-and-Execute让 Agent 先规划再行动针对 ReAct 单步推断的低效问题业界主流的进化方向是 Plan-and-Execute 模式思路是把规划和执行拆成两个模型层次。先用一个高层次的 Planner 模型把复杂任务拆解成步骤清单再用 Executor 模型逐条执行这些步骤。它跟 ReAct 最大的区别是ReAct 走一步看一步Plan-and-Execute 先画好地图再走路。如果任务中途发现语境变化可以触发 re-plan 重新生成步骤清单。我在任务结构清晰、步骤可预测的场景下会更倾向 Plan-and-Execute比如周报生成、多步骤数据处理、批处理任务。而 ReAct 更适合探索式的、需要根据实时反馈动态调整的任务比如研究分析、排障诊断、信息收集。这两个模式不是互斥的很多生产级 Agent 是混合使用的先用 Plan-and-Execute 拆出大的步骤每个步骤内部再用 ReAct 做细粒度的工具调用。5.3 再往下反思机制和多智能体协作ReAct 之后还有两条值得关注的路。一条是 Reflexion 这类带反思机制的方案模型在完成任务后会对自己的执行过程做复盘总结哪里做得好、哪里做得差把复盘结论存下来影响后续轮次本质上给 Agent 加了自我纠错的能力。另一条是多智能体系统多个 Agent 各司其职、分角色协作有些负责推理、有些负责执行、有些负责审查安排它们互相辩驳可以有效降低单 Agent 的幻觉率。但这些进阶方案复杂度提升了一个数量级不是所有项目都需要。我给团队的选型建议很简单你的任务需要多步决策和动态调整就上 ReAct步骤固定、执行明确的用普通工具调用或者 Plan-and-Execute 反而更高效不要为了用新技术而用。最优架构永远是够用就好。最后分享一个个人心得。我判断一个 Agent 项目是不是挂羊头卖狗肉就会去看它有没有推理-行动-观察这个闭环。如果没有不管它调了多少个工具、接了多少个 API本质上还是一个自动化脚本只是披了个智能的外壳。真正从脚本走向 Agent看起来是加了一个循环实际上是把决策权从代码移交给了模型——这是一个整个团队都要重新适应的思维转变。如果你的项目也卡在会调工具但显得很笨的阶段不妨拿 ReAct 的思路重写一遍主流程我猜你会有完全不一样的体验。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。