资讯详情

资讯详情

ReAct模式详解:让大模型智能体边想边做的核心实现

最近不少朋友都在问Agent的搭建尤其是想把大模型真正用起来做一个能“边想边做”的智能体。市面上各种Agent框架和Demo满天飞但你扒开底层会发现大多数都绕不开一个核心范式——ReAct模式。这个词这几年在AI圈热度一直没降过网上常有人把它跟前端框架React搞混也有不少人照着帖子的代码复现成功可真到了自己改工具、加逻辑的时候却一头雾水。今天我就把这套东西从头到尾捋一遍概念、原理、代码、排坑一次讲透。这篇内容适合谁如果你是刚接触大模型应用开发、打算自己搭Agent的新手或者已经在用LangChain/LangGraph但不太清楚背后运行逻辑的工程同学都可以放心往下看。我会尽量把每一步都拆成能直接抄作业的代码和配置也会把那些文档里不写的坑提前告诉你。1. ReAct模式到底是什么拆开Re和Act两层含义1.1 先把名字说清楚ReAct不是前端那套React在往下聊之前必须先把一个绕不开的混淆点说清楚。这里的ReAct是Reasoning Acting的缩写中文说得直白一点就是“推理与行动相结合”和前端圈子天天用的那个React组件库没有任何关系。只是拼写恰好一样走红的时间段也有重叠所以很多搜索词里会同时出现“react 生命周期函数”、“react 图表”这种东西——那些讲的是前端框架跟我们要聊的AI智能体完全是两个赛道。如果你在GitHub上搜ReAct大概率会看到一个比较有代表性的论文仓库那篇工作发表于2022年底、后来被ICLR 2023接收作者是Yao等人。它提出了一种让大模型交替执行“推理”和“调用外部工具”的策略。这个思路后来直接成了LangChain早期AgentExecutor的核心实现也是现在各种Agent框架里最常见的循环骨架。记住一点ReAct里的大写“R”和“A”分别对应Reasoning和Acting后面所有内容都围绕这两个动作展开。1.2 核心循环Thought、Action、Observation 三件套ReAct的核心其实特别简单就是让模型循环执行三个步骤第一步叫Thought相当于模型把脑子里的思路“说出来”。比如“我需要先弄清楚北京今天的天气再决定出门穿什么”。这一步不需要调用任何工具纯粹是模型基于上下文做出的推理。第二步叫Action模型根据刚才的想法决定调用哪把“工具”。工具的形态完全由你定义可能是搜索引擎、计算器、数据库查询接口也可能是某个业务API。模型只负责输出一个结构化指令真正执行的是你的代码。第三步叫Observation也就是工具返回的结果。这一步很关键它不靠模型编造而是实实在在的系统反馈。模型看到这个观测结果后会再次进入Thought阶段继续推理下一步行动信息够了就输出最终答案。整个过程形成一个循环Thought引导ActionAction产生ObservationObservation再驱动下一个Thought。这跟你写代码时“想一步、做一步、看结果、再想下一步”的思路非常像也是ReAct最本质的运作方式。1.3 一个最小的例子看一眼就懂我举个最简单的场景你就明白这个循环长什么样了。假设用户问“帮我查一下Q3的营收然后对比上季度增长了多少。”模型第一轮输出可能是这样Thought用户需要两个数据Q3营收和上季度营收。我先去知识库查Q3数字。Action{name: search, args: {query: 2024年Q3营收}}Observation系统返回“2024年Q3营收为12.6亿”。模型接着第二轮Thought拿到Q3数字了现在需要上季度的营收做对比。Action{name: search, args: {query: 2024年Q2营收}}Observation系统返回“2024年Q2营收为10.2亿”。第三轮模型手里已经有两个数字不需要再查工具了Thought两个数字都有了Q3是12.6亿Q2是10.2亿增速约23.5%可以组织答案。Action{name: finish, args: {answer: Q3营收12.6亿环比增长约23.5%}}看到没有这就是一个完整的ReAct闭环。模型每次只走一小步每一步都有真实工具结果兜底不靠记忆硬编。这就是它比“让模型一口气直接回答”更可靠的原因。2. 为什么ReAct能行它精准捅破了三个痛点2.1 纯推理的局限模型再强也会一本正经地胡说如果你只用Prompt让一个大模型直接回答复杂问题它能答对一部分但策略本质上是“猜”。模型是根据训练时见过的大量语料生成一个概率上最合理的答案序列它没有实时确认过这些信息的真假。你问它一个需要查询数据库、计算器或实时网页才能回答的问题它很容易一本正经地给你编一个数字而且语气非常自信这就是行业内常说的“幻觉”。很多人一开始不信邪觉得“只要Prompt写得够好模型就能想清楚”。但真实情况是CoTChain-of-Thought思维链确实能让模型把复杂问题拆解成中间步骤可拆出来的每一步依然是模型自己在脑内完成的推演没有任何外部验证。只要中间一步的“记忆”是错的后面所有推理都会跟着歪。ReAct的做法不同它把“内心独白”和“外部事实获取”强行分开。模型可以推理但一旦涉及知识性、实时性或计算性问题就必须调用工具拿真实结果然后把结果作为下一轮推理的输入。这样幻觉空间就被压缩到了模型真正擅长的逻辑组合部分而不是数据事实部分。2.2 纯行动的局限没有思考的智能体就是无头苍蝇再往另一个极端看。有些人觉得既然模型会编那干脆别让它想太多直接给它一堆工具让它一个个试就完了。这种做法叫Act-only风格比如早期的某些机器人流程自动化脚本或者一些粗糙的Agent Demo。你试过就会知道纯行动模式最大的问题是“没有规划导致的盲目试错”。模型不知道为什么要调用某个工具也不知道当前工具结果意味着什么只是机械地执行一连串动作经常在同一个失败路径上反复打转。比如查询了一个不存在的商品ID它不会说“这个ID可能是笔误我换一个关键词再试”而是继续用同一个ID测三次白白浪费时间和接口配额。ReAct把Reasoning放回去本质上就是给行动装了一个“方向盘”。每一次调用工具之前模型都要明确说出“我为什么这么做”而工具结果又会反过来修正模型的下一次判断。这种约束让整个行动过程变得可解释、可控制、可纠错工程上你也更容易定位问题出在哪一步。2.3 与CoT、Act-only、CoT-SC的横向对比看完两个极端再拉一张表对比主流方案你会更清楚ReAct的位置。方案模式有推理有外部信息获取典型问题直接Prompt否否错误率高、幻觉严重CoT思维链是否中间步骤无法验证CoT-SC自洽性采样是否采多轮投票成本高且仍无事实校验Act-only否是盲目尝试、缺少规划ReAct是是需要设计工具、观测和解析逻辑ReAct最核心的贡献不是发明了什么惊人算法而是把“推理”和“行动”两个能力单元组合成了一套工程上可落地的协作协议。它同时拥有了CoT的解释性优势和工具调用的真实数据优势代价则是你需要额外处理循环控制、输出解析和工具管理。3. 手把手实现搭建一个能思考与行动的ReAct智能体3.1 环境准备与工具定义先让智能体有手有脚理解了原理就该动手了。我以一个最常见的场景为例搭建一个能联网搜索、能算数、最后综合回答问题的问答Agent。代码以Python OpenAI接口为例但你完全可以把模型换成任何兼容OpenAI格式的服务。首先是环境准备安装必要的库pip install openai然后定义工具函数。ReAct本身不限定工具数量但每一步必须足够清晰。我这里规划三把工具def search_tool(query: str) - str: 模拟搜索接口实际项目中可替换为真实搜索API或知识库检索 return f关于“{query}”的搜索结果根据内部知识库相关内容为xxx。 def calculator_tool(expression: str) - str: 模拟计算器实际生产环境不要直接用eval try: return str(eval(expression)) except Exception as e: return f计算错误: {e}这里有个经验要提一下我示例里的eval只是为了演示方便生产环境绝对不能这么干它会带来代码注入风险。你可以用ast.literal_eval处理字面量表达式或者接入专门的表达式计算库也可以让模型调用一个远程计算服务。工具执行的边界越严格整个Agent就越不容易被恶意输入带偏。3.2 提示词模板把想法-行动-观察写进Prompt工具定义好了但模型怎么知道要按“Thought→Action→Observation”的格式走答案全在系统提示词里。ReAct对提示词格式的依赖非常大Prompt写得不清晰模型就会自由发挥。我建议一套经过多次调整的模板风格偏工程化用JSON表达Action方便后面代码解析SYSTEM_PROMPT 你是一个通过思考-行动-观察循环来解决问题的智能体。 每一步请严格输出 Thought: 你对当前问题的推理以及下一步计划。 Action: 一个JSON对象选择以下工具 {name: search, args: {query: 搜索关键词}} {name: calculator, args: {expression: 算术表达式}} {name: finish, args: {answer: 最终答案}} 规则 1. 不要伪造ObservationObservation由系统提供。 2. 信息不足时继续输出Thought和Action。 3. 信息充分时用Action: finish输出最终答案。 4. 每一步只能输出一个Thought和一个Action。 这里有一个容易被忽略的要点一定要在Prompt里写明“不要伪造Observation”。因为很多模型看到上面的历史对话中出现了“Observation: xxx”就会无意识地模仿这种格式自己去编造观测结果进而污染整个推理链。一旦出现这种情况你辛苦搭建的ReAct就退化成单纯靠模型编故事。最好的办法是在解析阶段也兜底——当你发现模型自己输出了Observation就把它截掉。3.3 Agent循环代码解析、执行、再喂回接下来写核心的Agent循环。算法逻辑是这样的把用户问题发给模型拿到回复后解析Action执行对应工具把工具结果作为Observation追加进对话记录然后再次请求模型直到模型输出finish或触发最大步数。import json import re from openai import OpenAI client OpenAI() def parse_action(reply: str): 从模型回复中提取Action JSON pattern re.compile( r\{name\s*:\s*[^]\s*,\s*args\s*:\s*\{[^{}]*\}\}, re.S ) matches pattern.findall(reply) if not matches: return None return json.loads(matches[-1]) def run_agent(question: str, max_steps: int 5): messages [ {role: system, content: SYSTEM_PROMPT}, {role: user, content: question}, ] for step in range(max_steps): response client.chat.completions.create( modelgpt-4o, messagesmessages, temperature0.2, max_tokens600, ) reply response.choices[0].message.content print(f--- Step {step 1} ---) print(reply) action parse_action(reply) if action is None: # 解析失败向模型反馈并要求重新输出 messages.append({role: assistant, content: reply}) messages.append({ role: user, content: 你没有输出合法的Action JSON请重新只输出Thought和Action。 }) continue if action[name] finish: return action[args].get(answer, ) tool_fn { search: search_tool, calculator: calculator_tool, }.get(action[name]) if tool_fn is None: observation f工具不存在: {action[name]} else: try: observation tool_fn(**action[args]) except Exception as e: observation f工具调用异常: {e} messages.append({role: assistant, content: reply}) messages.append({role: user, content: fObservation: {observation}}) return 达到最大步数未能完成。这段代码看起来不多但已经包含了ReAct的最小完整实现。关键在于它的消息结构模型的每次输出都作为assistant消息保留工具结果则作为user消息里的一段Observation回填。这样模型下一轮生成时能看到从问题到当前全部历史从而保持连贯性。3.4 关键参数设置与运行效果跑这个Agent之前有几个参数建议先调好。max_steps是最重要的安全阀。再聪明的Agent也偶尔会陷入钻牛角尖的状态如果不限制循环次数它可能一直调用工具不结束。按我的经验普通问答任务设5步够用需要复杂多跳检索的任务可以放到8到10步超过10步基本说明提示词或工具设计出了问题。temperature建议调到0.2甚至0。ReAct不是创意写作我们希望模型尽量稳定地遵循格式而不是每次生成五花八门的Thought。温度越高输出越飘Action格式越容易崩。真到了需要多样化探索的时候再调高比如让Agent生成多套候选方案时。max_tokens也不要给太小。如果模型输出到一半被截断很容易留下一个残缺的JSON让正则解析失败。600到800是一个比较宽容的数值既不会太慢也能容纳完整的Thought与Action。我实际跑过的效果是这样的在一个内部知识库问答Demo上直接问模型“A产品用户数和B产品用户数谁更多”模型第一轮会先去查A产品数据第二轮查B产品数据第三轮就能正确对比总结。整个过程清晰可见每一轮的Thought都像调试日志一样告诉你它为什么这么做。4. 常见问题与排查技巧实录4.1 输出格式不稳定怎么办我最早踩的坑就是模型时不时不按JSON格式输出。它可能输出一大段解释性文字然后在末尾带一个Action也可能把Action写成Action: Search[xxx]这种论文风格导致我的解析代码处理不了。应对思路有三个层次。第一Prompt里加few-shot示例给模型看一段完整的“Thought→Action→Observation→Thought→Action: finish”示例模仿效果比单纯描述格式好得多。第二解析时不要只找一个Action用正则找所有Action取最后一个因为模型可能先举了个例子再输出真正的Action。第三设计容错机制解析失败时把错误反馈给模型让它自己修正这在绝大多数情况下都能挽回。4.2 死循环与重复思考让智能体及时停下来死循环是ReAct最让人头疼的问题。模型可能在第4步已经拿到了所有信息但还在傻傻地调用搜索工具就是不输出finish。我见过最离谱的一次同一个search工具被调用了7次三次重复相同关键词。排查思路主要看三点。一是检查提示词里是否明确给出了“终止条件”如果没有模型不知道什么时候该停。二是看是否缺少工具结果的多样性如果连续几个Observation信息量很低模型就容易原地打转。三是策略层面的兜底在循环里加一个“重复动作检测”如果发现连续两轮的Action完全一致就直接中止本次调用把已获得的信息交给模型做最终整理。4.3 工具返回内容过长截断与摘要工具结果不一定都简短。真实场景里搜索API可能返回几千字的网页正文数据库接口可能丢过来一大坨字段。如果把这些原样塞进对话历史很快就把上下文窗口撑爆而且模型注意力会被无关信息淹没。我的习惯是给每个工具输出加一层后处理超过一定长度的内容先截断或者做摘要。比如搜索工具只保留标题、摘要和关键段落数据库工具只保留字段名和数值。还可以把Observation限制在500个字符以内让模型每次只聚焦最精炼的信息。这样既节省Token也明显提升循环稳定性。4.4 小模型跑ReAct的典型翻车点不是所有模型都适合直接跑ReAct。我在实际测试中发现参数量较小的模型对“JSON Action”这种强格式约束的理解能力有限容易把Action写成半结构化文本甚至直接输出中文描述。如果你只能使用小模型建议把Action格式改成更简单的文本协议比如Action: search[关键词]减少模型生成JSON的负担。还要注意小模型的推理链长度有限给它们5步以上的循环往往会出现前后矛盾。这时候不要把任务拆得太碎尽量让每一步工具调用解决一个较完整的信息获取需求减少循环次数。5. ReAct模式的进阶工程化建议5.1 多工具场景从一个搜索到一个工具箱当工具数量从1个变成10个问题就从“怎么调用工具”变成“怎么让模型选对工具”。最简单的做法是把每个工具的名称、描述、参数说明都写进系统提示词让模型根据任务自行选择。但如果工具太多、Prompt太长模型反而容易迷失。我建议设计一个“工具注册表”给每个工具打上标签和能力描述并在每轮循环开始前只把相关的几个工具注入到上下文里。这就是一种朴素但有效的工具路由。更进一步你可以让Agent先用一个轻量模型做意图识别缩小工具候选范围再让主模型进入ReAct循环这样既快又稳。5.2 引入长期记忆与状态管理标准的ReAct循环不携带长期记忆每一轮能看到的只是对话窗口里的历史。如果任务跨越多次会话或者需要记住用户之前的偏好就得额外引入记忆层。常见做法是把关键信息抽出来存入向量数据库下一轮开始时做相似度检索把相关记忆作为System Prompt的一部分塞回去。这能把ReAct从“单次任务执行器”升级成“有记忆的持续工作者”。另一种做法是给Agent维护一个结构化的状态JSON每一步工具执行结果都同步更新到这个状态里使长期任务可追溯、可中断、可恢复。5.3 在智能体框架中的位置ReAct是骨架而不是全部如果你看过LangChain、LangGraph这类框架的Agent框架图会发现ReAct通常只是里面一个带循环的节点而不是整个Agent的全部。框架图里一般会有几个输入输出端口接收用户问题、调用大模型、执行工具、返回观测结果。这个ReAct节点负责“思考—行动—观察”的微循环而框架本身还负责管理多节点编排、条件路由、人机交互、错误恢复等外层逻辑。工程上需要分清主次。ReAct解决的是“单轮内如何决策与行动”而一个完整的Agent产品还需要规划、记忆、多智能体协作、权限控制等多层能力。你可以把ReAct当作一块稳定的内核其他功能都围绕它做扩展但不要指望光靠一个ReAct循环就能支撑起复杂的生产系统。5.4 我的几条实战建议做Agent工程这么多年我总结了几条非常朴素的建议。第一日志一定要全。ReAct循环每一步的Thought、Action、Observation、Token消耗都要留痕。出问题时这些日志就是你排查的救命稻草。第二工具结果要做缓存。同样的搜索词、同样的查询参数短时间内很可能被模型反复调用加一层缓存能省不少费用和延迟。第三上下文窗口要留冗余。ReAct每一轮都会把历史追加进去看起来步数不多但Token消耗呈线性甚至超线性增长最好在循环中做消息压缩或历史裁剪。第四对finish条件做严格校验。模型说它完成了不代表答案一定正确可以让一个独立的评审模型对最终答案做二次校验能明显提高输出质量。我个人的体会是ReAct之所以能成为当前Agent开发的主流范式不是因为它的思路有多惊艳而是因为它足够简单、足够可控。你不需要理解复杂的强化学习或规划算法只要把握好Thought、Action、Observation这个三角循环配上几个好用的工具就能搭出一个真正能“边想边做”的智能体。每次看到模型在循环中自主修正方向都觉得这套协议比单纯堆参数更接近“智能”的本质。如果你正在做Agent不妨从今天这段最小实现开始亲手跑通一遍再一步步往里加工具、加记忆、加校验你会比只看框架文档理解得深刻得多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →