拓冰建站拓冰建站
首页 / 资讯中心 / 正文

ReAct智能体:从原理到实践,构建能思考会行动的大模型应用

1. 从“指令-执行”到“思考-行动”的范式跃迁如果你最近在折腾大语言模型应用尤其是想让它帮你完成一些稍微复杂点的任务比如分析一份财报、规划一次旅行或者调试一段代码你可能会发现一个尴尬的现象你给AI一个明确的指令它要么直接给出一个看似正确但经不起推敲的答案要么在执行多步任务时中途“卡壳”或者“跑偏”完全忘了最初的目标。这背后的核心问题是传统的大语言模型应用模式本质上是一种“指令-执行”的单步模式。模型接收你的问题基于其庞大的知识库生成一个回答这个过程缺乏一个内在的、持续的“思考”和“规划”回路。而ReAct智能体正是为了解决这个问题而生的。它不是一个具体的工具或SDK而是一种让大语言模型“学会”如何像人类一样解决问题的框架范式。ReAct这个名字拆开来看就是Reasoning推理和Acting行动。它的核心思想是让AI在面对复杂问题时不是急于给出最终答案而是先停下来“想一想”Reasoning规划出下一步该做什么、用什么工具做然后去“做一做”Acting执行这个动作并根据执行的结果再次进行“思考”决定后续步骤如此循环直到问题解决。这听起来是不是很像我们人类解决问题的方式比如当被问到“北京今天天气如何适合去故宫游玩吗”一个ReAct智能体的思考过程可能是推理要回答这个问题我需要知道北京今天的天气情况以及故宫的开放信息。行动调用“天气查询API”获取北京今天的温度、降水、风力等数据。观察API返回“北京晴25°C微风”。推理天气很好。但还需要确认故宫是否开放以及开放时间。行动调用“景点信息查询工具”或进行网络搜索获取故宫的开放状态。观察工具返回“故宫博物院今日正常开放开放时间8:30-17:00”。推理天气适宜景点开放。综合来看今天非常适合去故宫游玩。可以补充建议如注意防晒、提前预约等。最终回答生成最终答案。这个“思考-行动-观察”的循环就是ReAct智能体的灵魂。它让AI从被动的知识应答机变成了一个能主动使用工具、与环境交互、并基于反馈调整策略的“智能体”。这不仅仅是功能的增强更是一种能力的质变它打开了通向更复杂、更自主AI应用的大门。2. ReAct的核心工作循环拆解“思考”与“行动”的每一个齿轮要真正理解并应用ReAct我们不能停留在概念层面必须深入到其工作机制的每一个细节。这个循环并非魔法而是一套设计精巧的、可工程化的流程。下面我们来彻底拆解这个循环中的三个关键齿轮推理、行动和观察。2.1 推理不止是“想”更是“结构化地想”在ReAct中“推理”远不止是模型内部的知识联想。它是一个被明确要求、并被结构化的输出环节。其核心目标是根据当前的任务目标、已有的历史信息包括之前的推理、行动和观察结果决定下一步的最佳行动方案。这个“决定”通常需要输出两部分信息Thought思考用自然语言描述当前的思考过程。例如“用户想了解北京的天气。我目前没有任何信息所以我需要先查询天气。”Action动作指令一个结构化的指令明确指定要调用哪个工具以及传入什么参数。格式通常是Action: 工具名称[输入参数]。例如Action: Search[北京今日天气]这里的“思考”环节至关重要。它有两个作用第一让人可以理解智能体的决策过程便于调试和信任第二也是更重要的它强制模型进行链式思考Chain-of-Thought。将思考过程“说”出来能显著提升模型规划的逻辑性和准确性。在实际开发中我们会在给模型的系统提示System Prompt中明确规定输出的格式例如你必须按照以下格式回应 Thought: 描述你当前的思考过程 Action: 要执行的动作格式为 Action: 工具名[输入] ...执行后你会收到Observation Observation: 行动的结果 然后你开始新的Thought-Action循环 最终答案当任务完成时直接给出最终答案。2.2 行动智能体的“手”和“脚”“行动”是智能体与外部世界交互的唯一方式。这里的“行动”通常被具体化为调用一个预先定义好的“工具”。工具是什么工具就是一个个封装好的函数或API它们扩展了语言模型本身的能力边界。常见的工具类型包括搜索工具调用搜索引擎API获取最新或模型训练数据之外的信息。计算工具执行数学运算、单位换算等。代码执行器运行一段代码并返回结果常用于数据分析和处理。API调用工具查询天气、股票、翻译、数据库等。文件操作工具读取、写入本地文件。工具的设计与管理是关键。你需要为智能体提供一个“工具包”。在每次“推理”阶段模型需要知道它“手头有哪些工具可用”。因此我们必须在提示中清晰地描述每个工具的功能、输入格式和输出示例。一个设计良好的工具描述应该像一份简洁的API文档。例如定义搜索工具工具名称search 描述使用搜索引擎查询网络信息。当你需要获取实时信息、事实核查或未知领域知识时使用此工具。 输入一个明确的搜索查询字符串。 示例Action: search[特斯拉2024年第一季度财报]2.3 观察闭环反馈与动态调整“观察”是行动执行后返回的结果。它是智能体进行下一轮“推理”的唯一依据。没有准确、结构化的观察智能体就会变成“盲人摸象”很容易迷失方向。观察的处理需要注意以下几点信息完整性观察结果应尽可能包含所有相关信息但也要避免过于冗长。有时需要对原始API返回的JSON数据进行精简和格式化再提供给模型。错误处理当工具调用失败如网络超时、API限流、参数错误时观察结果不能只是一个错误码而应该是一个对模型友好的、能指导其下一步行动的自然语言描述。例如Observation: 搜索工具暂时不可用可能是网络问题。请稍后再试或者如果你有离线知识请基于已知信息回答。上下文长度管理ReAct的对话历史Thought-Action-Observation循环会不断增长必须注意不要超过模型的最大上下文长度。常见的策略是只保留最近几轮的循环或者对历史进行选择性摘要。这个“推理-行动-观察”的循环会一直持续直到模型在“推理”阶段判断任务已经完成然后输出“最终答案”并终止循环。整个流程就像一个拥有“内驱力”的程序由大语言模型这个“大脑”驱动通过工具这个“身体”去感知和改变环境。3. 从零构建一个ReAct智能体以“旅行规划助手”为例理解了原理我们动手实现一个。假设我们要构建一个“旅行规划助手”智能体它能根据用户的需求如目的地、时间、预算、兴趣自动搜索信息并生成一份详细的行程计划。我们将使用Python和OpenAI的API或其他兼容API的大模型来演示核心流程。3.1 第一步定义智能体的“工具包”这是最基础也最重要的一步。我们的智能体需要哪些“手”和“脚”# 首先我们模拟几个工具函数。在实际项目中这些函数会封装真实的API调用。 import json import random from datetime import datetime, timedelta def search_web(query: str) - str: 模拟网络搜索工具 # 这里应该调用SerpAPI、Google Search API等 # 为演示我们返回模拟数据 mock_data { 巴黎三日游: 埃菲尔铁塔开放时间9:30-23:45卢浮宫需提前官网预约塞纳河游船晚餐很受欢迎。, 东京天气: 东京近期天气晴朗气温15-22度适宜出行。, 平价东京酒店: 浅草区域有多家性价比高的商务酒店如Hotel MyStays每晚约500元人民币。, 京都寺庙推荐: 清水寺、金阁寺、伏见稻荷大社是必去景点岚山竹林小径也很美。 } return mock_data.get(query, f未找到关于{query}的详细信息。) def calculate_budget(days: int, daily_budget: float) - dict: 模拟预算计算工具 total days * daily_budget breakdown { 住宿: total * 0.4, 餐饮: total * 0.3, 交通门票: total * 0.2, 购物应急: total * 0.1 } return {总预算: total, 预算分配: breakdown} def get_current_date() - str: 获取当前日期工具 return datetime.now().strftime(%Y-%m-%d) # 将工具封装成智能体能理解的格式 tools [ { name: search, description: 当需要获取某个地点的旅游信息、景点开放时间、天气、酒店评价等实时或最新信息时使用此工具。, parameters: {type: object, properties: {query: {type: string}}}, func: search_web }, { name: calculate_budget, description: 根据旅行天数和每日预算计算总预算并进行合理分配。, parameters: {type: object, properties: {days: {type: integer}, daily_budget: {type: number}}}, func: calculate_budget }, { name: get_date, description: 获取当前日期用于规划行程时间。, parameters: {type: object, properties: {}}, func: get_current_date } ] # 生成工具描述文本用于插入系统提示 def get_tools_description(): desc [] for tool in tools: param_desc , .join([f{k} for k in tool[parameters][properties].keys()]) desc.append(f- {tool[name]}: {tool[description]} 输入格式: {tool[name]}[{param_desc}]) return \n.join(desc)3.2 第二步构建系统提示与ReAct循环引擎系统提示是智能体的“宪法”它定义了行为准则、工具使用方法和输出格式。import openai # 假设使用OpenAI也可替换为其他兼容库 def build_system_prompt(): tools_desc get_tools_description() prompt f 你是一个专业的旅行规划助手。你的目标是根据用户的需求规划一份详细、可行的旅行行程。 你拥有以下工具 {tools_desc} 你必须严格按照以下格式进行回应 1. 首先进行思考Thought分析当前情况和下一步计划。 2. 然后决定是否使用工具Action。如果使用格式必须为 Action: 工具名[输入参数]。输入参数应为简单的字符串或数字多个参数用逗号隔开。 3. 你将收到工具的返回结果Observation。 4. 基于观察结果开始新的思考-行动循环。 5. 当你认为已经收集到足够信息可以生成最终旅行计划时直接以“最终答案”开头输出你的计划。 现在开始与用户对话。用户的需求是{{user_query}} 记住一次只执行一个动作。 return prompt def parse_model_response(response): 解析模型的回复提取Thought和Action thought action None lines response.strip().split(\n) for line in lines: if line.startswith(Thought:): thought line.replace(Thought:, ).strip() elif line.startswith(Action:): action_str line.replace(Action:, ).strip() # 简单解析 Action: search[巴黎三日游] if [ in action_str and ] in action_str: tool_name action_str.split([)[0].strip() input_str action_str.split([)[1].rstrip(]) action {tool: tool_name, input: input_str} return thought, action def run_react_agent(user_query, max_turns8): 执行ReAct循环 system_prompt build_system_prompt().format(user_queryuser_query) messages [{role: system, content: system_prompt}] history [] # 记录循环历史便于调试 final_answer None for turn in range(max_turns): # 调用大模型 response openai.ChatCompletion.create( modelgpt-3.5-turbo, # 或 gpt-4 messagesmessages, temperature0.1, # 低温度保证输出格式稳定 max_tokens500 ) model_reply response.choices[0].message.content print(f\n--- Turn {turn1} ---) print(f模型回复:\n{model_reply}) # 检查是否已给出最终答案 if 最终答案 in model_reply: final_answer model_reply.split(最终答案)[-1].strip() print(f\n任务完成最终答案已生成。) break # 解析回复 thought, action parse_model_response(model_reply) history.append({thought: thought, action: action}) if not action: print(未解析出有效Action可能格式错误或模型直接给出了答案。) # 可以选择将模型回复作为观察继续或者结束循环 obs model_reply else: # 执行工具调用 tool_to_use None for tool in tools: if tool[name] action[tool]: tool_to_use tool break if tool_to_use: # 这里需要根据工具参数定义更复杂的参数解析本例简单处理 try: # 对于search工具input就是查询字符串 if tool_to_use[name] search: result tool_to_use[func](action[input]) elif tool_to_use[name] calculate_budget: # 假设输入是“3, 1000” params [p.strip() for p in action[input].split(,)] result tool_to_use[func](int(params[0]), float(params[1])) result json.dumps(result, ensure_asciiFalse) elif tool_to_use[name] get_date: result tool_to_use[func]() else: result f工具 {action[tool]} 执行成功模拟。 obs fObservation: {result} except Exception as e: obs fObservation: 调用工具 {action[tool]} 时出错{str(e)} else: obs fObservation: 未知的工具 {action[tool]}。请使用提供的工具列表中的工具。 print(f观察结果: {obs}) # 将观察结果添加到对话历史驱动下一轮循环 messages.append({role: assistant, content: model_reply}) messages.append({role: user, content: obs}) # 将Observation作为用户输入反馈给模型 if not final_answer and turn max_turns - 1: final_answer 达到最大循环次数未能完成规划。 return final_answer, history # 运行示例 if __name__ __main__: # 需要先设置 openai.api_key user_request 我想规划一次为期3天的巴黎旅行总预算控制在5000元人民币左右。 answer, hist run_react_agent(user_request) print(f\n 最终旅行计划 \n{answer}) print(f\n 智能体思考过程 ) for h in hist: print(f思考: {h[thought]}) print(f行动: {h[action]})3.3 第三步运行、调试与关键细节剖析运行上面的代码你会看到智能体一步步“思考”和“行动”的过程。这个过程可能不会一帆风顺这正是我们需要关注的关键点1. 提示工程是成败的关键系统提示词的质量直接决定了智能体是否“听话”。你需要清晰地界定角色与目标明确告诉模型“你是谁”、“你要干什么”。工具使用规范工具描述要准确输入输出示例要清晰。模糊的描述会导致模型错误调用。输出格式强制必须严格要求Thought:和Action:的格式。模型有时会“偷懒”直接输出答案或使用非指定格式。可以在提示词中加入“你必须严格按照此格式回应否则任务将失败”等强约束语句。停止条件明确告知模型何时输出“最终答案”。可以是通过自然语言描述也可以是通过示例演示。2. 错误处理与鲁棒性我们的示例代码错误处理很简单。在生产环境中你必须考虑工具调用失败网络错误、API限流、无效输入等。观察结果应能引导模型重试或调整策略。模型输出格式错误模型可能不按格式输出。你的解析函数需要足够健壮能处理各种意外情况并给出纠正性的观察反馈如“Observation: 你的回复格式不正确。请确保以‘Thought:’开始你的思考如果需要行动请使用‘Action: 工具名[输入]’的格式。”循环失控智能体可能陷入死循环例如反复搜索同一个词。需要设置最大循环次数如max_turns10并在提示词中鼓励其高效完成任务。3. 上下文管理与长程规划随着循环进行对话历史会越来越长。这有两个问题一是可能超出模型上下文窗口二是无关历史可能干扰当前决策。策略包括选择性摘要只保留最近几轮如3轮完整的Thought-Action-Observation将更早的历史总结成一段摘要如“之前我们已经查询了巴黎的主要景点和天气”。外部记忆使用向量数据库等存储所有历史记录在每一步只将与当前思考最相关的几条历史检索出来放入上下文。4. 工具设计的艺术工具并非越多越好。工具设计应遵循“高内聚、低耦合”原则功能明确一个工具只做一件事。不要设计一个get_travel_info的工具而应该拆成search_attractions、check_weather、find_hotels。输入简单尽量让输入参数是简单的字符串、数字或列表。复杂的JSON结构会增加模型调用错误的概率。输出干净工具的返回结果应该信息完整且格式整洁便于模型阅读理解。避免返回带有大量无关HTML标签或复杂嵌套JSON的数据。通过这个“旅行规划助手”的实战你应该能感受到构建一个ReAct智能体更像是在设计一个“人机协同”的系统。你作为架构师负责搭建舞台定义工具、编写提示、制定规则输出格式、循环逻辑而大语言模型则是台上的主角负责具体的推理和决策。两者的配合至关重要。4. 超越基础循环高级模式与实战避坑指南当你成功跑通一个基础的ReAct智能体后可能会遇到更复杂的场景和挑战。本节我们来探讨一些高级模式和那些只有踩过坑才知道的细节。4.1 模式一动态工具检索与调用在基础示例中我们在系统提示里写死了所有工具描述。但当工具数量成百上千时例如企业内部有大量API这会导致提示词过长且每次调用模型都要处理无关工具信息效率低下。解决方案是动态工具检索。其工作流程是用户提出问题。模型首先根据问题生成一个对所需工具的“描述”或“查询”。系统根据这个描述从一个工具向量数据库中检索出最相关的几个工具例如使用嵌入模型计算相似度。只把这几个相关工具的描述放入当前提示词中让模型进行后续的ReAct循环。# 伪代码示例 def retrieve_relevant_tools(user_query, all_tools, top_k3): # 1. 将用户查询和所有工具描述转换为向量 query_embedding get_embedding(user_query) tool_embeddings [get_embedding(tool[description]) for tool in all_tools] # 2. 计算余弦相似度 similarities calculate_cosine_similarity(query_embedding, tool_embeddings) # 3. 返回最相关的top_k个工具 relevant_indices np.argsort(similarities)[-top_k:][::-1] return [all_tools[i] for i in relevant_indices] # 在构建系统提示时不再使用所有工具而是使用检索到的工具 relevant_tools retrieve_relevant_tools(user_query, all_tools_list) system_prompt build_system_prompt_with_tools(relevant_tools)这种方式大大提升了智能体在庞大工具集中的操作效率和准确性。4.2 模式二子任务分解与协同有些复杂任务无法在一个线性循环中解决。例如“为公司下周的团队建设活动制定一个方案包括场地预订、活动安排、餐饮和预算”。这需要并行或先后处理多个子任务。此时可以引入“规划-执行”层。让一个“主智能体”先进行高层规划将任务分解为几个独立的子任务如“1. 寻找场地”、“2. 设计活动”、“3. 安排餐饮”、“4. 制定预算”。然后它可以为每个子任务启动一个独立的“子智能体”一个ReAct循环或者自己按顺序执行这些子任务。每个子任务都可以有自己的工具集和目标。# 主智能体的“思考”可能输出 Thought: 这是一个复杂的团队建设规划任务我需要将其分解。 Action: decompose_task[为公司下周的团队建设活动制定一个方案包括场地预订、活动安排、餐饮和预算] Observation: 任务已分解为1. 寻找合适场地2. 设计具体活动流程3. 确定餐饮方案4. 编制详细预算。 Thought: 现在我先处理第一个子任务寻找合适场地。 Action: search[北京 适合20人团队建设 场地 下周可用] ...这种模式对模型的规划能力要求更高通常需要性能更强的模型如GPT-4。4.3 避坑指南那些我踩过的“坑”坑一模型的“格式叛逆”即使提示词写得再清楚模型偶尔也会不按格式输出比如把Action: search[xxx]写成I will use the search tool for xxx。应对策略在解析响应时除了严格的格式匹配可以加入一些启发式规则或使用一个轻量级模型如gpt-3.5-turbo专门进行格式校验和纠正。也可以在观察中给予惩罚性反馈如“Observation: 你未使用规定的Action格式。请重试。”坑二工具描述的“语义歧义”如果你有两个工具get_user_info(id)和get_user_profile(name)模型可能无法准确区分何时用id查询何时用name查询。应对策略工具描述要极度精确。例如“get_user_info: 通过用户的唯一数据库ID获取其基本信息。输入必须是数字ID。” “get_user_profile: 通过用户的姓名全名查找其公开资料。输入为字符串。”坑三无限循环与原地打转智能体可能反复执行同一个无意义的动作比如不停地搜索同一个关键词或者在一个子问题上纠结不休。应对策略设置硬性限制如最大循环次数max_turns。在提示词中鼓励“完结”明确告诉模型“如果你认为当前信息已足够回答问题请果断给出最终答案”。实现循环检测在代码中记录历史动作如果检测到最近三次动作完全相同或高度相似则在观察中强制中断“Observation: 检测到重复动作这可能陷入了死循环。请重新评估你的计划或尝试不同的方法。”坑四复杂参数的处理难题当工具需要复杂对象作为输入时如一个包含多个字段的JSON让模型直接生成正确的JSON字符串非常困难容易格式错误。应对策略不要让模型直接输出JSON。改为让模型输出自然语言描述然后由你编写的一个中间解析层来将描述转换为结构化参数。例如模型输出Action: book_flight[从北京到上海明天出发经济舱]你的代码解析这个字符串提取出origin,destination,date,class等字段再调用真正的预订API。坑五观察结果的“信息过载”工具尤其是搜索工具返回的结果可能非常冗长包含大量无关信息。这些噪声会干扰模型的后续推理。应对策略在将观察结果返回给模型前先进行信息提炼。可以用一个小模型或规则对原始结果进行摘要只保留与当前任务最相关的核心信息。例如搜索“巴黎天气”返回了包含过去一周数据的整个页面你可以提炼为“Observation: 巴黎未来三天天气预报晴气温18-25°C风力2-3级。”5. ReAct智能体的应用场景与未来展望ReAct框架的价值在于其通用性。它本质上提供了一种将大语言模型的推理能力与外部工具的执行能力相结合的标准化范式。因此其应用场景几乎遍布所有需要多步骤、有条件判断和外部交互的领域。1. 复杂信息处理与报告生成金融分析输入“分析一下特斯拉和比亚迪最近一个季度的财报对比他们的营收增长和研发投入”。智能体可以自动搜索最新财报、提取关键数据、进行计算对比最后生成一份结构化的分析报告。市场调研输入“调研一下2024年国内智能家居音箱的市场趋势和主要玩家”。智能体可以遍历行业报告、新闻、电商评论汇总出趋势、份额、产品优缺点等信息。2. 自动化工作流与运维IT运维当监控系统报警“服务器CPU使用率超过95%”智能体可以自动执行排查流程先登录服务器检查进程Action: ssh_execute[top -bn1]发现是某个Java应用异常然后查询知识库获取重启指令Action: search_knowledge_base[应用XXX重启流程]最后执行重启Action: ssh_execute[systemctl restart appXXX]并将整个过程记录为工单。客户支持用户描述一个产品问题智能体可以引导用户提供信息、查询知识库、甚至运行诊断脚本最终给出解决方案或自动创建故障单。3. 创意与内容创作的副驾驶视频脚本创作输入“为一个介绍ReAct智能体的科普视频写一个分镜脚本时长5分钟”。智能体可以先搜索优秀的科普视频结构Action: search[科普视频 分镜脚本 结构]然后根据结构结合ReAct的知识一步步生成开场白、概念讲解、示例演示、总结等各部分的具体文案和视觉建议。游戏内容生成在游戏中NPC可以根据与玩家的对话动态规划行为。比如玩家问“城里的铁匠铺在哪”NPC的ReAct循环可能是思考玩家需要方向- 行动查询内部地图数据库- 观察铁匠铺位于主广场东侧- 最终回答给出详细路线。未来的挑战与演进方向从我实际的开发体验来看ReAct目前还不是一个“开箱即用”的解决方案它更像一个强大的“原型框架”。要使其稳定可靠地运行需要大量的工程优化包括但不限于更精准的工具检索、更鲁棒的对话状态管理、对长上下文更高效的利用、以及针对特定领域的提示词微调。一个重要的演进方向是“智能体即平台”。未来可能会出现更成熟的智能体开发框架它们将ReAct的循环、工具管理、记忆、规划等核心组件标准化、模块化开发者只需像搭积木一样配置工具和业务逻辑就能快速构建出强大的智能体应用。同时多智能体协作也是一个充满想象力的方向让多个具备不同专长的智能体相互对话、分工合作共同解决超级复杂的问题。构建ReAct智能体的过程是一个不断与模型“对齐”和“博弈”的过程。你需要理解它的思维模式引导它约束它同时也为它的“创造力”留下空间。当看到一个智能体从零开始通过自己的“思考”和“行动”一步步解决掉你抛给它的复杂问题时那种感觉就像在教一个数字生命如何认识世界和改造世界这其中的乐趣和挑战远超简单的API调用。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门