一、介绍
1. 什么是 ReAct
ReAct 是一种很经典的 Agent 设计范式,名字来自Reason + Act,也就是“推理 + 行动”。
它的核心思想并不复杂:
不要让模型一次性把答案“憋”出来,而是让它在任务过程中不断循环下面几个动作:
- 先根据当前信息判断,下一步最应该做什么
- 再执行一个动作,比如搜索、调用工具、查询数据库、读取文件
- 根据新得到的结果,继续判断下一步
所以,ReAct 的本质并不是“让模型更会思考”,而是让模型能够边想边做,边做边修正。
这也是它和普通问答式 Prompt 最大的区别。很多真实任务并不是只靠语言生成就能完成,而是必须与外部世界交互。ReAct 正是为这种场景设计的。
2. 为什么 ReAct 很重要
在很多真实业务里,模型并不能只靠“知道一些知识”就给出可靠答案,它往往还需要去查、去看、去验证。
比如下面这些场景:
- 信息不完整,需要先检索资料
- 问题比较复杂,需要逐步拆解
- 中间可能出现错误,需要根据反馈纠偏
- 最终结果依赖工具执行,而不是纯文本生成
举个很典型的例子:
如果用户问“某客户的订单为什么失败”,模型本身并不知道答案。它需要去查订单数据库、检索服务日志、确认接口状态,甚至比对不同系统之间的数据。没有这些动作,模型再聪明也只能猜。
ReAct 的价值就在这里:
它把“猜答案”变成了“查信息、做动作、再判断”。
3、核心结构
最经典的 ReAct 流程一般可以抽象成下面几个阶段:
- Thought:当前应该如何推进
- Action:执行什么动作
- Observation:动作返回了什么结果
- 重复上面三步
- Final Answer:最终输出结果
可以把它理解成一个闭环:
问题 → 判断下一步 → 调工具 / 查信息 → 获取结果 → 再判断下一步 → 最终回答
这个过程看起来很像人处理问题的方式。
人遇到复杂任务时,通常也不是立刻给答案,而是先判断、再操作、再根据结果调整方向。ReAct 把这种过程显式化了。
4、一个简单例子
比如用户说:
帮我订一张下周一下午从上海到北京的高铁,优先 3 点后出发、二等座。
如果是 ReAct Agent,它通常不会立刻输出一个“想象中的结果”,而是会这样推进:
- 先识别用户约束:出发地、目的地、日期、出发时间偏好、座位类型
- 调用车次查询工具
- 读取工具返回结果,筛选是否有 3 点后出发且有二等座的班次
- 如果没有完全匹配,就尝试寻找次优方案
- 在下单前向用户确认
- 最后调用订票工具完成操作
这正是 ReAct 特别适合“任务执行型 Agent”的原因。
它不是一次性输出答案,而是在执行过程中动态决策。
5、ReAct 的优缺点
优点
ReAct 之所以经典,是因为它在很多实际场景里确实很好用。
首先,它的可解释性更强。因为整个过程是分步骤推进的,所以更容易看出每一步在做什么,出了问题也更容易排查。
其次,它通常比直接回答更稳。当信息不足时,模型会先去查,而不是直接编造答案。
再者,它也比较容易扩展。只要给 Agent 接入新的工具,它的能力就能被持续增强。
另外,ReAct 还有一个很重要的特点,就是具备纠偏能力。前一步拿到的新信息可能会改变后续判断,这使得系统不像单轮输出那样“一错到底”。
最后,它很接近真实工作流。很多人类任务本来就是“分析一下、操作一下、再看结果”,ReAct 和这种方式天然契合。
缺点
当然,ReAct 也不是万能的。
最常见的问题是:步数一多,成本和时延就会上去。
一次任务如果要循环很多轮,那么 token 消耗和工具调用时间都会明显增加。
第二个问题是:工具一多,模型可能会乱用。
如果工具定义不清楚,或者工具能力边界重叠,Agent 很容易频繁调用无效工具。
第三,ReAct 对Observation 的质量非常敏感。
如果工具返回结果不完整、不准确,模型后续的判断就会被带偏。
另外,它对 prompt、工具描述、停止条件也比较敏感。
同样的流程,稍微改一下提示词或工具定义,行为就可能明显不同。
还有一点很现实:对于简单任务,ReAct 反而可能显得太重。
一个本来一句话就能回答的问题,没必要硬走多轮推理和工具调用。
实际落地中的常见做法
因此,很多工程系统并不会“纯用 ReAct”,而是把它和其他策略组合使用,比如:
- 简单任务直接回答
- 复杂任务再进入 ReAct
- 高风险动作必须人工确认
- 大任务先做 Plan,再在子任务里使用 ReAct
这也是 ReAct 在工程上更常见的形态:
不是唯一范式,而是工作流中的一个核心机制。所以很多系统不会纯用 ReAct,而是把它和别的范式组合。
6、使用 ReAct 时的几个实践建议
在实际开发中,想让 ReAct 更稳定,通常要注意几件事。
第一,工具描述一定要清楚。
模型是否能正确调用工具,和工具名、参数说明、适用场景描述关系很大。
第二,一步只做一件高价值的事。
不要让模型一口气做太多动作,否则很容易失控。
第三,给它明确的停止条件。
比如“拿到足够证据后结束”“最多调用工具 N 次”“高风险操作必须确认”,这些都很重要。
第四,Observation 尽量结构化。
工具返回结果越清晰,模型越容易做出正确判断。
第五,不要把所有任务都强行套成 ReAct。
简单问题直接回答,复杂问题再进入 ReAct,往往更实用。
二、ReAct 的基本使用方式
常见的 Prompt 模板
在工程实践里,ReAct 常常会被写成类似下面这样的系统提示:
你是一个可以使用工具的助手。 目标:解决用户问题,但不要凭空猜测。 规则: 1. 如果信息不足,优先调用工具获取信息 2. 每次只做一个最有价值的动作 3. 根据工具返回结果决定下一步 4. 有充分证据后再给最终答案 5. 如果涉及高风险操作,先请求确认
这个模板背后的思想很简单:
- 不鼓励“猜”
- 鼓励先查证
- 每一步只做最必要的动作
- 最终输出要建立在证据之上
这其实就是 ReAct 的核心原则。更准确地说是:
- tool 必须有,尤其是函数名、参数类型、docstring 这几个信息
- system_prompt 只是行为提示,不是 ReAct 的硬性必需项
- ReAct 的“循环推理 + 调工具”主要是 agent 运行机制,不是你一定要在提示词里写 “ReAct”
ReAct 真正发挥作用,往往离不开工具系统。常见的工具可能包括:
search_docs(query)
query_db(sql)
read_log(service, time_range)
send_email(...)
有了这些工具,模型就不再只是“说”,而是可以“做”。
在这个意义上,ReAct 可以理解为一种调度机制:
它负责根据当前上下文,决定什么时候该查文档、什么时候该查数据库、什么时候该读日志,以及什么时候已经足够给出最终答案。
代码实现
以langchain为例可以使用以下代码:
from langchain.agents import create_agent agent = create_agent( llm, tools=xxx, ) result = agent.invoke({"messages": [{"role": "user", "content": question}]})
- llm.invoke(...) 是一次模型调用,不算ReAct
因为只负责把输入发给模型,最多返回 tool_calls,不会自动帮你执行工具
- agent.invoke(...) 是启动一个带循环的 agent 运行时
create_agent 会把模型和工具编成一个运行时,内部会在 model node -> tools node -> model node 之间反复跳,直到模型给出最终答案
这里需要注意:
- tools/list + prompt = 工具说明,只把工具说明塞进 prompt,让模型一次性回答 = 不是 ReAct
- tools/list + prompt + 你自己的执行循环 = ReAct 风格,如python AI工程(一)python实现mcp(2)sdk-CSDN博客 是ReAct
三、ReAct 适合用在哪些场景
ReAct 并不是适合所有任务,但它非常适合那些不能只靠脑补完成的工作。
常见场景包括:
1. 检索问答
需要查网页、知识库、文档,再基于检索结果回答问题。
2. 业务排障
需要查日志、查数据库、看监控、分析接口状态,然后定位问题原因。
3. 办公自动化
需要调用日历、邮件、表格、工单系统等外部工具,完成真实操作。
4. 编码助手
需要读取代码文件、检索仓库、运行测试、根据报错再修复代码。
5. 多步决策任务
任务中途要根据结果改变路线,而不是一开始就把路径固定死。
一句话总结就是:
只要任务不是“光靠生成文字就能完成”,ReAct 往往都比一次性回答更靠谱。
四、demo
用户询问
帮我判断一下北京明天下午适不适合跑步
ReAct
import importlib import json import os from langchain.agents import create_agent from langchain_core.tools import tool from langchain_openai import ChatOpenAI @tool def get_weather(city: str, day: str) -> str: """查询某个城市某一天的天气;有真实工具时优先调用,否则返回 mock 数据。""" module_name = os.getenv("WEATHER_TOOL_MODULE") if module_name: try: module = importlib.import_module(module_name) result = module.get_weather(city, day) return result if isinstance(result, str) else json.dumps(result, ensure_ascii=False) except Exception: pass fake_db = { ("北京", "明天"): { "weather": "多云", "temp_c": 24, "wind_level": 2, "rain": False, } } return json.dumps( fake_db.get( (city, day), {"weather": "未知", "temp_c": None, "wind_level": None, "rain": None}, ), ensure_ascii=False, ) llm = ChatOpenAI( model="gpt-5.1", base_url="https://llm-gateway.xxx.xxx.xx/v1", api_key="dummy", default_headers={ "X-Api-Key": "******", }, temperature=0, use_responses_api=False, ) agent = create_agent( llm, tools=[get_weather], system_prompt="你是一个遵循 ReAct 风格的助手。信息不足时先调用工具,拿到结果后再给出结论。", ) def main() -> None: question = "帮我判断一下北京明天下午适不适合跑步" result = agent.invoke({"messages": [{"role": "user", "content": question}]}) print(result["messages"][-1].content) if __name__ == "__main__": main()输出:
根据明天北京下午的天气情况(多云、约24℃、微风2级、无降雨),整体来说是很适合跑步的。
简单建议:
- 时间:下午 4 点以后更舒适,避免日照最强时段。
- 装备:短袖+短裤即可,带一小瓶水。
- 配速:如果是平时有跑步习惯,可以正常训练配速;如果最近较少运动,可以适当放慢,当成轻松跑。不适合跑步的情况(即使天气不错也要注意):
- 近期感冒、发烧、咳嗽、身体明显疲劳。
- 心率异常、胸闷、头晕等不适症状。如果你告诉我你的跑步习惯(比如每周几次、单次几公里),我可以帮你具体设计一下明天下午的跑步计划。
测试发现去掉system_prompt,效果是一样的。