大模型本身是“回答问题”的高手,但在“完成复杂任务”这件事上却经常捉襟见肘。你问它“北京今天适合带伞吗”,它能给出一个听起来合理但可能是编造的回答;你让它帮你对比三份方案并给出结论,它往往只在文字层面打转,既不会主动去查最新数据,也不会发现自己的推理已经偏离事实。真实世界里的任务,哪是只靠一次问答就能完成的?
这正是 ReAct 出现的契机。ReAct 不是某个前端框架,而是一套让大模型像人一样“先思考、再行动、看结果、再思考”的工作模式。它的名字来自 Reasoning + Acting,直译过来就是“推理与行动协同”。自 2022 年底论文《ReAct: Synergizing Reasoning and Acting in Language Models》发表以来,这个概念已经成了 AI Agent 领域最基础、也最常被引用的控制流范式之一——今天绝大多数 Agent 应用,底层都能看到 ReAct 的影子。
这篇文章的目标很简单:用最短时间帮你建立对 ReAct 的正确认知,然后给出一份可以直接复制运行的最小代码。读完你会明白三件事:ReAct 和 React 框架到底有什么关系;它和 CoT、RAG、Plan-and-Execute 这些概念的区别在哪里;以及不依赖重量级框架,怎么用朴素代码手写一个最小可用的 ReAct Agent。如果你想快速判断这个模式适不适合自己的项目,读第 1 到第 4 节就够了;想动手实践,直接跳到第 5、6 节。
1. 先澄清:ReAct 不是 React 框架
很多第一次接触 ReAct 的开发者,都会把它和前端框架 React 搞混。这不能怪大家,因为从拼写上看,ReAct 和 React 只差了一个字母的大小写。你去搜索引擎里搜“ReAct 入门”,前面几页大概率是 JSX、组件生命周期、虚拟 DOM 这类前端教程。中文语境下,这两者的名字几乎无法区分,给学习 AI Agent 的人造成了不小的困扰。
从词源上就能看出本质区别。React 是 Facebook 开发的 UI 库,因为“构建响应式界面”而得名;ReAct 则来自学术论文,它的初衷是让语言模型在推理(Reasoning)和行动(Acting)之间来回切换,从而解决单纯问答回答不了的问题。一个是前端渲染方案,一个是 Agent 控制流模式,根本不在同一个技术层次上。
如果你在面试中被问到 Agent 相关内容,提到的 ReAct 通常都是论文里的这个模式,而不是前端框架。建议无论如何都先确认对方指的是哪一个,避免鸡同鸭讲。React 前端框架解决的是“界面怎么渲染”,ReAct 解决的是“智能体要怎么一步步完成任务”,两者唯一的共同点,就是名字看起来很像。
2. 为什么需要 ReAct:传统提示词方法的三个瓶颈
在 ReAct 提出之前,用大模型做任务有三种常见思路,但它们各自都有明显短板。
第一种是普通的少样本提示(Few-shot Prompting)。给模型几个示例,让它照着格式回答。这种方式适合单轮问答,但模型一旦遇到没见过的问题,很容易一本正经地给出错误答案。第二种是思维链(Chain-of-Thought,CoT),让模型先把推理过程写出来,再给出最终结论。这一步能提升数学题、逻辑题的正确率,但本质上仍然是“闭卷答题”——模型从头到尾都在自己脑补,没有外部信息进来,也没有纠错机制。第三种是先写一段计划,再让模型按计划执行,看起来更接近真实工作,但计划一旦出错,模型往往没有能力在执行中途修正。
这三条路线共同的问题是:大模型只有“思考”这一根支柱,没有“行动”。它不会主动查资料,不会调用工具,也不会因为中途发现信息错误而停下来调整。而 ReAct 的突破点恰恰在这里:它把“思考”和“行动”放到同一个循环里,模型可以一边推理,一边选择调用外部工具,拿到工具返回的真实结果后再继续推理。
打个比方,CoT 是闭卷考试时在草稿纸上打草稿,ReAct 是允许你开卷考试,并且可以随时翻书、查资料、做完一步再回头改草稿。后者显然更接近人类解决复杂问题的过程,也正是真实业务对 Agent 的期待。
3. ReAct 核心机制:Thought / Action / Observation 循环
ReAct 的整个循环,可以浓缩成一句话:遇到任务时,按照“思考 → 行动 → 观察 → 再思考”的顺序推进,直到得到最终答案。
这个流程可以表示为:
Thought → Action → Observation → Thought → Action → Observation → …… → Final Answer其中三个关键组件分别承担不同职责。
Thought(思考)是模型对当前状态的分析,作用是决定下一步应该做什么。这一步不需要特别复杂,但必须让后续动作有依据。Action(行动)是模型选择要调用的工具,比如“搜索天气”“查询数据库”“执行某个内部 API”。模型输出的 Action 通常需要附上一个 Action Input,也就是给工具的输入参数。Observation(观察)是工具执行后返回的结果,模型拿到这个外部反馈之后,再进入下一轮 Thought。如此反复,直到模型判断信息已经足够,才输出 Final Answer 结束循环。
拿最开始的问题举例。用户问“北京今天适合带伞吗”,一个 ReAct Agent 的完整过程可能是这样:
第一轮,模型先推理:用户想知道北京天气情况,我还没有数据,所以先查天气。于是输出:
Thought: 我需要先查询北京的天气信息,再判断是否需要带伞。 Action: search Action Input: 北京天气工具返回:
Observation: 北京今天晴,最高气温25摄氏度,最低气温12摄氏度。模型看到这个结果,再进入第二轮:今天是晴天,没有降雨,所以不需要带伞;如果白天在户外时间长,可以提醒注意防晒。于是输出:
Thought: 查询结果显示北京今天是晴天,没有降雨迹象。 Final Answer: 北京今天晴,不需要带伞;户外时间长的话,建议注意防晒。整个过程中,模型每轮只推进一小步,而且每一步的推理都留下了文字轨迹。这就是 ReAct 的重要价值:它不止让智能体“会做事”,还让做事过程“可解释、可审计、可排查”。一旦最终结果出错,你能顺着推理轨迹找到是哪一步决策失误,这是黑盒式单轮问答做不到的。
还有一个容易被忽略的优势:因为行动能带来外部事实,模型可以在不确定时主动选择“检索”而不是“编造”,这在降低幻觉上会比纯粹脑补稳定不少。当然,它不能根治幻觉——如果工具本身返回了错误信息,模型依然可能基于错误继续推理,这一点后面在工程建议里会再提到。
4. 与主流方案对比:CoT、RAG、Plan-and-Execute 到底什么关系
很多初学者会把 ReAct 和另外几个高频概念搞混,这里用一张表直接对比。
| 方法 | 核心动作 | 是否可以调用工具 | 是否在执行中调整 | 典型场景 |
|---|---|---|---|---|
| CoT | 仅推理 | 否 | 否 | 数学题、逻辑推理题 |
| RAG | 先检索再生成 | 仅一次检索 | 否 | 知识库问答、文档问答 |
| ReAct | 推理 + 行动循环 | 可以,多轮调用 | 是 | 多步任务、实时信息查询、工具编排 |
| Plan-and-Execute | 先规划再执行 | 可以是 | 通常否 | 步骤明确、可预先拆解的任务 |
CoT 最接近“想一想再回答”,适合不需要外部信息的纯推理任务。RAG 则多了一个“查资料”的动作,但通常只查一次,查到结果后直接生成答案,不会因为答案不完整而再次检索。ReAct 更像是 RAG 的加强版——它把“检索”变成一个可复用的行动,并且允许模型在一轮检索之后发现问题,再决定是否进行第二轮。
Plan-and-Execute 和 ReAct 的差别更微妙。前者先让模型生成一个完整计划,再按顺序执行,执行过程中一般不重新规划;ReAct 则是边执行边看结果,每走一步都重新评估下一步。打个比方,Plan-and-Execute 是出发前规划好所有路线的自驾游,ReAct 是边走边查地图、遇到封路随时掉头的自由行。对天气变化、信息不确定的场景,ReAct 的容错能力明显更强;但对步骤非常明确、几乎没有意外的任务,Plan-and-Execute 的成本通常更低。
现实项目中,ReAct 和 RAG 不是只能二选一。一个常见的组合是把“检索知识库”包装成 ReAct 的一个工具,这样模型可以根据用户问题的复杂度,决定要不要检索、检索几次、还需要检索什么。你可以把它理解成 RAG 是插在 Agent 手里的一个工具包,而 ReAct 是使用工具包的决策流程。
5. 环境准备:你需要哪些依赖和 API Key
这一节我们准备写代码。为了让示例足够“朴素”,不依赖 LangChain 或 LlamaIndex 这类重量级 Agent 框架,直接用 OpenAI 兼容接口手写循环。这样做的好处是,你能看到 ReAct 的全部骨骼,而不是被框架封装的黑盒。
环境准备清单如下:
- Python 3.9 或更高版本。
- 安装 OpenAI Python SDK。终端执行:
pip install openai- 一个支持 OpenAI 兼容接口的大模型 API Key,并配置环境变量。
export OPENAI_API_KEY="你的APIKey"如果你的模型服务商提供了自定义接口地址,也可以额外配置 base_url。比如某些国内云厂商提供 OpenAI 兼容接口,可以把服务地址设置成对应的 base_url。没有 OpenAI 官方账号完全不影响学习,关键是这个 API 必须支持 Chat Completions 格式。
需要提醒的是,如果你手头暂时没有可用的 API Key,依然可以先把代码写好,用一个假的 Key 跑一遍,观察模型输出位置的报错流程。真正的 ReAct 循环逻辑与具体 Key 无关,后续换成可用 Key 即可。另外,由于 ReAct 每次循环都要调用一次大模型接口,务必注意 API 服务的计费规则,建议先在测试环境用最小模型跑通。
6. ReAct 最小实现:用 OpenAI 兼容 API 手写 Agent 循环
下面这份代码是本文的核心示例。它没有使用任何 Agent 框架,而是自己维护一段对话消息列表,按照 ReAct 协议循环调用大模型。
# 文件路径:react_demo.py import os import re from openai import OpenAI client = OpenAI( api_key=os.environ.get("OPENAI_API_KEY", "sk-你的APIKey"), base_url=os.environ.get("OPENAI_BASE_URL", "https://api.openai.com/v1"), ) SYSTEM_PROMPT = """ 你是一个通过“思考-行动-观察”循环来完成任务的 AI 助手。 你必须严格遵守以下协议: 1. 每一轮先输出 Thought: 用一句话描述你当前的推理。 2. 如果你需要调用工具,继续输出 Action 和 Action Input。 3. 拿到工具返回的 Observation 结果后,继续进入下一轮推理。 4. 当你确信已经得到最终答案,直接输出 Final Answer: 最终答案。 你可以使用的工具如下: - search(query): 返回一段与 query 相关的本地信息。 - calculator(expression): 计算一个数学表达式并返回结果。 输出示例: Thought: 我需要先查出目标城市的天气数据。 Action: search Action Input: 北京天气 Observation: 北京今天晴,25摄氏度。 Thought: 我现在得到了足够信息,可以给出答案。 Final Answer: 北京今天晴,25摄氏度。 """ TOOL_TABLE = { "北京天气": "北京今天晴,最高气温25摄氏度,最低气温12摄氏度,无降雨。", "上海天气": "上海今天多云,最高气温23摄氏度,最低气温16摄氏度,午后可能有阵雨。", } def search(query: str) -> str: """教学演示工具:返回本地字典中的数据。""" if query in TOOL_TABLE: return TOOL_TABLE[query] return f"没有找到与‘{query}’相关的数据,请尝试更精确的关键词。" def calculator(expression: str) -> str: """教学演示工具:计算数学表达式。生产环境请勿直接使用 eval。""" try: result = eval(expression, {"__builtins__": {}}, {}) return str(result) except Exception as e: return f"计算出错: {e}" def run_agent(user_input: str, max_steps: int = 6) -> str: messages = [ {"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": user_input}, ] for step in range(max_steps): response = client.chat.completions.create( model="gpt-4o-mini", messages=messages, temperature=0, ) answer = response.choices[0].message.content print(f"===== Step {step + 1} =====") print(answer) print() if "Final Answer:" in answer: return answer.split("Final Answer:", 1)[1].strip() action_match = re.search(r"Action:\s*(\w+)", answer) input_match = re.search(r"Action Input:\s*(.+)", answer) if action_match and input_match: action = action_match.group(1) action_input = input_match.group(1).strip() if action == "search": observation = search(action_input) elif action == "calculator": observation = calculator(action_input) else: observation = f"未知工具: {action}" messages.append({"role": "assistant", "content": answer}) messages.append({"role": "user", "content": f"Observation: {observation}"}) else: messages.append({"role": "assistant", "content": answer}) messages.append({"role": "user", "content": "请严格按协议输出 Thought/Action/Action Input/Final Answer。"}) return "达到最大步数,未得到最终答案。" if __name__ == "__main__": result = run_agent("北京今天天气怎么样?如果带伞合适吗?") print("最终答案:", result)这段代码的关键逻辑可以拆成四步看。
第一步,维护 messages 列表。这是 ReAct 循环的数据基础,每轮都会把模型输出和工具观察结果追加进去,让模型在下一次调用时能看到完整的执行历史。
第二步,调用大模型。用 chat.completions.create 让模型按照 system prompt 给定的 ReAct 协议输出文本。temperature 设为 0,是为了减少随机性,让模型的工具选择更稳定。
第三步,解析模型输出。用正则匹配 Action 和 Action Input,然后根据动作名分发到具体工具。这里用了 search 和 calculator 两个工具,search 返回的是本地字典里的固定信息,实际项目中可以换成搜索引擎接口或内部知识库 API。
第四步,把 Observation 放回上下文。这是 ReAct 循环最核心的一步——模型输出的内容作为 assistant 消息,工具返回的结果作为一条新的 user 消息。下一轮模型调用时,就能基于 Observation 继续推理。
需要特别提醒的是,示例中的 calculator 使用了 eval,这在生产环境是非常危险的,存在任意代码执行风险。这里纯粹为了演示循环协议,生产环境请使用专门的计算库或沙箱执行。search 工具也只是一个教学演示,不要直接搬到线上。
另外,示例代码默认使用 gpt-4o-mini 模型。你可以根据自己实际可用的模型服务改成其他模型名称,比如某些云厂商提供的 qwen-plus、deepseek-chat 等,只要 base_url 对应兼容接口即可。这部分配置写成环境变量,方便在不同服务之间切换。
7. 运行结果与效果验证
运行方式很简单,终端执行:
python react_demo.py正常情况下,你会看到类似下面的输出(实际内容取决于模型生成,不一定逐字一致):
===== Step 1 ===== Thought: 用户想知道北京的天气情况,以及是否需要带伞。我需要先查询北京的天气信息。 Action: search Action Input: 北京天气 ===== Step 2 ===== Thought: 查询结果显示北京今天是晴天,没有降雨,所以不需要带伞。 Final Answer: 北京今天晴,不需要带伞;户外时间长的话建议注意防晒。 最终答案: 北京今天晴,不需要带伞;户外时间长的话建议注意防晒。判断循环是否成功的标准有三个。第一,模型没有一次给出最终答案,而是先选择了工具;第二,工具返回的 Observation 确实被展示在了助手输出里,并且后续模型推理引用了这个结果;第三,循环在有限步内终止,输出 Final Answer,而且没有报错。
如果模型只输出了 Thought 而没有 Action,或者出现了“达到最大步数”的提示,排除时先打印每轮 messages 内容,重点看模型是否按照 system prompt 里的协议输出。很多情况下,这是提示词描述不够清楚导致的,而不是代码逻辑问题。可以适当在 SYSTEM_PROMPT 里增加一个完整的 few-shot 示例,模型通常会立刻改观。
8. 常见问题与排查思路
ReAct 循环本身不复杂,但在实际运行中会遇到各种意外。这里整理了一份排查清单,遇到问题时按表定位。
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 模型只输出 Thought,没有 Action | 提示词工具描述不明确,或模型想直接回答 | 打印模型完整输出,检查是否提前出现 Final Answer | 在 SYSTEM_PROMPT 中补充工具说明和示例,必要时增加 few-shot |
| 在同一个 Action 上反复循环 | 工具返回没有新信息,或 Observation 没有正确拼入上下文 | 打印每轮 messages 内容 | 精简工具返回内容,设置 max_steps 上限,检查解析规则 |
| 上下文越来越长、成本上升 | ReAct 每轮都会追加消息,多次调用 token 消耗叠加 | 观察 API 调用计费和 token 用量 | 降低 max_steps,工具返回长文本时先做摘要 |
| 模型输出格式混乱,正则解析失败 | 模型没有严格遵守协议 | 打开日志记录完整输出 | 改用 function calling 或结构化输出,减少文本解析脆弱性 |
| 调接口报 401 错误 | API Key 无效或没有权限 | 检查环境变量和控制台配置 | 重新生成 Key,配置最小权限 |
| 使用其他模型服务,接口报连接错误 | base_url 配置不正确 | 检查 base_url 是否与模型服务商文档一致 | 换成可用的兼容服务地址或云厂商接口 |
| 本地小模型生成结果不稳定 | 模型指令跟随能力较弱 | 换大模型或调整采样参数 | 降低 temperature,或换用指令遵循能力更强的模型 |
这里重点展开两个高频问题。
第一,模型输出格式不稳定。这是文本协议方案最主要的痛点。即使 SYSTEM_PROMPT 写得再详细,模型依然可能偶尔不按格式输出。工程上更稳妥的解法是用 OpenAI 的 function calling 或结构化输出,让模型直接返回一个结构化的工具调用对象,而不是让文本解析器去猜。但理解 ReAct 思想时,文本协议是最好懂的入门方式,因为它把循环机制完整暴露了出来。
第二,长短上下文与成本控制。ReAct 每轮循环都要携带全部历史消息,包括所有 Thought、Action、Observation。随着步数增加,上下文会快速膨胀,不仅影响速度,也影响成本。建议在实际项目中给工具返回设置长度上限,或者在 Observation 进入上下文之前做一次摘要压缩。另一个思路是限制 Agent 只处理必要信息,不要把一大段原始文档塞回上下文。
9. 工程化建议与使用边界
理解了 ReAct 的循环原理之后,接下来要回答一个更实际的问题:真实项目里,要不要上 ReAct、以及怎么上。
先给判断标准。如果你的任务满足以下条件,ReAct 是一个比较合适的选择:任务需要多步推理;任务需要访问实时或外部数据;执行过程中存在不确定性,需要根据中间结果调整;过程需要可审计。典型场景包括个人知识库问答助手、营销素材自动整理、客服工单跟进、需要跨多个内部系统查询数据的 Agent。
反过来,如果任务只是单轮知识问答,或者对响应延迟非常敏感,ReAct 反而不是好选择。因为 ReAct 每走一步都要调用一次大模型,多轮循环意味着延迟和成本的多倍放大。一个简单的“今天是几号”问题如果非要套 ReAct,纯属杀鸡用牛刀。很多工具,包括搜索引擎、数据库查询、代码执行器,单独就能解决一轮任务,不需要让模型反复决策。
工程上落地时,有几个经验值得参考。工具描述要写得足够具体,模型是通过工具描述来判断该调用哪个工具的,描述模糊会直接导致选错工具。比如一个检索公司内部文档的工具,描述最好写成“输入员工姓名,返回其所在部门和入职时间”,而不是一句含糊的“查询员工信息”。循环要有边界,max_steps 和超时时间必须设置,否则模型可能在某个奇怪的问题上无限循环。日志要记录每一步的 Thought、Action、Observation,排错和审计都依赖这些中间信息。
安全边界是最不能忽略的部分。Agent 能调用的工具必须白名单化,不要让 Agent 自由选择任意系统命令。涉及数据库写操作、生产环境变更、支付、删除等高风险操作时,必须设置人工审批环节,不能让 Agent 直接执行。API Key 要遵循最小权限原则,只授予 Agent 完成任务所需的必要权限。所有涉及生产环境的改动,都应该先在测试环境验证,再逐步放量。
关于框架的选择,我的建议是入门阶段先手写一遍最小循环,理解协议之后再去使用 LangChain、LangGraph、LlamaIndex 这类工具。框架可以帮你省去消息拼装和工具分发的重复劳动,但也可能把循环机制封装成黑盒,遇到问题时不方便排查。用 LangGraph 这类图编排框架时,尤其要理解节点的执行顺序和状态传递,否则写出来的 Agent 依然难以维护。需要提醒的是,框架版本迭代很快,Agent API 在不同版本之间差异很大,使用前一定要核对当前文档。
10. 总结:ReAct 到底改变了什么
ReAct 看起来结构简单,但它对 LLM 应用范式的影响非常深远。在 ReAct 之前,大模型和外部世界的交互是割裂的:要么模型独立思考回答,要么通过 RAG 查一次资料直接生成。ReAct 把“思考”和“行动”组合成了一个紧密的循环,让模型可以在执行过程中不断吸收新信息、修正判断,最终给出来自推理与实证共同支撑的答案。
真正值得记住的点是:ReAct 解决的核心问题不是“调用一个工具”,而是“如何让一个语言模型在不迷失方向的情况下完成多步任务”。Thought 负责方向,Action 负责触碰外部世界,Observation 负责把外部世界的结果反馈回模型。三者交替出现,才构成了 Agent 的基本工作单元。理解了这一点,再看各种 Agent 框架,你会发现自己已经能看懂它们的骨架了。
建议的下一步是:把本文的示例代码跑通,然后把 search 换成真实的数据源,比如天气 API、数据库查询、内部系统接口。观察它在真实数据下的表现,再评估延迟和成本。等你对循环机制足够熟悉后,再去研究 LangGraph、多智能体协作、Agent 自我改进等进阶方向。AI Agent 领域发展很快,但 ReAct 作为最基础的控制流范式之一,值得你花一个下午彻底搞懂。收藏这篇文章,动手跑一遍,比看十篇概念科普都管用。