ReAct范式实战:让Agent学会边想边做
说实话,我第一次看到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?
理论说完了,聊点干的。我试过两种实现方式,各有千秋。
方式一:手动管理循环
这是最直接的方式。你手动维护一个"思考-行动-观察"的循环队列。
核心代码框架大概是这样:
defreact_loop(user_input,max_steps=10):messages=[{"role":"system","content":REACT_SYSTEM_PROMPT},{"role":"user","content":user_input}]forstepinrange(max_steps):response=llm.call(messages)ifresponse.has_final_answer:returnresponse.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的图结构:
fromlanggraph.graphimportStateGraph,ENDfromlanggraph.prebuiltimportToolExecutor# 定义Agent节点defagent_node(state):messages=state["messages"]response=llm_with_tools.invoke(messages)return{"messages":[response]}# 定义工具节点deftool_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做更深层次的规划。感兴趣的可以关注一波。
