ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从ReAct到Plan-and-Solve:构建具备规划能力的AI智能体

从ReAct到Plan-and-Solve:构建具备规划能力的AI智能体 1. 项目概述从“想到就做”到“三思而后行”的智能体进化在AI智能体开发这个圈子里最近两年大家讨论最多的可能就是如何让大语言模型LLM驱动的智能体从一个只会“直线思考”的简单执行者变成一个懂得“三思而后行”的复杂决策者。我们经常遇到这样的场景你给智能体一个稍微复杂点的任务比如“帮我分析一下上个月的销售数据找出问题并给出下个月的优化建议”它可能会立刻开始行动但往往因为缺乏规划导致步骤混乱、信息遗漏甚至中途“跑偏”。这背后的核心痛点就是智能体缺乏一个有效的“内部思考”和“规划”机制。今天要聊的“Plan-and-Solve”智能体正是为了解决这个问题而生。它不是某个具体的开源项目而是一种设计范式、一种架构思想旨在让智能体在执行任何任务前先停下来花点时间“想一想”怎么做。这种范式与之前流行的ReActReasoning Acting框架有深刻的渊源但又在思路上做了关键的演进。简单来说ReAct让智能体学会了在“思考一步”和“行动一步”之间交替进行这已经是巨大的进步。但Plan-and-Solve更进一步它强调在执行开始前先进行一个相对完整、结构化的“规划”阶段。这个规划不是模糊的“我要做A、B、C”而是更接近于人类解决问题时的思维过程拆解目标、评估资源、预判难点、设计步骤序列。对于任何想要构建更可靠、更复杂应用比如自动化数据分析、多步骤工作流编排、复杂问题求解的开发者来说理解并实践Plan-and-Solve模式是提升智能体能力上限的必经之路。2. 核心设计思路为何“规划”优于“反应”2.1 从ReAct到Plan-and-Solve的演进逻辑要理解Plan-and-Solve的价值必须先回顾一下它的前身——ReAct模式。ReAct的核心贡献在于将“推理链”Chain-of-Thought与“工具调用”Tool Use动态地结合了起来。智能体在每一步都会生成一个“思考”Thought基于这个思考来决定是调用工具Action还是直接给出答案然后观察工具返回的结果Observation再进行下一轮思考。这个过程模拟了人类“边想边做”的循环。然而ReAct模式在实践中暴露出几个典型问题短视决策由于每一步的思考只基于当前状态和历史观察智能体容易陷入局部最优缺乏对任务全局的考量。比如在需要多步信息检索的任务中它可能过早地锁定一个信息来源而忽略了其他更优的路径。错误累积与迷失一旦某一步行动出错后续的思考很容易被带偏难以回到正轨导致整个任务失败。效率低下对于可以并行或按固定流程执行的任务ReAct的串行“思考-行动”循环会引入不必要的延迟。Plan-and-Solve模式正是针对这些痛点提出的。它的核心思想是将“规划”Plan作为一个独立的、优先的阶段。在这个阶段智能体不执行任何对外部环境产生影响的动作而是专注于利用其知识和对任务的理解生成一个可执行的计划。这个计划通常包括任务分解、步骤序列、预期使用的工具、可能的风险及备用方案。完成规划后再进入“解决”Solve阶段即严格或灵活地执行该计划。注意这里的“规划”并非要求智能体生成一个完美无缺、不可更改的“神圣计划”。相反一个优秀的Plan-and-Solve架构应该允许智能体在“解决”阶段根据实际情况如工具调用失败、信息不符合预期动态地调整原计划即具备“重规划”Re-planning的能力。这更像是“战略规划”与“战术执行”的结合。2.2 Plan-and-Solve智能体的关键组件设计一个典型的Plan-and-Solve智能体在架构上通常会包含以下几个核心组件它们共同协作实现了从任务接收到结果输出的闭环任务解析与意图理解模块这是规划的起点。它负责将用户模糊的自然语言指令转化为结构化的任务目标。例如将“分析销售数据”解析为“目标生成包含趋势分析、问题诊断、优化建议的报告输入过去30天的销售CSV文件约束需要在1小时内完成”。规划器这是整个智能体的“大脑”。规划器接收结构化的任务目标并输出一个行动计划。这个计划的生成方式有多种基于提示的规划通过精心设计的提示词Prompt直接要求LLM生成步骤列表。这是最简单的方式但对提示词工程要求高且规划质量不稳定。规划模板为常见任务类型如数据查询、内容生成、代码审查预定义步骤模板规划器根据任务类型填充模板细节。规划即程序将规划过程视为生成一段特殊的“元程序”或“工作流描述语言”如类似LangGraph的DSL这个程序定义了执行逻辑。计划执行引擎这是智能体的“双手”。它接收规划器输出的计划并按顺序或并行调用相应的工具或技能来执行每一步。它负责管理工具调用的生命周期、处理输入输出、以及传递上下文。状态管理与监控器这是智能体的“感官和神经系统”。它持续追踪执行状态当前步骤、已完成步骤、中间结果、监控外部环境变化以及计划执行是否偏离预期例如工具返回错误或结果与预期不符。当检测到异常或失败时它会触发重规划流程。重规划与反思模块这是智能体“从错误中学习”的能力。当执行失败或结果不理想时此模块会分析失败原因并指导规划器生成一个新的、修正后的计划。更高级的实现还包括“事后反思”将本次任务的经验成功或失败存储到记忆中以供未来参考。在实际构建时这些组件可能被整合在像LangChain、LangGraph、Dify、Coze这样的智能体开发框架中。开发者需要根据任务复杂度决定是使用框架提供的高级抽象还是自己组合这些底层组件。3. 核心细节解析与实操要点3.1 规划阶段如何生成一个“好”计划规划阶段的质量直接决定了整个任务的成败。一个“好”的计划不仅仅是步骤列表它应该具备以下特征可执行性每一步都必须对应一个智能体可用的、具体的工具或能力。合理性步骤顺序符合逻辑后一步依赖前一步的输出。健壮性考虑了可能的失败点并包含备用方案如“如果A API调用失败则尝试B方法”。粒度适中步骤既不能太粗如“处理数据”导致执行器无从下手也不能太细如“打开文件、读取第一行”导致规划过于冗长和僵化。实操技巧设计高效的规划提示词对于采用“基于提示的规划”的方式提示词的设计至关重要。下面是一个改进后的规划提示词示例它比简单的“请列出步骤”要有效得多你是一个经验丰富的任务规划专家。请为用户的任务制定一个详细、可执行的计划。 # 任务目标 {用户输入的任务描述} # 可用工具 1. web_search(query): 执行网络搜索返回摘要和链接。 2. read_file(file_path): 读取指定路径的文本文件内容。 3. python_executor(code): 在一个安全的沙箱中执行Python代码并返回结果。 4. write_report(content, format): 根据内容和格式markdown/docx生成报告文件。 # 规划要求 - 请将任务分解为连续的、具体的步骤。 - 每一步必须明确说明将使用哪个工具从上述列表中选择以及该步骤的输入是什么可以引用上一步的输出。 - 请考虑可能的风险如网络搜索无结果、文件不存在、代码错误并在相关步骤后注明备用方案。 - 如果任务涉及决策点例如根据数据结果选择不同分析路径请在计划中说明判断条件。 请以JSON格式输出计划结构如下 { task_goal: 任务目标的简要总结, steps: [ { step_id: 1, description: 步骤描述, tool: 工具名称, input: 工具输入可以是静态字符串或如{step_1_output}的动态引用, contingency_plan: 备用方案可选 } // ... 更多步骤 ] }这个提示词通过结构化输入任务、可用工具、明确输出格式JSON并强制要求考虑备用方案极大地提高了规划的可控性和质量。在实际调用LLM API时你可以将{用户输入的任务描述}替换为实际内容。3.2 解决阶段灵活执行与动态调整有了计划执行阶段看似是机械化的按图索骥实则暗藏玄机。一个僵化的执行器会因一点意外而全盘崩溃而一个灵活的执行器则能应对变化。关键实现上下文管理与步骤间数据传递执行引擎必须维护一个“执行上下文”这是一个字典或对象用于存储整个任务生命周期中的数据。通常每个步骤执行后的输出Observation会被存储在这个上下文中并分配一个唯一的键如step_1_output,search_results。后续步骤的输入可以引用这些键。例如在规划中某一步的input字段可能是“分析以下数据{step_2_output}”执行引擎需要在运行时将其替换为实际值。动态调整策略何时以及如何触发重规划重规划是Plan-and-Solve智能体“智能”的体现。以下是几种常见的触发条件和处理策略触发条件现象示例重规划策略工具执行失败API返回错误码、文件未找到、网络超时。1.重试对于瞬时错误可配置重试机制。2.启用备用方案如果计划中包含了该步骤的contingency_plan则执行它。3.局部重规划仅针对失败步骤及其后续依赖步骤重新规划。结果不符合预期网络搜索返回的结果与主题无关数据分析结果全是零。1.验证与过滤设计结果验证器如关键词匹配、数据范围检查过滤无效结果。2.调整参数重试例如修改搜索查询词重新调用工具。3.触发反思将“结果不符合预期”这一观察连同当前上下文发送给反思模块生成新的分析思路和计划。外部条件变化用户中途修改了需求或依赖的某个服务状态变更。1.状态同步监控器需能感知外部变化。2.全局重规划基于新的任务目标或环境状态从头开始规划。在LangGraph这类基于状态机的框架中实现重规划非常直观。你可以设计一个“监督节点”Supervisor Node它在每个步骤执行后检查状态。如果状态标记为“需要重规划”则让流程跳转回规划节点并携带当前的上下文和历史信息让规划器在已有进度的基础上制定新计划。4. 实操过程构建一个简易的Plan-and-Solve数据分析智能体让我们通过一个具体案例将上述理论付诸实践。我们将构建一个智能体其任务是“读取本地的sales.csv文件计算过去7天的日均销售额并判断相较于上上周是上升还是下降最后将结论写入report.txt。”4.1 环境准备与工具定义我们使用Python和LangChain框架来简化开发。首先安装必要库并定义工具。pip install langchain langchain-openai pandas# plan_and_solve_agent.py import os import pandas as pd from typing import Optional, Dict, Any from langchain.tools import tool from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, SystemMessage import json # 1. 定义工具集 tool def read_file(file_path: str) - str: 读取指定路径的文本文件内容。 try: with open(file_path, r, encodingutf-8) as f: return f.read() except FileNotFoundError: return f错误找不到文件 {file_path} except Exception as e: return f读取文件时出错{str(e)} tool def write_file(content: str, file_path: str) - str: 将内容写入指定路径的文件。 try: with open(file_path, w, encodingutf-8) as f: f.write(content) return f成功写入文件{file_path} except Exception as e: return f写入文件时出错{str(e)} tool def analyze_sales_data(csv_content: str) - Dict[str, Any]: 分析销售CSV数据。计算过去7天和上上周的日均销售额并进行比较。 try: # 假设CSV有date和revenue两列 from io import StringIO df pd.read_csv(StringIO(csv_content)) df[date] pd.to_datetime(df[date]) df df.sort_values(date) # 获取最近日期 latest_date df[date].iloc[-1] # 过去7天 last_7_days df[df[date] (latest_date - pd.Timedelta(days7))] # 上上周7天前到14天前 two_weeks_ago_start latest_date - pd.Timedelta(days14) two_weeks_ago_end latest_date - pd.Timedelta(days7) previous_week df[(df[date] two_weeks_ago_start) (df[date] two_weeks_ago_end)] avg_last_7 last_7_days[revenue].mean() if not last_7_days.empty else 0 avg_previous previous_week[revenue].mean() if not previous_week.empty else 0 change avg_last_7 - avg_previous change_percentage (change / avg_previous * 100) if avg_previous ! 0 else float(inf) return { last_7_days_avg: round(avg_last_7, 2), previous_week_avg: round(avg_previous, 2), change_amount: round(change, 2), change_percentage: round(change_percentage, 2), trend: 上升 if change 0 else 下降 } except Exception as e: return {error: f数据分析失败{str(e)}} # 工具列表供规划器知晓 available_tools { read_file: read_file, write_file: write_file, analyze_sales_data: analyze_sales_data }4.2 实现规划器与执行引擎接下来我们实现一个简单的规划器和执行循环。# 接上文代码 # 2. 规划器 (基于提示的规划) class Planner: def __init__(self, llm): self.llm llm def generate_plan(self, task: str) - Dict: 根据任务生成执行计划。 prompt f 你是一个任务规划AI。请为以下任务制定一个详细的、可执行的JSON格式计划。 # 任务 {task} # 可用工具 - read_file(file_path): 读取文件内容。 - analyze_sales_data(csv_content): 分析销售CSV数据返回统计结果。 - write_file(content, file_path): 将内容写入文件。 # 输出格式 {{ steps: [ {{ step_id: 1, description: 步骤描述, tool: 工具名, input: 工具输入可引用上一步输出如{{step_1_result}} }} ] }} 请确保步骤逻辑连贯输入输出引用正确。 messages [SystemMessage(content你是一个严谨的任务规划专家。), HumanMessage(contentprompt)] response self.llm.invoke(messages) # 假设LLM返回的是合法的JSON字符串 try: plan json.loads(response.content) return plan except json.JSONDecodeError: # 简单处理尝试提取JSON部分 import re json_match re.search(r\{.*\}, response.content, re.DOTALL) if json_match: return json.loads(json_match.group()) else: raise ValueError(fLLM未返回有效JSON。响应{response.content}) # 3. 执行引擎 class ExecutionEngine: def __init__(self, tools): self.tools {t.name: t for t in tools} # 按工具名索引 self.context {} # 执行上下文存储步骤结果 def execute_step(self, step: Dict) - str: 执行单个步骤返回结果字符串。 step_id step[step_id] tool_name step[tool] raw_input step[input] # 渲染输入将上下文变量如{{step_1_result}}替换为实际值 tool_input self._render_input(raw_input) if tool_name not in self.tools: return f错误未知工具 {tool_name} try: tool self.tools[tool_name] # 调用工具 result tool.invoke(tool_input) # 将结果存入上下文供后续步骤引用 ctx_key fstep_{step_id}_result self.context[ctx_key] str(result) return str(result) except Exception as e: error_msg f步骤{step_id}执行失败{str(e)} self.context[fstep_{step_id}_error] error_msg return error_msg def _render_input(self, raw_input: str) - str: 将输入字符串中的变量占位符替换为上下文中的实际值。 import re def replace_match(match): var_name match.group(1) # 获取{{var}}中的var return self.context.get(var_name, match.group(0)) # 找不到则保留原样 return re.sub(r\{\{(\w)\}\}, replace_match, raw_input) # 4. 主程序Plan-and-Solve工作流 def main(): # 初始化LLM这里使用OpenAI GPT-4实际使用时请替换为你的API Key llm ChatOpenAI(modelgpt-4, temperature0, api_keyos.getenv(OPENAI_API_KEY)) # 初始化组件 planner Planner(llm) engine ExecutionEngine([read_file, write_file, analyze_sales_data]) # 用户任务 task 读取本地的sales.csv文件计算过去7天的日均销售额并判断相较于上上周是上升还是下降最后将结论写入report.txt。 print( 规划阶段 ) plan planner.generate_plan(task) print(生成的计划) print(json.dumps(plan, indent2, ensure_asciiFalse)) print(\n 解决阶段 ) for step in plan[steps]: print(f\n执行步骤 {step[step_id]}: {step[description]}) print(f 工具: {step[tool]}, 输入: {step[input]}) result engine.execute_step(step) print(f 结果: {result[:200]}...) # 打印前200字符 print(\n 任务完成 ) # 可以从上下文中读取最终结果 final_result engine.context.get(fstep_{len(plan[steps])}_result, 无结果) print(f最终输出已保存。上下文最后一步结果{final_result}) if __name__ __main__: main()运行这个程序规划器可能会生成类似如下的计划{ steps: [ { step_id: 1, description: 读取sales.csv文件内容, tool: read_file, input: sales.csv }, { step_id: 2, description: 分析读取到的CSV数据计算日均销售额并比较趋势, tool: analyze_sales_data, input: {{step_1_result}} }, { step_id: 3, description: 将分析结果总结并写入报告文件, tool: write_file, input: 销售分析报告过去7天日均销售额为{{step_2_result.last_7_days_avg}}上上周为{{step_2_result.previous_week_avg}}趋势为{{step_2_result.trend}}。变化幅度{{step_2_result.change_percentage}}%。\n报告生成时间2023-10-27 } ] }这个简单的例子展示了Plan-and-Solve的核心流程解析任务、生成结构化计划、按计划执行并管理上下文。虽然它没有实现复杂的重规划但已经具备了基本骨架。5. 常见问题与排查技巧实录在实际开发和应用Plan-and-Solve智能体时你会遇到各种各样的问题。下面是我在多个项目中总结的一些典型问题及其解决方案。5.1 规划阶段常见问题问题1LLM生成的计划不可执行或工具调用错误。现象规划器返回的计划中tool字段的名称与注册的工具名不匹配或者input格式不符合工具要求。排查与解决工具描述标准化在给LLM的提示词中清晰、无歧义地列出工具名、参数及其类型。例如使用类似OpenAI Function Calling的规范来描述工具。后处理校验在规划器输出后增加一个校验步骤。检查每个步骤的tool是否在可用工具列表中并尝试用工具的参数模式去验证input的格式。少样本提示在提示词中提供1-2个高质量的计划示例Few-shot Learning让LLM模仿正确的格式和逻辑。问题2计划过于僵化或过于模糊。现象计划要么步骤太细导致执行效率低下且无法适应微小变化要么步骤太粗如“处理数据”让执行器无法理解具体要做什么。排查与解决任务分类与模板对常见任务类型进行分类并为每类任务预定义计划模板。例如“数据分析报告”类任务可以固定为“获取数据 - 清洗分析 - 生成报告”三步模板规划器只需填充具体参数如文件路径、分析指标。分层规划采用“高层规划”与“底层规划”结合。高层规划只定义里程碑和子目标每个子目标在即将执行时再触发一次具体的底层规划。这平衡了灵活性与可控性。5.2 执行与重规划阶段常见问题问题3工具执行失败导致流程中断。现象网络工具调用超时、文件不存在、API返回非预期错误。排查与解决完善的错误处理与重试在每个工具调用外层包裹健壮的异常处理。对于网络等瞬时错误实现指数退避的重试机制。计划中的备用路径如前文所述在规划阶段就要求LLM为关键步骤思考备用方案contingency_plan。执行引擎在遇到失败时首先尝试执行备用方案。设置超时与熔断为每个工具调用设置合理的超时时间。如果某个工具连续失败可以暂时将其标记为不可用熔断避免后续步骤继续尝试。问题4上下文管理混乱数据传递错误。现象步骤B试图引用步骤A的输出{{step_A_result}}但实际引用的是原始字符串而非解析后的值或者因为键名不一致导致找不到数据。排查与解决严格的上下文键名约定建立统一的命名规则如step_{id}_output、step_{id}_error。并在规划提示词中明确告知LLM这个规则。上下文渲染与类型检查执行引擎在替换变量时应能处理复杂的嵌套结构如{{step_2_result.last_7_days_avg}}。可以引入简单的模板引擎如Jinja2或使用JSONPath进行精确提取。上下文快照与调试在执行过程中定期将整个上下文状态记录到日志中。当出现数据传递问题时查看日志可以快速定位是哪个步骤的输出出了问题。问题5重规划循环或效率低下。现象智能体陷入“失败-重规划-再失败”的死循环或者重规划耗时过长影响整体体验。排查与解决设置重规划上限为单个任务设置最大重规划次数如3次超过则整体失败并向用户报告。提供更丰富的失败上下文触发重规划时不要只把错误信息丢给规划器。应该将当前完整的执行历史、已尝试过的方案、以及具体的错误原因都提供给规划器帮助它做出更明智的新计划。缓存规划结果对于常见任务或子任务可以将成功的计划缓存起来。当遇到类似任务时优先尝试使用缓存计划或在其基础上进行微调而不是每次都从头规划。5.3 性能与成本优化问题6规划阶段LLM调用成本高、延迟大。现象复杂任务的规划提示词很长每次调用GPT-4等大型模型成本不菲且响应慢。解决思路轻量级规划器对于确定性高的任务可以使用更小、更快的模型如GPT-3.5-Turbo进行规划或者使用基于规则的规划器。规划缓存同上对任务描述进行哈希缓存对应的成功计划。异步规划如果用户对实时性要求不高可以在后台异步执行规划规划完成后再通知执行。问题7执行过程冗长用户等待焦虑。现象一个包含10个步骤的计划执行完可能需要几分钟期间用户看不到任何进展。解决思路进度反馈执行引擎向用户界面实时推送进度信息“正在执行步骤1/10读取文件...”。步骤并行化分析计划中步骤的依赖关系对于彼此独立的步骤可以并行执行以缩短总耗时。这需要更复杂的依赖分析和执行调度器。构建一个成熟的Plan-and-Solve智能体是一个迭代过程。从最简单的静态规划执行开始逐步加入错误处理、重规划、上下文优化等能力。最关键的是要深入理解你的具体业务场景明确智能体需要应对的不确定性主要来自哪里是工具不可靠还是用户需求模糊然后有针对性地强化智能体在该环节的能力。记住没有一劳永逸的架构只有最适合当前场景的设计。
返回列表