摘要 (TL;DR): ReAct 虽好,但在长链条复杂任务中容易“迷路”。本篇介绍 Plan-and-Solve 范式,它将任务解耦为“规划”与“执行”两个阶段。我们将实现一个能解决复杂逻辑问题的智能体,涵盖 Planner(规划器)与 Executor(执行器)的独立封装与状态管理。
关键词: Plan-and-Solve, Task Decomposition, Chain of Thought, State Management
本文是基于Datawhale的hello-agent开源项目做的一些笔记,内容仅供参考,原PDF以及代码可以去github仓库获取https://datawhalechina.github.io/hello-agents


在 Part 1 & 2 中,我们构建了 ReAct 智能体,它像侦探一样边走边看。现在,我们要构建一个像建筑师一样的智能体:在动工前先画好蓝图。

4.3 Plan-and-Solve

掌握了反应式的 ReAct 后,我们探讨一种风格迥异但强大的范式:Plan-and-Solve。顾名思义,它将任务明确分为两个阶段:先规划 (Plan),后执行 (Solve) 。

4.3.1 Plan-and-Solve 的工作原理

如果说 ReAct 是根据现场蛛丝马迹推理的侦探,那么 Plan-and-Solve 则更像一位建筑师。它源于 Lei Wang 在 2023 年提出的 Plan-and-Solve Prompting 。

其核心为了解决 CoT (思维链) 在处理多步骤复杂问题时容易“偏离轨道”的问题。它将流程解耦为:

  1. 规划阶段 (Planning Phase):接收用户问题,不是直接解决,而是将其分解为一个清晰、分步骤的行动计划 。
  2. 执行阶段 (Solving Phase):严格按照计划 ,逐一执行步骤。每一步的输入都包含原始问题、完整计划以及之前步骤的执行结果 。
数学形式化

规划模型 生成计划 :

执行模型 逐步求解,依赖于历史结果:

流程可视化

Solving Phase

Planning Phase

Request

Generate Tasks

Execute Plan

Loop to solve task

Step Result

Final Answer

User

Planner Agent

Task List / Plan

Task Agent

Execution History

(图 4.2 Plan-and-Solve 范式的两阶段工作流)

这种模式特别适用于结构性强、可清晰分解的任务,如多步数学应用题、长篇报告撰写或代码生成 。


4.3.2 规划阶段 (The Planner)

为了凸显该范式在结构化推理上的优势,我们将解决一个逻辑应用题,而非使用工具。

目标问题

“一个水果店周一卖出了15个苹果。周二卖出的苹果数量是周一的两倍。周三卖出的数量比周二少了5个。请问这三天总共卖出了多少个苹果?”

1. 提示词设计 (Planner Prompt)

我们需要一个“顶级 AI 规划专家”,它的任务是将复杂问题拆解为 Python 列表格式的子任务。

