说实话,我第一次看到ReAct这个论文标题的时候,内心是拒绝的。
"又是个学术界造的概念吧?"我当时想。
直到我真的在一个实际项目里试了一下,才发现------这玩意儿真的不是花架子。它是Agent从"傻傻地问一句答一句"到"能思考、能行动、能纠错"的关键一步。
这篇文章不讲虚的,直接聊ReAct怎么落地。
问题:单纯的Prompt + Tool Call为什么不够?
你可能会说:"给LLM配几个工具的Function Calling不就行了吗?"
行,但不完全行。
举个我踩过的坑。我让一个Agent帮忙分析用户反馈数据。它需要先查数据库拿到最近一周的反馈,然后做个情感分析,最后输出一个报表。
如果只用Function Calling,LLM会尝试一步到位------"好的,我来查数据、分析情感、生成报表"。但问题是:它不知道自己的查询结果质量如何,也不知道情感分析结果出来后有没有异常需要二次验证。
这就好比一个程序员写完代码不测试就直接上线。能跑是运气,崩了是常态。
核心思想:思考 → 行动 → 观察 → 再思考
ReAct的全称是 Reasoning + Acting。核心就四个字:边想边做。
传统的LLM调用流程是:
输入 → LLM → 输出
ReAct的流程是:
输入 → LLM思考(我要做什么)→ 行动(调用工具)→ 观察(工具返回的结果)→ 再思考(结果是否合理?下一步做什么?)→ 再行动 → ... → 最终输出
这个循环让Agent有了"反馈回路"。每一步的结果都会影响下一步的决策。
一个完整的ReAct循环长什么样
我看过最清晰的解释,是论文里的一个例子。问Agent:"在加州哪里可以买到Xbox?"
- 思考: 用户想知道Xbox的购买地点。我需要查一下加州的零售信息。
- 行动: search("加州 Xbox 购买地点")
- 观察: 搜索结果返回了GameStop、Best Buy、Target和微软官网的链接
- 思考: 搜索结果包含了实体店和线上渠道。用户问的是"买",可能包括两者。让我进一步搜一下有没有具体的优惠信息。
- 行动: search("Xbox Series X 加州的库存情况")
- 观察: 有些店有现货,有些显示缺货
- 思考: 我可以整理一下哪些店有现货,以及购买渠道。
- 最终回答: 在加州,你可以去GameStop、Best Buy等实体店购买,或者上微软官网下单。目前GameStop和Best Buy部分店面有现货...
看到了吗?每一步都是"我想一下 → 我做一下 → 我看结果 → 我再想一下"。
工程实现:怎么落地ReAct?
理论说完了,聊点干的。我试过两种实现方式,各有千秋。
方式一:手动管理循环
这是最直接的方式。你手动维护一个"思考-行动-观察"的循环队列。
核心代码框架大概是这样:
python
def react_loop(user_input, max_steps=10):
messages = [
{"role": "system", "content": REACT_SYSTEM_PROMPT},
{"role": "user", "content": user_input}
]
for step in range(max_steps):
response = llm.call(messages)
if response.has_final_answer:
return response.final_answer
action = parse_action(response.text)
observation = execute_tool(action.name, action.args)
messages.append({"role": "assistant", "content": response.text})
messages.append({"role": "tool", "content": observation})
return "Max steps reached"
关键点在于system prompt要写得够好。论文里推荐在System Prompt中给出明确的格式要求,让LLM知道每一步应该输出什么。
我用的System Prompt模板是这样的:
你是一个智能Agent。你需要通过"思考→行动→观察"的循环来完成任务。
每轮你需要输出:
1. 思考(Thought):分析当前状态,决定下一步做什么
2. 行动(Action):调用一个工具函数
3. 观察(Observation):工具返回的结果(由系统填充)
当你认为任务已经完成时,输出最终答案(Final Answer)。
可用工具列表:
- search(query): 搜索网络信息
- calculate(expression): 执行数学计算
- ...
说实话,这种方式简单直接,但有一个问题------Token消耗大。每一步的思考和行动都会产生大量的Token输出。如果你的task需要5-6步,光是ReAct循环本身的Token消耗就够你心疼的。
方式二:用Agent框架
偷懒的方式是直接用现成的Agent框架。LangChain、LangGraph、CrewAI、AutoGen这些框架都内置了ReAct支持。
以LangGraph为例,它把ReAct封装成了一个Node + Edge的图结构:
python
from langgraph.graph import StateGraph, END
from langgraph.prebuilt import ToolExecutor
# 定义Agent节点
def agent_node(state):
messages = state["messages"]
response = llm_with_tools.invoke(messages)
return {"messages": [response]}
# 定义工具节点
def tool_node(state):
messages = state["messages"]
last_message = messages[-1]
tool_calls = last_message.tool_calls
results = tool_executor.batch(tool_calls)
return {"messages": results}
# 构建图
graph = StateGraph(AgentState)
graph.add_node("agent", agent_node)
graph.add_node("tools", tool_node)
graph.add_conditional_edges("agent", should_continue, {"continue": "tools", "end": END})
graph.add_edge("tools", "agent")
框架的好处是帮你处理了循环逻辑、上下文管理、错误重试这些脏活。但代价是------你失去了对每一步的精细控制。如果你的场景需要非常定制化的ReAct逻辑,手撸可能更灵活。
踩坑记录
这东西看着简单,但真正用起来有几个坑。
坑1:LLM喜欢"跳过思考"
我遇到过一个奇怪的问题:LLM在某个轮次突然不输出了Thinking,直接给出了Final Answer。
排查后发现,是模型觉得"结果已经够好了"。但这往往是因为它忽略了某些细节。
解决办法:在System Prompt里加上"除非任务明确完成,否则每一步都必须包含思考步骤"的约束。
坑2:循环不终止
有些问题没有明确的"完成"条件,Agent会在工具调用和观察之间来回跳跃,永远不停。
解决方案:设置最大步数限制(我一般设5-8步),超时后返回当前最好的结果。
坑3:观察结果太长
工具返回的数据可能非常长(比如一次搜索返回10条结果),这些内容全部塞进上下文,Token爆炸。
方案:对观察结果进行摘要。只保留关键信息,丢掉冗余内容。这招能省至少30%的Token。
什么时候用ReAct?什么时候不用?
我个人觉得,ReAct最适合的场景是:
- 多步骤推理任务:需要多次查询/计算才能得出结论的
- 需要纠错的任务:第一次行动的结果可能不准确,需要二次验证
- 决策路径不确定的任务:Agent需要根据中间结果动态调整策略
不适合的场景:
- 简单的QA:用户问"今天天气怎么样?",直接查天气API返回结果就行,不需要思考循环
- 高实时性场景:ReAct的每一次循环都需要一次LLM调用,延迟不可控
- Token敏感场景:ReAct的Token消耗比普通问答高一个数量级
写在最后
ReAct不是什么黑魔法。它的核心思想很简单:让LLM的推理过程和实际行动交替进行,互相验证。
这个思路不仅在Agent领域有用,在日常编码中也有启发------别写一段超长的代码再debug,而是一小段一小段地写,每写完一段就跑一下看看结果。高效多了。
如果你也想在项目中引入Agent能力,建议从ReAct开始。它是最基础、最可控的Agent范式,也是理解和实现更复杂Agent架构(比如Tree-of-Thought、Multi-Agent)的基石。
下一篇我打算聊聊Tree-of-Thought,让Agent做更深层次的规划。感兴趣的可以关注一波。