考察点
ReAct 是 Agent 工作模式里的必考题,后面必跟「怎么防止死循环」「和 Plan-and-Execute 的区别」。面试官想确认你不只会背 Reasoning + Acting 两个词,而是真的写过这个循环:轨迹怎么拼进 prompt、工具结果怎么回填、什么时候停下来。
参考答案
原理:把「想」和「做」交织起来
ReAct 出自 2022 年 Google 的论文,核心思想一句话:让模型在每一步同时输出「我在想什么」和「我要做什么」。传统的 Chain-of-Thought 只在脑子里推理,推到底才给答案,中间没法接触外部世界;纯工具调用又缺少显式推理,模型容易乱调。ReAct 把两者交织成一个循环:
Thought: 用户问的是今年特斯拉股价,我的知识截止了,需要查实时数据
Action: search("Tesla stock price 2026")
Observation: 搜索结果……
Thought: 拿到了当前价格,但还需要对比年初数据
Action: search("TSLA price January 2026")
Observation: ……
Thought: 信息够了,可以回答
Final Answer: ……
关键机制是 Observation 回填:每次工具执行的结果作为新的上下文拼回 prompt,模型下一步的推理建立在前一步的真实观察上,而不是自己的想象上。这一下就掐住了纯 CoT 最大的毛病——推理链里某一步想错了,后面全盘皆错,ReAct 至少能用外部世界的反馈纠偏。
工程落地的三个细节
1. 轨迹格式别自己发明。 现在主流模型(GPT-4o、Claude、Qwen 等)都原生支持 tool_calls 协议,生产环境直接用模型的 Function Call 能力,不要退回「prompt 里教模型输出 Action: xxx 再用正则解析」的老路——那是 ReAct 论文时代的做法,现在只有模型不支持 FC 时才用。Thought 这一侧,很多框架把推理放在 tool_call 的 reasoning 字段或者消息 content 里,效果一样。
2. 上下文管理是成本大头。 ReAct 每跑一步,完整轨迹(所有历史 Thought + Action + Observation)都要重新喂给模型,token 消耗随步数线性甚至超线性增长。我踩过的坑是网页抓取类的工具:一个页面几十万字塞进 Observation,两轮就爆了。工程上的做法是工具层截断(比如搜索结果只留前 500 字 + 摘要),或者把大对象存外部存储、上下文里只放引用句柄。
3. 必须有刹车。 ReAct 循环的标准三件套:max_iterations(我一般设 10-15)、wall-clock 超时、token 预算。再加一个「重复检测」:连续两轮 Action 相同且参数相同,直接判死循环退出。这些是兜底的,后面追问里展开。
优缺点和适用场景
优点:简单、通用、可解释——轨迹本身就是审计日志,出了错能顺着 Thought 一步步查。缺点也明显:局部贪心,模型只看眼前一步,长程任务容易跑偏;串行执行,延迟随步数累加;每步都要全量重放上下文,成本不低。
所以我的选型习惯是:步骤数 3-8 步、路径不可预知的探索型任务(故障排查、深度检索、代码修复),ReAct 是默认选择;步骤超过 10 步或者子任务能并行,就该考虑 Plan-and-Execute 了。
一个最小实现
伪代码也就几十行:
for step in range(max_steps):
resp = llm.chat(messages, tools=tools)
if resp.final_answer:
return resp.final_answer
for call in resp.tool_calls:
result = execute(call) # 带超时和异常捕获
messages.append(tool_msg(call.id, truncate(result)))
raise LoopExhausted("超过最大步数,转人工或降级")
骨架就这么简单,真正的工程量全在 execute 的容错、truncate 的上下文管理、以及 messages 的裁剪策略里。
可能的追问
- 「ReAct 死循环怎么防?」 三层:硬上限(步数/超时/token 预算)、重复检测(同 Action 同参数连续出现即退出)、prompt 层引导(告诉模型「两次尝试失败就换策略或放弃」)。
- 「ReAct 和 CoT 的区别?」 CoT 是单次调用内的纯推理,不接触外部世界;ReAct 是多次调用的循环,每步用真实观察校正推理,抗幻觉能力更强但成本高。
- 「ReAct 和 Plan-and-Execute 怎么选?」 看任务可分解性:能提前拆出稳定子任务的用 Plan-and-Execute 省 token;路径必须边走边看的用 ReAct。