PLANNER_PROMPT_TEMPLATE = """
你是一个顶级的 AI 规划专家。你的任务是将用户提出的复杂问题分解成一个由多个简单步骤组成的行动计划。
请确保计划中的每个步骤都是一个独立的、可执行的子任务,并且严格按照逻辑顺序排列。

你的输出必须是一个 Python 列表,其中每个元素都是一个描述子任务的字符串。

问题: {question}

请严格按照以下格式输出你的计划,python与```作为前后缀是必要的:
```python
["步骤1", "步骤2", "步骤3", ...]

“”"


> 💡 **深度解析 (Deep Dive)**:
> 这里强制要求输出 `Python List` 格式的字符串,而非自然语言列表(如 "1. First...")。这是一个重要的工程技巧:**Structured Output (结构化输出)**。在代码中解析 JSON 或 Python List 比解析自然语言文本要稳定得多,能有效避免格式错误。

#### 2. 规划器实现
我们将封装一个 `Planner` 类,负责生成计划并将其解析为 Python 对象。

```python
import ast # 用于安全解析字符串列表
# 假设已导入 HelloAgentsLLM

class Planner:
    def __init__(self, llm_client):
        self.llm_client = llm_client

    def plan(self, question: str) -> list[str]:
        """根据用户问题生成一个行动计划。"""
        prompt = PLANNER_PROMPT_TEMPLATE.format(question=question)
        messages = [{"role": "user", "content": prompt}]
        
        print("--- 正在生成计划 ---")
        response_text = self.llm_client.think(messages=messages) or ""
        print(f"计划已生成:\n{response_text}")

        # 解析 LLM 输出的列表字符串
        try:
            # 提取 markdown 代码块中的内容
            if "python" in response_text:
                plan_str = response_text.split("python")[1].split("```")[0].strip()
            elif "[" in response_text and "]" in response_text:
                # 容错处理:如果没写代码块,尝试直接找 []
                start = response_text.find("[")
                end = response_text.rfind("]") + 1
                plan_str = response_text[start:end]
            else:
                plan_str = "[]"

            # 使用 ast.literal_eval 安全转换
            plan = ast.literal_eval(plan_str)
            return plan if isinstance(plan, list) else []
            
        except (ValueError, SyntaxError, IndexError, Exception) as e:
            print(f"解析计划时出错: {e}")
            return []


4.3.3 执行器与状态管理 (The Executor)

生成蓝图后,我们需要 Executor 来搬砖。执行器最关键的角色是 State Management (状态管理):它必须记录每一步的执行结果,并作为上下文传给下一步 。

1. 执行器提示词

执行器的目标不是拆解,而是“专注当下”。

EXECUTOR_PROMPT_TEMPLATE = """
你是一位顶级的 AI 执行专家。你的任务是严格按照给定的计划,一步步地解决问题。
你将收到原始问题、完整的计划、以及到目前为止已经完成的步骤和结果。

请你专注于解决“当前步骤”,并仅输出该步骤的最终答案,不要输出任何额外的解释或对话。

# 原始问题:
{question}

# 完整计划:
{plan}

# 历史步骤与结果:
{history}

# 当前步骤:
{current_step}

请仅输出针对“当前步骤”的回答:
"""

2. 执行器实现

Executor 类循环遍历计划,维护 history 字符串。

class Executor:
    def __init__(self, llm_client):
        self.llm_client = llm_client

    def execute(self, question: str, plan: list[str]) -> str:
        """根据计划,逐步执行并解决问题。"""
        history = "" # 用于存储历史步骤和结果的状态
        final_answer = ""
        
        print("\n--- 正在执行计划 ---")
        
        for i, step in enumerate(plan):
            print(f"\n-> 正在执行步骤 {i+1}/{len(plan)}: {step}")
            
            prompt = EXECUTOR_PROMPT_TEMPLATE.format(
                question=question,
                plan=plan,
                history=history if history else "无",
                current_step=step
            )
            
            messages = [{"role": "user", "content": prompt}]
            response_text = self.llm_client.think(messages=messages) or ""
            
            # 更新历史记录 (State Update)
            history += f"步骤 {i+1}: {step}\n结果: {response_text}\n\n"
            print(f"步骤 {i+1} 已完成,结果: {response_text}")
            
            final_answer = response_text
            
        return final_answer


4.3.4 整合与运行 (Orchestration)

最后,我们用 PlanAndSolveAgent 将规划器和执行器组合起来。这体现了“组合优于继承”的原则 。

class PlanAndSolveAgent:
    def __init__(self, llm_client):
        self.llm_client = llm_client
        self.planner = Planner(self.llm_client)
        self.executor = Executor(self.llm_client)

    def run(self, question: str):
        print(f"\n--- 开始处理问题 ---\n问题: {question}")
        
        # 1. 规划
        plan = self.planner.plan(question)
        if not plan:
            print("\n--- 任务终止: 无法生成有效的行动计划 ---")
            return

        # 2. 执行
        final_answer = self.executor.execute(question, plan)
        print(f"\n--- 任务完成 ---\n最终答案: {final_answer}")

运行结果分析:

  1. Planner 输出: ['计算周一数量: 15', '计算周二数量: 15*2=30', '计算周三数量: 30-5=25', '计算总和: 15+30+25=70']
  2. Executor 执行:
  • Step 1 -> Result: 15
  • Step 2 -> Result: 30 (基于 Step 1)
  • Step 3 -> Result: 25 (基于 Step 2)
  • Step 4 -> Result: 70
  1. 最终答案: 70

通过这种方式,智能体避免了在一步推理中同时处理三个变量计算可能导致的算术错误(Hallucination in Logic)。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