AI Agent ReAct模式解析:从推理到行动的智能体实现
1. 从“想”到“做”:Agent ReAct模式的核心价值
如果你最近在关注AI Agent的开发,或者尝试过让大模型去完成一些稍微复杂的任务,比如“帮我查一下明天北京的天气,然后根据天气推荐一个室内活动,并生成一份简单的日程安排”,你可能会发现一个尴尬的现象:模型要么直接开始“编造”天气信息,要么在推荐活动时天马行空,完全脱离了查询到的真实数据。它似乎在一个“纯想象”的层面工作,缺乏与现实世界(比如一个真实的天气API)交互并基于反馈调整行动的能力。
这正是ReAct模式要解决的核心问题。ReAct,即“Reasoning + Acting”(推理+行动),它不是某个具体的框架或工具,而是一种让AI Agent(智能体)更可靠、更接近人类解决问题方式的核心范式。简单来说,它让Agent学会“三思而后行”:先动脑推理(Reasoning),再动手执行(Acting),然后根据执行结果再次推理,形成一个“思考-行动-观察”的闭环。
为什么这种模式如此重要?在传统的提示工程或简单链式调用中,大模型更像一个“闭卷考试”的考生,只能基于已有的知识(训练数据)进行一次性输出。但对于动态、多步骤、需要与外部工具交互的任务,这种模式就力不从心了。ReAct模式将Agent变成了一个“开卷考试”的考生,允许它主动查阅资料(调用工具)、验证信息,并动态调整解题思路。
我最初接触这个概念是在尝试构建一个自动化数据分析Agent时。当时模型能很好地理解“分析上个月销售数据”这个指令,并生成一段看似合理的分析文本。但问题是,它分析的数据是它“想象”出来的,并非我数据库里的真实数字。这让我意识到,没有“行动”能力的“推理”,在真实业务场景中几乎是无效的。ReAct模式正是连接AI“大脑”与真实世界“手脚”的关键桥梁。
2. ReAct模式的工作原理:一个动态的思考循环
理解ReAct,最好的方式不是看定义,而是看它如何运行。我们可以将其拆解为一个可重复的循环单元,这个循环由三个核心步骤构成:推理(Reason)、行动(Act)、观察(Observe)。
2.1 循环拆解:Reason, Act, Observe
第一步:推理(Reason)这是Agent的“思考”阶段。Agent基于当前的任务目标、已有的历史信息(包括之前的观察结果)以及自身的知识,来决定下一步应该做什么。这个“决定”通常以自然语言的形式输出,内容是关于下一步行动的思考过程和具体指令。例如:“用户想了解明天的天气。要完成这个任务,我需要先调用天气查询API。目前我还没有任何数据,所以我的下一步行动是查询北京明天的天气。”
关键点在于,这个推理过程是可解释的。Agent会“说出”它的思考,这让我们能够追踪其决策逻辑,这在调试和优化Agent时至关重要。它不仅仅是内部的一个向量计算,而是以文本形式呈现的思维链(Chain-of-Thought)。
第二步:行动(Act)根据推理步骤得出的结论,Agent会执行一个具体的动作。这个动作通常是调用一个预定义的工具(Tool/Function),比如:
- 执行一段代码(
python) - 调用一个外部API(
get_weather(api_key, city)) - 查询数据库(
sql_query) - 甚至是在一个模拟环境中移动(
move_forward)
行动的输出是一个对工具的调用请求。这个阶段,Agent从“思考者”转变为“执行者”。
第三步:观察(Observe)行动执行后,会有一个结果。这个结果被反馈给Agent,成为新的“观察”。例如,调用天气API后,返回的结果可能是:{“city”: “Beijing”, “weather”: “Sunny”, “temp”: “22°C”}。Agent需要接收并理解这个观察结果。
观察结果被纳入到Agent的上下文(Context)中。接下来,循环回到第一步“推理”。Agent会基于这个新的观察,重新思考:“我已经获得了北京的天气是晴天22°C。用户的下一个需求是根据天气推荐活动。晴天适合户外活动,所以我下一步应该搜索‘北京晴天户外活动推荐’。”
这个Reason -> Act -> Observe -> Reason -> ...的循环会一直持续,直到Agent推理出任务已经完成(例如,生成了最终的日程安排并回复给用户),或者达到了预设的循环次数上限。
2.2 与简单链式调用(Chain)的本质区别
很多人容易把ReAct和LangChain等框架中常见的“Sequential Chain”(顺序链)混淆。它们看起来都是一步接一步,但内核完全不同。
简单链式调用(Chain):流程是预设且线性的。好比一个事先写好的剧本:第一步,调用工具A;第二步,将A的结果传给工具B;第三步,将B的结果总结输出。如果工具A失败了,整个链就断了,或者会带着错误信息继续执行,缺乏应变能力。它没有“思考”环节,只是机械地执行预定步骤。
ReAct模式:流程是动态且由模型实时决定的。好比一个拥有剧本大纲的导演,他会根据上一场戏的拍摄效果(观察),临时决定下一场戏该怎么拍(推理),然后指挥剧组行动(行动)。如果查询天气API失败(观察到一个错误),Agent会推理:“API调用失败,可能网络问题或城市名错误。我可以重试一次,或者换一个备用天气源。” 然后执行新的行动。它的路径是不确定的,具备强大的容错和规划能力。
用一个更生活的比喻:Chain像是按照固定菜谱做菜,ReAct则像是经验丰富的大厨,会根据灶火的大小、食材的状态随时调整翻炒的手法和调味料的用量。
3. 如何实现一个基础的ReAct Agent:从零搭建
理解了原理,我们来看如何动手实现。这里我们不依赖任何重型框架,用最直观的Python代码和OpenAI API来演示核心骨架,让你看清每一块“骨头”是怎么长的。我们以实现一个“联网搜索Agent”为例。
3.1 环境准备与核心组件定义
首先,你需要准备一个支持函数调用(Function Calling)的大模型API,如OpenAI的GPT-4系列或Claude 3系列。我们以OpenAI为例。
pip install openai然后,定义我们Agent最核心的两个部分:工具集和提示词模板。
import openai import json import requests from typing import Dict, Any, List # 假设你的API Key已设置在环境变量中 client = openai.OpenAI() # 1. 定义工具集(Actions) def search_web(query: str) -> str: """一个模拟的网页搜索工具。在实际应用中,你可以接入Serper API、Google Search API等。""" # 这里为了演示,我们模拟返回一些固定结果 print(f"[行动] 正在搜索: {query}") # 模拟网络请求和结果解析 # 真实情况:results = call_search_api(query) mock_results = f"关于'{query}'的搜索结果:晴天适合户外徒步、骑行或公园野餐。北京奥林匹克森林公园是热门选择。" return mock_results def calculate(expression: str) -> str: """一个简单的计算器工具。""" print(f"[行动] 正在计算: {expression}") try: result = eval(expression) # 注意:生产环境请使用更安全的方式如`ast.literal_eval` return f"计算结果: {result}" except Exception as e: return f"计算错误: {e}" # 将工具函数包装成模型能识别的格式 tools = [ { "type": "function", "function": { "name": "search_web", "description": "在互联网上搜索信息,回答实时性问题。", "parameters": { "type": "object", "properties": { "query": {"type": "string", "description": "搜索查询词"} }, "required": ["query"] } } }, { "type": "function", "function": { "name": "calculate", "description": "执行数学计算。", "parameters": { "type": "object", "properties": { "expression": {"type": "string", "description": "数学表达式,如 '3 + 5 * 2'"} }, "required": ["expression"] } } } ]3.2 构建核心ReAct循环引擎
接下来是重头戏:驱动循环的引擎。这个引擎负责管理对话历史(即Reason, Act, Observe的记录),并反复调用模型,直到任务完成。
def run_react_agent(user_query: str, max_steps: int = 10) -> str: """ 运行一个简单的ReAct Agent。 :param user_query: 用户初始问题 :param max_steps: 最大循环步数,防止无限循环 :return: Agent的最终回答 """ # 初始化对话历史,其中包含系统指令,用于设定Agent的角色和行为模式。 messages = [ { "role": "system", "content": """你是一个善于思考并能够使用工具的助手。请遵循以下步骤解决问题: 1. **思考(Reason)**:分析当前情况、目标和可用工具,决定下一步做什么。把你的思考过程用中文写在“思考:”后面。 2. **行动(Act)**:如果需要使用工具,严格按照JSON格式调用工具,格式为:{"action": "工具名", "action_input": {工具参数}}。 3. **观察(Observe)**:工具调用结果会以“观察:{结果}”的形式提供给你。 4. 重复1-3步,直到你认为可以给出最终答案。 5. 给出最终答案时,以“最终答案:”开头。 请严格按此格式响应。""" }, {"role": "user", "content": user_query} ] step = 0 while step < max_steps: step += 1 print(f"\n--- 第 {step} 步 ---") # 调用模型进行“推理”(Reason),并期望它可能决定“行动”(Act) response = client.chat.completions.create( model="gpt-4-turbo", # 或 gpt-3.5-turbo,但4的推理和工具调用能力更强 messages=messages, tools=tools, tool_choice="auto", # 由模型决定是否调用工具 ) response_message = response.choices[0].message # 将模型的响应(包含推理文本)添加到历史中 messages.append(response_message) # 检查模型是否决定调用工具(Act) tool_calls = response_message.tool_calls if tool_calls: # 模型决定行动,可能有多个工具调用(这里处理第一个) print(f"[推理] {response_message.content}") for tool_call in tool_calls: function_name = tool_call.function.name function_args = json.loads(tool_call.function.arguments) print(f"[行动] 调用工具: {function_name}, 参数: {function_args}") # 执行对应的工具函数 available_functions = {"search_web": search_web, "calculate": calculate} function_to_call = available_functions[function_name] action_result = function_to_call(**function_args) print(f"[观察] {action_result}") # 将工具执行结果(Observe)作为新的消息添加到历史中 messages.append({ "role": "tool", "tool_call_id": tool_call.id, "name": function_name, "content": action_result, # 观察结果 }) # 本轮结束,进入下一轮循环(模型将基于观察进行新的推理) else: # 模型没有调用工具,直接输出了内容,这可能是最终答案或纯推理 print(f"[推理/答案] {response_message.content}") if "最终答案:" in response_message.content: print("\n--- 任务完成 ---") return response_message.content # 如果不是最终答案,继续循环(例如模型只做了推理但没行动,这在实际中较少见,但循环会继续) return "达到最大步数,任务未完成。" # 运行示例 if __name__ == "__main__": final_answer = run_react_agent("北京明天天气怎么样?如果是晴天,推荐一个活动,并估算一下如果4个人参加这个活动,交通和餐饮大概需要多少预算?") print("\n最终输出:", final_answer)运行这段代码,你会在控制台看到一个清晰的ReAct循环过程。对于上面的复杂问题,模型可能会先推理需要天气,调用搜索工具(模拟),观察到“晴天”结果,然后推理需要推荐活动,再次调用搜索,再推理需要计算预算,调用计算器工具。每一步的“思考”和“行动”都清晰可见。
注意:上面的模拟搜索工具返回了固定结果。在真实场景中,你需要接入真实的搜索API,并处理好可能出现的网络错误、结果解析失败等情况。这就是ReAct模式展现优势的地方:当工具返回错误时,模型能观察到错误信息,并推理出重试或更换策略。
3.3 提示词工程的关键细节
系统提示词(System Prompt)是ReAct Agent的“大脑初始化指令”,其质量直接决定Agent的表现。除了上面示例中的基础结构,有几个关键点需要打磨:
- 强化格式遵从:必须明确要求模型按“思考:”、“行动:”、“最终答案:”的格式输出。可以使用更严格的描述,如“你的输出必须且只能包含以下三个部分之一:思考、工具调用、最终答案。”
- 设定思考深度:鼓励模型进行深度推理。例如,可以加入:“在思考步骤,请详细分析当前已知信息、待解决问题、可用工具的适用性,并解释为什么选择下一步行动。”
- 引入反思机制:在提示词中要求模型在得到观察后,不仅规划下一步,还要评估上一步行动的有效性。例如:“观察结果是否直接回答了问题?如果没有,缺失了什么信息?”
- 防止幻觉与循环:明确指令“严禁编造工具不提供的信息”、“如果你在连续三步中使用了相同工具且未获得进展,应尝试不同策略或给出当前已知的最佳答案。”
一个更健壮的提示词开头可以是:
你是一个严谨的AI助手,通过思考、行动、观察的循环解决问题。你的每次响应必须严格遵循以下结构: **思考:** [在此详细阐述你的分析过程。基于当前对话历史和观察,你现在知道什么?你的目标是什么?有哪些可用工具?你下一步计划做什么以及为什么?] **行动:** [仅当需要调用工具时填写。必须为严格的JSON格式:{"action": "tool_name", "action_input": {"param": "value"}}。如果不需要工具,则留空或写“无”。] **最终答案:** [当你拥有足够信息可以直接、准确回答用户原始问题时,在此给出完整答案。这是循环的终点。] 现在,开始处理第一个用户请求。4. 进阶实践:处理复杂任务与常见陷阱
一个基础的ReAct循环跑通后,你会很快遇到更现实的问题:任务可能非常复杂,需要多个工具协同;模型可能会陷入死循环;或者工具返回的结果模型无法正确理解。下面分享一些进阶实践和踩坑经验。
4.1 多工具协作与状态管理
现实任务很少只用一个工具。例如,“总结某公司最新财报并分析其股价影响”可能需要:1) 搜索工具获取财报新闻;2) 爬虫或API工具获取具体财报PDF;3) 文档解析工具提取文本;4) 总结分析工具(可能是另一个LLM调用)生成摘要;5) 金融数据工具查询历史股价。
这时,Agent的“推理”步骤就变得至关重要。它需要像一个项目经理,决定调用哪个工具、以什么顺序、以及如何将上一个工具的输出转化为下一个工具的输入。在我们的简单引擎中,所有历史消息都通过messages列表传递,这实际上就是Agent的“工作记忆”。但对于超长对话,需要警惕上下文长度限制。
解决方案:
- 关键信息摘要:在每一轮循环后,可以添加一个轻量级的“总结”步骤,将冗长的观察结果提炼成关键事实,再放入上下文。这可以是一个独立的提示词调用,要求模型:“请用一句话总结刚才观察到的核心信息。”
- 向量数据库存储:对于非常长的任务,可以将历史“观察”中的重要结果(如提取的数据、关键结论)存入向量数据库。当模型需要参考时,通过检索相关片段而非传入全部历史。
- 子任务分解:在初始推理时,就要求模型将复杂任务分解为清晰的子任务列表。然后ReAct循环可以围绕一个“子任务栈”进行,完成一个弹出下一个,使状态更清晰。
4.2 循环失控与停滞的应对策略
ReAct Agent最常见的两个问题是:1)无限循环:在两个工具间来回调用,无法推进;2)提前终止:模型过早地给出了不完整的“最终答案”。
无限循环的案例: 假设工具search_web有时返回“未找到相关信息”。Agent的推理可能是:“未找到信息,我需要换一个关键词再搜。”于是它再次调用search_web,可能又失败,陷入“搜索-失败-再搜索”的死循环。
应对策略:
- 设置最大步数:如示例代码中的
max_steps,这是最后的安全网。 - 在工具层面增加重试和降级逻辑:例如,
search_web工具内部可以尝试3种不同的查询句式,如果都失败,返回一个结构化的错误信息,如{"status": "error", "reason": "multiple_attempts_failed", "suggestion": "请尝试更具体或不同的关键词。"}。这样观察结果更丰富,有助于模型推理出新策略。 - 在提示词中引入“循环检测”指令:例如,“请保持对历史的关注。如果你发现最近三次行动的模式高度相似且未获得新信息,你应该停止当前策略,尝试一个完全不同的方法,或承认无法从此路径获得更多信息。”
- 实现一个简单的循环检测器:在引擎代码中,维护一个最近N步的行动历史列表。如果检测到完全相同的“推理-行动”对重复出现,则中断循环,并主动向对话历史中插入一条警告信息:“检测到可能循环,请重新评估策略。”
提前终止的案例: 用户问“写一份关于量子计算的行业报告”,模型可能搜索了一两篇文章后,就推理“已有足够信息”,开始生成一份非常肤浅的报告。
应对策略:
- 在系统提示中明确“完成标准”:例如,“只有当你能从至少三个独立可靠来源交叉验证核心信息,并已涵盖用户问题中的所有关键子主题时,才能给出最终答案。”
- 实施“答案质量检查”步骤:在模型输出“最终答案:”后,不立即返回。可以启动一个额外的“审查”步骤,用另一个提示词让模型(或另一个审查模型)评估答案的完整性、准确性。如果不合格,则将审查意见作为新的“观察”丢回主循环。
4.3 观察结果的解析与标准化
工具返回的结果(观察)五花八门,可能是JSON、HTML、纯文本或错误码。模型有时会错误解析这些结果。例如,一个返回HTML片段的搜索工具,模型可能误将<div>标签当作答案的一部分。
经验之谈:
- 工具设计应面向LLM:尽可能让工具返回结构清晰、简洁的文本或JSON。例如,网页搜索工具不应返回整个HTML,而应通过后端解析,返回格式如
[{"title": "...", "snippet": "...", "url": "..."}, ...]的标准化结果。 - 为观察结果添加元数据:在将观察插入消息历史时,可以包装一下。例如,不是直接插入
“晴天”,而是插入“[来自天气API] 查询成功。天气状况:晴天,温度22°C。可信度:高。”这为模型的推理提供了更多上下文。 - 处理工具错误:工具抛出的异常一定要被捕获,并以模型能理解的方式格式化后作为观察。例如,
“观察:[工具调用失败] 天气服务暂时不可用(HTTP 503)。建议:稍后重试或使用备用城市数据。”这比一个Python异常堆栈跟踪有用得多。
5. 从ReAct模式看AI Agent的发展层次
理解了ReAct,我们就能更好地定位目前市面上各种各样的Agent框架和概念。在我看来,AI Agent的能力可以粗略分为三个层次,ReAct是通往更高层次的基石。
第一层:基础工具调用(Function Calling)这是大多数LLM API现已支持的能力。模型根据提示词决定是否调用以及调用哪个用户定义的函数。它本质上是单次的“推理-行动”,缺少持续的、基于观察的循环。只能完成“一步到位”的简单任务,比如“计算一下156的平方根”。
第二层:规划与执行循环(ReAct模式)这就是本文讨论的核心。Agent具备了在较长序列中动态规划、执行、调整的能力。它解决了“多步骤”和“依赖外部状态”的任务。目前大多数自称的“Agent框架”(如LangChain的AgentExecutor、AutoGPT的早期核心)都是在这一层构建的。其上限取决于模型本身的规划推理能力和工具集的丰富程度。
第三层:长期记忆与技能学习这是当前的前沿探索方向。在这一层,Agent不仅能在一次会话中完成循环,还能将本次循环中学到的“经验”(例如,某种查询方式总能得到更好结果)存储到长期记忆中,并在未来的任务中复用。它甚至能通过分析自己的成功和失败案例,自动优化提示词或生成新的工具(技能)。Harness、Hermes等框架所探讨的“基础设施层”,正是为了支撑Agent向这一层演进,提供持久化存储、技能库管理、性能监控等能力。
ReAct模式是第二层的核心实现范式,也是通向第三层的必经之路。它让AI从静态的知识库,变成了一个能够主动探索、试错并解决问题的动态智能体。虽然当前的ReAct Agent还远未达到通用人工智能的水平,在复杂规划、长期一致性方面仍有局限,但它已经为自动化处理大量知识型工作流打开了大门。
在我自己的项目中,将客服工单分类、信息提取和初步回复的流程改造成ReAct Agent后,不仅准确率提升了,更重要的是,整个流程变得可解释、可调试。当出现错误时,我可以回溯完整的“思考-行动”链,精准定位是工具的问题、模型推理的偏差,还是提示词指令的模糊。这种透明度和可控性,是在生产环境中应用AI不可或缺的。
最后,一个实用的建议:当你开始设计自己的Agent时,不要一开始就追求全自动和复杂。从一个定义清晰、边界明确的小任务开始,精心设计一两个核心工具,打磨好系统提示词,跑通一个稳定的ReAct循环。这个最小可行产品(MVP)所带来的理解,远比直接套用一个复杂框架要深刻得多。先让Agent可靠地完成一件小事,再思考如何让它去做更多。
