1.2 ReAct范式:推理与行动的交织循环 一、ReAct的核心直觉:思考与行动的「乒乓对练」 ReAct(Reasoning + Acting)这个名字本身就道出了它的核心思想——推理(Reasoning)和行动(Acting)不是先后关系,而是交替进行的。就像乒乓球比赛中的来回击球,Agent在「想一步、做一步、看结果、再想一步」的循环中推进任务。 在深入理论之前,让我们用一个完整的执行trace来建立直觉。假设你让Agent完成这个任务: 「帮我查一下明天从北京飞东京最便宜的直飞航班,顺便看看东京明天的天气。」 这不是一个简单的查询——它包含两个子任务(查航班和查天气),每个都需要不同的工具,且有时间约束(明天)、空间约束(北京到东京)、偏好约束(最便宜的直飞)。
ReAct(Reasoning + Acting)这个名字本身就道出了它的核心思想——推理(Reasoning)和行动(Acting)不是先后关系,而是交替进行的。就像乒乓球比赛中的来回击球,Agent在「想一步、做一步、看结果、再想一步」的循环中推进任务。
在深入理论之前,让我们用一个完整的执行trace来建立直觉。假设你让Agent完成这个任务:
「帮我查一下明天从北京飞东京最便宜的直飞航班,顺便看看东京明天的天气。」
这不是一个简单的查询——它包含两个子任务(查航班和查天气),每个都需要不同的工具,且有时间约束(明天)、空间约束(北京到东京)、偏好约束(最便宜的直飞)。我们来看Agent如何用ReAct范式处理:
第一轮:理解与拆解
Thought: 用户要求完成两个任务:(1)查明天北京飞东京最便宜的直飞航班,(2)查东京明天天气。我需要先确定明天的具体日期,然后分别执行两个查询。先查日期。 Action: get_current_date
Observation: 2025-03-15
第二轮:执行第一个子任务——查航班
Thought: 明天是2025-03-16。现在需要查询北京(PEK)到东京(HND/NRT)的直飞航班。我应该使用航班搜索工具,出发地设为PEK,目的地同时搜索HND和NRT两个机场,日期2025-03-16。 Action: search_flights(departure="PEK", arrival="HND,NRT", date="2025-03-16", direct_only=true)
Observation: 航班1: CA183 PEK→HND 07:30-11:30 ¥2,340 航班2: JL872 PEK→NRT 09:15-14:00 ¥3,120 航班3: CA929 PEK→HND 14:00-18:00 ¥2,180 航班4: NH960 PEK→NRT 16:30-21:15 ¥2,890
第三轮:分析与选择
Thought: 返回了4个直飞航班。按价格排序:CA929(¥2,180) < CA183(¥2,340) < NH960(¥2,890) < JL872(¥3,120)。最便宜的是CA929,下午2点出发。不过我应该注意,用户可能隐含了出发时间偏好——如果是出差,早上出发更合理;如果是旅行,下午也行。考虑到用户没有明确说明,我先选最便宜的CA929,但同时也应该考虑时间因素。等等,用户说的是"最便宜的直飞航班",这个优先级很明确,就是价格最低,所以选CA929。现在去查东京天气。
第四轮:执行第二个子任务——查天气
Thought: 现在查询东京明天的天气。东京的时区是UTC+9,和北京一样。日期2025-03-16。 Action: get_weather(location="Tokyo", date="2025-03-16")
Observation: Tokyo 2025-03-16: 多云转晴,气温8-16°C,降水概率15%,风速15km/h
第五轮:整合与回复
Thought: 已经收集了所有信息。最便宜的直飞航班是国航CA929,下午2:00从PEK出发,6:00到达HND,票价¥2,180。东京明天天气不错,多云转晴,气温8-16°C,基本不会下雨。我应该在回复中把两个信息整合呈现,并给出一些实用的出行建议(比如温度偏低需要带外套)。 Final Answer: 明天(3月16日)从北京飞东京最便宜的直飞航班是: ✈️ 国航CA929 | PEK 14:00 → HND 18:00 | ¥2,180 东京明天天气:☁️→☀️ 多云转晴,8~16°C,基本不下雨。 温馨提示:东京气温偏低,建议带件外套。祝旅途愉快!
这个trace虽然简化,但展示了ReAct的完整节奏:Thought-Analyze → Action-Execute → Observe-Learn → Thought-Decide → 循环或结束。每一轮Thought都建立在前一轮Observation的基础上,形成了一条清晰的推理链。
在上面的trace中,你可能已经注意到一个关键细节:每一轮的Thought不仅在做「接下来做什么」的决策,还在做「根据观察结果调整策略」的分析。Thought的质量,直接决定了后续Action的正确性和效率。
让我们用一个反面例子来说明。假设第三轮的Thought质量很低:
Thought (低质量): 找到了航班,最便宜的是CA929,¥2,180。现在查天气。
这个Thought虽然得出了相同的结论(选CA929),但跳过了关键的推理过程。如果情况稍微复杂一点,比如:
一个低质量的Thought可能忽略这些上下文信息,做出看似合理但实际不合适的决策。高质量的Thought则会:
这就是为什么ReAct范式中,推理深度直接映射到行动质量。Thought不是可有可无的注释,而是Agent决策的核心引擎。
ReAct范式能否生效,很大程度上取决于Prompt如何引导模型进入正确的推理-行动模式。经过大量实践,业界沉淀出了一套相对成熟的Prompt工程框架。
一个典型的ReAct Prompt包含以下几个部分:
1. 角色设定(Role Definition)
告诉模型它是一个能够推理和行动的Agent,并明确它的能力边界:
你是一个智能助手,能够通过推理来分析问题,并通过调用工具来执行操作。 你可以访问以下工具:[工具列表及描述]
角色设定看似简单,但它建立了模型对自身能力的认知框架——模型需要知道「我能做什么」才能合理规划。
2. 推理格式规范(Format Specification)
明确告诉模型每个步骤应该输出什么格式:
在每一步,你必须严格按以下格式输出: Thought: [你的推理分析过程] Action: [工具名称和参数] 或者当你有足够信息给出最终答案时: Thought: [你的总结分析] Final Answer: [给用户的回答]
格式规范的重要性怎么强调都不过分。没有格式约束,模型可能把推理和行动混在一起输出,导致解析失败。格式规范实际上是在模型和执行框架之间建立了一个通信协议。
3. 少样本示例(Few-shot Examples)
提供2-3个完整的Thought-Action-Observation示例,让模型理解期望的推理深度和行动模式:
示例: Question: 什么是法国首都的人口? Thought: 这是一个事实查询问题。我需要查找巴黎的人口数据。 Action: search(query="Paris population") Observation: 巴黎市区人口约210万,大巴黎地区人口约1220万。 Thought: 我得到了巴黎的人口数据。可以给出答案了。 Final Answer: 法国首都巴黎的市区人口约210万,大巴黎地区人口约1220万。
示例有两个关键作用:一是展示推理的深度(不是只说「搜索巴黎人口」然后直接给答案),二是展示格式(严格遵循Thought-Action-Observation循环)。
工具使用指南(Tool Usage Guide)
除了列出工具名称和参数,还应该提供每个工具的使用注意事项和典型场景:
工具: search_flights 参数: departure(出发机场IATA代码), arrival(到达机场IATA代码), date(日期YYYY-MM-DD), direct_only(是否仅直飞) 注意事项: - 机场代码需使用IATA标准代码,如PEK=北京首都,Tokyo需指定HND或NRT - 结果可能包含中转航班,设direct_only=true过滤 - 票价为参考价,实际价格可能变化
这种详细的使用指南能显著减少工具调用错误,是提升Agent可靠性的重要手段。
反思提示(Reflection Prompt)
在Prompt中加入引导模型自我检查的指令:
在给出Final Answer之前,请检查: 1. 是否遗漏了用户问题中的任何要求? 2. 工具返回的数据是否合理?有无异常值? 3. 我的推理链是否连贯?有无逻辑跳跃?
这个技巧相当于在Agent的执行循环中内嵌了一个轻量级的「反思检查点」,虽然不如专门的反射机制(下一章讨论)强大,但成本极低,能有效减少低级错误。
ReAct范式虽然优雅,但在实际应用中会遇到一系列典型的失败模式。了解这些失败模式,是设计健壮Agent系统的前提。
Agent在两个或多个状态之间反复切换,永远无法到达终止条件。这是ReAct系统中最常见也最危险的失败模式。
典型表现:
Thought: 我需要查一下北京的天气来确认航班是否受影响。 Action: get_weather(location="Beijing") Observation: 北京明天有小雨。 Thought: 下雨可能影响航班,我再查一下航班的延误情况。 Action: check_flight_delay(flight="CA929") Observation: 暂无延误信息。 Thought: 暂无信息不代表不会延误,我应该持续监控。再查一次天气确认。 Action: get_weather(location="Beijing") Observation: 北京明天有小雨。 ... [无限循环]
根本原因:Agent缺乏终止判断能力。它不知道什么时候应该停止查询并给出答案。
应对策略:
Agent在多步推理中逐渐偏离原始任务目标,进入无关的推理分支。这是大模型「容易分心」特性在Agent场景下的放大表现。
典型表现:
Thought: 用户问北京到东京的航班。北京首都机场(PEK)... Thought: 等等,北京还有大兴机场(PKX),也许从大兴出发更便宜? Thought: 大兴机场是2019年启用的,当时还引发了一些争议... Thought: 说起机场争议,希思罗机场第三跑道也争议了很多年... ... [推理越来越偏,完全忘记查航班这个任务]
根本原因:大模型的注意力在长上下文中容易发生偏移,且每一步推理都以上一步为基础,偏移会被逐步放大。
应对策略:
Agent调用了错误的工具、传递了错误的参数、或对工具返回的结果做了错误解读。
典型表现:
Thought: 我需要查航班价格,使用search_hotels工具。 Action: search_hotels(city="Beijing", check_in="2025-03-16") Observation: [返回了北京酒店列表] Thought: 找到了最便宜的航班信息...
根本原因:模型对工具功能的理解不精确,或者工具描述不够清晰,导致Agent混淆了功能相似的工具。
应对策略:
Agent在还没有充分收集信息、推理链条还不完整时就草率给出Final Answer。
典型表现:
Thought: 用户问北京到东京的航班。我记得国航有这条航线。 Final Answer: 北京到东京可以坐国航航班,大概3个小时。
根本原因:模型对自身知识过度自信,跳过了工具调用的步骤;或者为了追求速度而牺牲了信息完整性。
应对策略:
把ReAct范式落地为可运行的系统,需要一套工程架构来支撑。典型的ReAct Agent系统包含以下几个核心组件:
1. 任务解析器(Task Parser)
接收用户输入,解析出核心任务和约束条件。这通常由大模型的第一次推理完成。
2. 推理引擎(Reasoning Engine)
生成Thought和Action。每次推理都以上下文(任务历史+工具描述+Observation)为输入,输出下一步的Thought和Action(或Final Answer)。
3. 工具调度器(Tool Dispatcher)
接收推理引擎输出的Action,解析工具名称和参数,调用对应的工具实现,将结果封装为Observation返回。
4. 循环控制器(Loop Controller)
管理ReAct循环的生命周期:判断是否继续循环、检测死循环、控制最大步数、决定何时终止。
5. 状态管理器(State Manager)
维护整个执行过程的状态:任务历史、已执行的工具调用、中间结果、推理上下文。在长链任务中,状态管理还涉及上下文窗口的裁剪和压缩。
这五个组件协同工作,构成了ReAct Agent的运行骨架。其中推理引擎由大模型驱动,其余四个组件是传统软件工程实现——这恰恰是Agent系统的典型特征:AI驱动的推理与传统软件工程的深度融合。
ReAct范式是当前Agent系统最主流的执行框架,它的核心思想是推理与行动的交替循环:每一步先思考(Thought)再行动(Action),然后观察结果(Observation)再思考下一步。
在这个过程中,Thought的质量是决定Agent表现的关键因素——高质量的Thought能准确分析当前状态、合理选择下一步行动、有效利用历史上下文。而低质量的Thought会导致推理漂移、工具误用和过早终止。
Prompt工程是引导模型产出高质量Thought的主要手段,包括角色设定、格式规范、少样本示例和工具使用指南。同时,系统层面必须有健壮的失败模式处理机制,特别是针对死循环和推理漂移的防护。
ReAct解决的是Agent「如何行动」的问题。但光会行动还不够——一个优秀的Agent还需要能够评估自己的行动效果,并在发现问题时自我纠正。这就是下一章要讨论的核心机制:反射(Reflection)。