尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从ReAct模式到智能体实战:构建动态思考的AI Agent

从ReAct模式到智能体实战:构建动态思考的AI Agent 1. 从“单步调用”到“思考-行动”的范式转变最近在搞AI应用落地的朋友估计没少被“Agent”这个词刷屏。从OpenAI的GPTs到各种开源框架好像不提Agent技术方案就落伍了。但说实话很多初涉此道的开发者包括我早期也一样对Agent的理解容易停留在“能调用工具的ChatGPT”这个层面。我们习惯性地把大模型当成一个更聪明的函数给它一个指令它返回一个结果顶多中间加个工具调用。这种“单步调用”模式在处理简单、明确的任务时没问题但一旦任务变得复杂、模糊需要多步决策和试错时就立刻捉襟见肘了。这就是为什么我们需要深入理解像ReActReasoning Acting这样的工作模式。它不是一个具体的框架或工具而是一种让AI智能体Agent真正“动起来”的思维框架。简单说ReAct的核心思想是让Agent模仿人类解决问题的方式先思考Reasoning再行动Acting根据行动结果再思考如此循环。这听起来很自然但在工程实现上却和传统的“输入-输出”或“规划-执行”流水线有本质区别。我最初在项目里尝试引入ReAct时就踩了不少坑比如Agent陷入无限循环的“空想”或者行动步骤杂乱无章。后来经过多次迭代才慢慢摸清门道。今天我就结合自己的实战经验抛开那些高大上的概念聊聊如何把一个ReAct模式的Agent从理论落地到代码特别是那些在官方教程里不会细说的“魔鬼细节”。无论你是想给自己的聊天机器人增加复杂任务处理能力还是构建一个自动化的工作流引擎理解ReAct都至关重要。2. ReAct模式的核心机理不只是“链式思考”在深入代码之前我们必须先打破一个误区ReAct不等于CoTChain-of-Thought思维链加上工具调用。虽然表面上看都是让模型输出一段思考过程但它们的驱动逻辑和结构设计有根本不同。2.1 与CoT和传统规划执行模式的本质区别CoT更侧重于“解释性”。它的主要目的是通过让模型展示推理步骤来提升最终答案的准确性和可解释性。它的输出终点通常是一个结论或答案。而ReAct模式中“思考”的终点是指导一个具体的、可执行的“行动”。这个行动会改变环境比如查询了数据库、调用了API并产生新的观察Observation而这个观察又会成为下一轮思考的输入。传统的“规划-执行”模式Plan-and-Execute则通常是分离的。Agent先制定一个完整的计划比如1.搜索A2.分析B3.合成C然后按顺序执行。这种模式的缺点是计划缺乏弹性一旦某一步执行结果与预期不符比如搜索不到A整个计划可能就失效了。ReAct的“思考-行动”循环是交织的、动态的它允许Agent根据上一步的结果实时调整后续策略适应性更强。用一个生活化的类比CoT像是你在心里默默盘算今晚做什么菜最后得出“做番茄炒蛋”的结论。传统规划像是你写下一张完整的购物清单和烹饪步骤表然后严格按表操作。而ReAct则是你站在厨房里打开冰箱观察发现没有番茄观察结果于是你思考“没有番茄可以用什么代替也许炒个葱花鸡蛋”思考然后去拿鸡蛋行动。整个过程是动态交互的。2.2 ReAct循环的标准化结构一个标准的ReAct循环其提示词Prompt和输出解析需要遵循一个严格的格式这是工程实现的关键。通常我们要求模型的输出严格包含以下几个部分Thought: [基于当前任务和已知信息的推理过程。这里需要分析现状评估可选行动并决定下一步做什么。] Action: [要执行的具体操作名称如 Search, Calculator, Lookup。] Action Input: [执行上述操作所需的输入参数通常是一个字符串或字典。]当模型输出上述内容后系统或一个执行器会解析出Action和Action Input去调用对应的工具Tool或函数Function。工具执行后会返回一个结果这个结果会被格式化为Observation: [工具执行后返回的结果或信息。]然后这个Observation会和之前的上下文一起再次作为输入喂给模型开启下一轮“Thought-Action”循环。直到模型认为任务已经完成它会输出Thought: [我已经获得了所有必要信息可以给出最终答案了。] Final Answer: [对用户原始问题的完整回答。]这个结构看似简单但要让它稳定工作提示词的设计、工具的封装、历史上下文的管理每一个环节都有讲究。很多开源框架如LangChain、LlamaIndex提供了ReAct的抽象但如果不理解其底层逻辑一旦出问题就很难调试。3. 实战构建一个能联网查询的天气旅行顾问Agent光说不练假把式。我们来实现一个具体的Agent一个天气旅行顾问。它的任务是根据用户提供的城市和日期查询该地天气并结合天气情况给出旅行建议如是否适合出行、需要带什么。这需要多步操作先解析用户意图再调用天气API获取数据最后综合分析给出建议。3.1 环境准备与工具定义首先我们选择Python环境并使用LangChain框架来简化流程。LangChain的AgentExecutor和create_react_agent函数封装了ReAct的大部分繁琐逻辑。但请注意我这里会尽量拆解其内部过程以便你理解本质。pip install langchain langchain-openai requests假设我们使用OpenAI的模型如gpt-3.5-turbo并有一个免费的天气API例如openweathermap。我们需要先定义两个核心工具获取天气工具 (get_current_weather)调用外部API。日期解析工具 (parse_date)将用户说的“明天”、“下周六”转换成具体日期。虽然大模型本身能理解但用一个专用工具来保证格式统一是更工程化的做法。import os from datetime import datetime, timedelta import requests from langchain.tools import tool # 工具1获取天气 tool def get_current_weather(city: str, date: str) - str: 根据城市名和日期格式YYYY-MM-DD查询天气预报。 返回一个包含天气状况、温度、湿度等信息的字符串。 # 这里需要替换成真实的API Key和端点 api_key os.getenv(WEATHER_API_KEY) # 注意很多免费天气API只提供当前天气这里假设我们的API支持未来日期 url fhttp://api.weatherapi.com/v1/forecast.json?key{api_key}q{city}dt{date} try: response requests.get(url) data response.json() # 简化处理实际需解析具体字段 forecast data[forecast][forecastday][0][day] condition forecast[condition][text] max_temp forecast[maxtemp_c] min_temp forecast[mintemp_c] humidity forecast[avghumidity] return f{date} {city}的天气{condition}最高温度{max_temp}°C最低温度{min_temp}°C平均湿度{humidity}%。 except Exception as e: return f查询天气失败{str(e)} # 工具2解析日期 tool def parse_date(human_date: str) - str: 将人类可读的日期描述如“明天”、“下周一”转换为标准格式 YYYY-MM-DD。 today datetime.now() human_date human_date.lower().strip() if human_date 今天: return today.strftime(%Y-%m-%d) elif human_date 明天: return (today timedelta(days1)).strftime(%Y-%m-%d) elif human_date 后天: return (today timedelta(days2)).strftime(%Y-%m-%d) # 这里可以扩展更多规则或者使用更强大的日期解析库如dateutil else: # 如果无法解析返回原字符串让LLM在Thought中处理或提示用户 return human_date关键点1工具描述的清晰性。tool装饰器下的函数文档字符串docstring至关重要LangChain会将这个描述注入到给模型的提示词中模型依靠这个描述来决定何时以及如何使用该工具。描述必须清晰说明功能、输入参数和输出格式。关键点2工具的健壮性。工具函数内部必须有完善的错误处理try-except并返回明确的错误信息。因为Observation会直接反馈给模型一个“查询失败”的Observation能让模型调整策略例如重试或提示用户而一个崩溃的工具则会直接导致Agent执行中断。3.2 构造ReAct提示词与Agent执行流程接下来是核心部分组装提示词并创建Agent。LangChain提供了create_react_agent函数但了解其内部构建的提示词模板能让你更有掌控力。from langchain import hub from langchain.agents import AgentExecutor, create_react_agent from langchain_openai import ChatOpenAI from langchain.memory import ConversationBufferMemory # 1. 初始化LLM llm ChatOpenAI(modelgpt-3.5-turbo, temperature0, openai_api_keyos.getenv(OPENAI_API_KEY)) # 2. 定义工具列表 tools [get_current_weather, parse_date] # 3. 获取ReAct提示词模板 # LangChain Hub上维护了一个标准的ReAct提示词模板 prompt hub.pull(hwchase17/react) # 4. 创建Agent agent create_react_agent(llm, tools, prompt) # 5. 创建Agent执行器并传入记忆用于多轮对话 memory ConversationBufferMemory(memory_keychat_history, return_messagesTrue) agent_executor AgentExecutor( agentagent, toolstools, memorymemory, verboseTrue, # 开启详细日志方便调试 handle_parsing_errorsTrue, # 关键处理模型输出格式错误 max_iterations5, # 防止无限循环 early_stopping_methodgenerate # 当模型输出Final Answer时停止 )现在让我们看看这个提示词模板hwchase17/react大概长什么样。理解它你才能知道模型是如何被引导的Answer the following questions as best you can. You have access to the following tools: {tool_descriptions} Use the following format: Question: the input question you must answer Thought: you should always think about what to do Action: the action to take, should be one of [{tool_names}] Action Input: the input to the action Observation: the result of the action ... (this Thought/Action/Action Input/Observation can repeat N times) Thought: I now know the final answer Final Answer: the final answer to the original input question Begin! Question: {input} Thought:这个模板做了几件关键事明确指令和工具告诉模型你的角色、可用工具及其描述。强制格式化输出严格规定了Thought/Action/Action Input/Observation的格式。提供示例结构展示了循环过程如何开始和结束。当我们运行agent_executor.invoke({input: 我下周末想去杭州玩天气怎么样适合穿什么})时verboseTrue会让我们看到模型内部的思考过程 Entering new AgentExecutor chain... Thought: 用户想知道下周末杭州的天气并根据天气给出穿衣建议。首先我需要明确“下周末”的具体日期。今天日期是2023-10-27下周末大概是11月4-5日。我需要先解析这个日期。 Action: parse_date Action Input: 下周末 Observation: 下周末 # 注意我们的parse_date工具无法处理“下周末”原样返回了。 Thought: 工具没有成功解析日期。我需要更精确地定义日期。或者我可以先查询今天再推算出周末。但更直接的方法是我可以假设用户指的是11月4日周六并基于此查询天气。如果用户有异议可以再澄清。 Action: get_current_weather Action Input: {city: 杭州, date: 2023-11-04} Observation: 2023-11-04 杭州的天气多云最高温度22°C最低温度15°C平均湿度65%。 Thought: 我已经获得了11月4日杭州的天气信息。现在需要结合这个天气给出穿衣建议。最高22度最低15度多云湿度适中。这种天气比较舒适早晚温差稍大。 Final Answer: 根据预报下周六11月4日杭州天气多云气温在15°C到22°C之间湿度65%。这种天气比较舒适适合出行。建议穿着白天一件长袖T恤或薄衬衫搭配外套或风衣以备傍晚降温。鞋子选择舒适的运动鞋或休闲鞋即可。由于是多云天气建议携带遮阳帽或太阳镜。3.3 从执行日志中诊断与优化上面的执行日志暴露了几个典型问题也是我们实战中必须处理的工具能力不足我们的parse_date工具太简单无法处理“下周末”这样的复杂描述。这导致模型第一次行动得到了一个无用的Observation。优化方案有两种一是增强工具本身集成更强大的日期解析库如dateutil.parser二是依靠模型的推理能力在Thought中直接推算日期并格式化成YYYY-MM-DD然后直接调用天气工具。对于复杂日期后者往往更灵活。模型的“妥协”策略当工具解析失败后模型在第二次Thought中展示了它的“应急处理”能力它没有死磕而是基于常识“下周末大概是11月4-5日”假设了一个具体日期继续执行。这是一个很好的ReAct特性——根据观察动态调整计划。但这也带来了不确定性。为了更可靠我们可以在提示词中加强引导例如“如果你无法确定具体日期请向用户询问澄清。”最终答案的生成模型在得到天气Observation后并没有再次调用任何工具而是直接生成了最终答案。这是因为在Thought中它判断已有足够信息。最终答案的质量依赖于模型本身的常识和指令遵循能力。我们可以通过优化系统提示词来提升建议的专业性例如“请以专业旅行顾问的口吻提供具体、可操作的穿衣和出行建议。”4. 避坑指南让ReAct Agent稳定工作的关键细节构建一个能跑通的Demo不难但要让ReAct Agent在生产环境中稳定、可靠地工作以下这些坑你必须提前知道。4.1 无限循环与最大迭代次数这是新手最常遇到的问题。Agent可能陷入“思考-行动”的死循环。常见原因有工具返回无效信息例如搜索工具总是返回“未找到结果”模型不断尝试新的搜索词。任务本身无法完成用户问了一个不存在答案的问题。模型输出格式错误导致执行器无法解析出有效的Action但又没有触发错误处理。解决方案强制设置max_iterations如上例中的max_iterations5这是必须的安全阀。一般简单任务3-5步复杂任务可设到10-15步。实现超时机制除了迭代次数还可以加上总执行时间限制。优化工具设计确保工具在失败时返回有信息量的错误消息如“未找到关于‘XXX’的信息请尝试其他关键词”而不是空字符串或异常堆栈这能帮助模型调整策略。完善错误处理AgentExecutor的handle_parsing_errors参数设为True很重要它会在模型输出不符合格式时尝试将错误信息反馈给模型让它重试。你可以自定义这个错误处理函数提供更友好的提示。4.2 上下文管理与Token消耗ReAct的每一步都会将完整的思考、行动、观察历史作为上下文传递给下一步。对于一个多轮复杂任务上下文会迅速膨胀导致Token消耗剧增成本上升。可能触及模型上下文长度限制导致尾部历史丢失。无关历史干扰模型判断。解决方案选择性记忆不要使用简单的ConversationBufferMemory它记录所有历史。改用ConversationSummaryMemory或ConversationBufferWindowMemory。后者只保留最近K轮对话对于长流程任务更合适。在Thought中总结在提示词中鼓励模型在Thought阶段对历史观察进行简要总结而不是罗列所有细节。例如“基于之前的观察我们已经知道A和B现在需要解决C...”任务分解与子Agent对于超长任务可以设计一个顶层“规划Agent”将大任务分解为多个子任务每个子任务由一个独立的“执行Agent”使用ReAct完成。顶层Agent管理子任务间的依赖和结果汇总。4.3 工具设计的“粒度”与“描述”艺术工具不是越多越好也不是功能越强越好。工具粒度过粗例如一个叫plan_trip的工具输入城市和日期直接返回完整的旅行计划。这剥夺了ReAct分步推理的优势变成了单次函数调用。工具粒度过细例如把“查询温度”、“查询湿度”、“查询风速”分成三个工具。这会导致不必要的思考步骤增加复杂度和出错概率。最佳实践工具功能应单一、原子化一个工具最好只做一件事并且这件事是模型无法直接完成的如调用外部API、查询专有数据库、执行计算。get_current_weather就是一个好例子。工具描述要精准且具引导性描述要清晰说明工具的用途、输入格式和输出示例。可以用“此工具用于...”、“输入应为...”、“返回...”这样的句式。好的描述能极大减少模型的误用。为工具提供示例一些高级的Agent框架允许你为工具提供调用示例这能进一步规范模型的行为。4.4 提示词工程引导模型“正确思考”默认的ReAct提示词模板是个好的起点但针对特定领域你需要微调。强调停止条件在提示词中明确写出“当你认为已经收集到足够信息来回答问题或者无法通过现有工具获得更多信息时你必须输出Final Answer:。”规定思考方向对于复杂任务可以给一些思考框架。例如“在Thought中你应该1. 回顾当前目标和已有信息2. 分析缺失什么关键信息3. 选择最合适的工具来获取该信息。”处理模糊输入如果用户问题很模糊你希望Agent主动询问可以在提示词中加入“如果用户的问题信息不足无法决定使用哪个工具或需要具体参数你应该在Thought中提出澄清性问题并以Final Answer:的形式向用户提问。”5. 超越基础多工具协作与复杂工作流设计当任务需要多个工具按特定顺序或条件协同工作时基础的ReAct循环可能显得有些“自由散漫”。这时我们需要引入更精细的控制。5.1 使用“工具依赖”提示我们可以在提示词中明确工具之间的关系。例如在我们的天气旅行顾问里可以这样写可用工具 1. parse_date: 将人类日期转换为YYYY-MM-DD格式。在查询天气前通常需要先使用此工具。 2. get_current_weather: 根据城市和日期查询天气。使用此工具前你应确保已获得格式正确的日期。这样模型在思考时会更有倾向性地先调用parse_date。5.2 实现有条件的分支逻辑有时下一步行动取决于上一步的结果。例如一个客服Agent先查订单状态如果状态是“已发货”则调用物流查询工具如果是“待付款”则调用支付提醒工具。这需要模型在Thought中进行条件判断。这恰恰是ReAct的优势所在。模型在Thought中可以进行复杂的逻辑推理“根据Observation订单状态为‘已发货’。因此下一步应该获取物流信息。” 然后调用对应的物流工具。我们无需在代码中硬编码if-else而是将逻辑判断交给了更灵活的LLM。5.3 与工作流引擎结合对于极其复杂、步骤固定的业务流程ReAct Agent可以作为其中一个智能决策节点嵌入到更大的工作流引擎如Airflow、Prefect中。工作流引擎负责宏观流程编排和状态持久化而ReAct Agent则负责在某个节点上处理需要认知和决策的复杂子任务。例如在一个自动化报告生成流程中有一个节点是“分析数据异常原因”这个节点就可以用一个ReAct Agent来实现它可以使用数据查询工具、统计计算工具等自主分析并生成分析结论。6. 评估与调试你的Agent真的在“思考”吗开发完成后如何评估Agent的表现不能只看最终答案对不对。过程评估检查每一步的Thought是否合理。它的推理是否符合逻辑选择的工具是否恰当当工具失败时它的应对策略是否有效工具使用效率统计完成一个任务平均需要多少步迭代次数。步数过多可能意味着工具设计不佳或提示词引导不够。成本与延迟监控每次调用的总Token消耗和整体执行时间。ReAct模式由于多轮交互成本通常高于单次问答。稳定性测试用一批涵盖各种边界情况的测试用例如模糊问题、错误输入、工具异常来“轰炸”你的Agent观察其崩溃率、循环率和最终答案的可用性。调试时一定要开启verboseTrue仔细阅读完整的思维链。很多时候问题不是出在最后一步而是在中间的某一步思考出现了偏差。你可能需要针对性地调整提示词、优化工具描述或增加更具体的指令。从我自己的经验来看构建一个可靠的ReAct Agent三分在算法七分在工程。它要求开发者不仅懂LLM更要懂软件设计、懂用户体验、懂异常处理。它不是一个即插即用的黑盒而是一个需要精心调校的复杂系统。但一旦调校得当它能解决的问题上限将远远超过传统的规则引擎或简单的API调用链。这种让机器具备“动态思考-行动”能力的过程本身就是一种充满挑战和乐趣的创造。
返回列表