4.3 Plan-and-Solve
After mastering ReAct, this reactive, step-by-step decision-making agent paradigm, we will next explore a method with a very different style but equally powerful: Plan-and-Solve. As the name suggests, this paradigm explicitly divides task processing into two stages: Plan first, then Solve.
If ReAct is like an experienced detective who reasons step by step based on clues at the scene (Observation) and adjusts investigation direction at any time; then Plan-and-Solve is more like an architect who must first draw a complete blueprint (Plan) before starting construction, then strictly build according to the blueprint (Solve). In fact, many large model tools' Agent modes we use now incorporate this design pattern.
4.3.1 Working Principle of Plan-and-Solve
Plan-and-Solve Prompting was proposed by Lei Wang in 2023<sup>[2]</sup>. Its core motivation is to solve the problem that chain-of-thought easily "goes off track" when handling multi-step, complex problems.
Unlike ReAct, which integrates thinking and acting at each step, Plan-and-Solve decouples the entire process into two core stages, as shown in Figure 4.2:
- Planning Phase: First, the agent receives the user's complete question. Its first task is not to directly solve the problem or call tools, but to decompose the problem and formulate a clear, step-by-step action plan. This plan itself is the product of a large language model call.
- Solving Phase: After obtaining the complete plan, the agent enters the execution phase. It will strictly execute according to the steps in the plan, one by one. Each step's execution may be an independent LLM call or processing of the previous step's results, until all steps in the plan are completed and the final answer is obtained.
This "plan before acting" strategy enables the agent to maintain higher goal consistency when handling complex tasks requiring long-term planning, avoiding getting lost in intermediate steps.
We can formally express this two-stage process. First, the planning model $\pi_{\text{plan}}$ generates a plan $P = (p_1, p_2, \dots, p_n)$ containing $n$ steps based on the original question $q$:
$$ P = \pi_{\text{plan}}(q) $$
Subsequently, in the execution phase, the execution model $\pi_{\text{solve}}$ will complete the steps in the plan one by one. For the $i$-th step, the generation of its solution $s_i$ will depend on the original question $q$, the complete plan $P$, and the execution results of all previous steps $(s_1, \dots, s_{i-1})$:
$$ s_i = \pi_{\text{solve}}(q, P, (s_1, \dots, s_{i-1})) $$
The final answer is the execution result of the last step $s_n$.

Figure 4.2 Two-Stage Workflow of Plan-and-Solve Paradigm
Plan-and-Solve is especially suitable for complex tasks with strong structure that can be clearly decomposed, such as:
- Multi-step math word problems: Need to first list calculation steps, then solve one by one.
- Report writing integrating multiple information sources: Need to first plan the report structure (introduction, data source A, data source B, summary), then fill in content one by one.
- Code generation tasks: Need to first conceive the structure of functions, classes, and modules, then implement one by one.
4.3.2 Planning Phase
To highlight the advantages of the Plan-and-Solve paradigm in structured reasoning tasks, we will not use tools but complete a reasoning task through prompt design.
The characteristic of this type of task is that the answer cannot be obtained through a single query or calculation; the problem must first be decomposed into a series of logically coherent sub-steps, then solved in order. This precisely leverages Plan-and-Solve's core capability of "plan first, execute later."
Our target problem is: "A fruit store sold 15 apples on Monday. The number of apples sold on Tuesday was twice that of Monday. The number sold on Wednesday was 5 fewer than Tuesday. How many apples were sold in total over these three days?"
This problem is not particularly difficult for large language models, but it contains a clear logical chain for reference. For some actual logical puzzles, if the large model cannot reason out accurate answers with high quality, you can refer to this design pattern to design your own Agent to complete the task. The agent needs to:
- Planning Phase: First, decompose the problem into three independent calculation steps (calculate Tuesday sales, calculate Wednesday sales, calculate total sales).
- Execution Phase: Then, strictly follow the plan, execute calculations step by step, and use each step's result as input for the next step, finally obtaining the total.
The goal of the planning phase is to have the large language model receive the original problem and output a clear, step-by-step action plan. This plan must be structured so our code can easily parse and execute it one by one. Therefore, the prompt we design needs to clearly tell the model its role and task and provide an example of the output format.
PLANNER_PROMPT_TEMPLATE = """
You are a top AI planning expert. Your task is to decompose complex problems posed by users into an action plan consisting of multiple simple steps.
Please ensure that each step in the plan is an independent, executable subtask and is strictly arranged in logical order.
Your output must be a Python list, where each element is a string describing a subtask.
Question: {question}
Please strictly output your plan in the following format, with ```python and ``` as prefix and suffix being necessary:
```python
["Step 1", "Step 2", "Step 3", ...]
```
"""This prompt ensures output quality and stability through the following points:
- Role Setting: "Top AI planning expert" activates the model's professional capabilities.
- Task Description: Clearly defines the goal of "decomposing problems."
- Format Constraint: Forces output to be a string in Python list format, which greatly simplifies subsequent code parsing work, making it more stable and reliable than parsing natural language.
Next, we encapsulate this prompt logic into a Planner class, which is also our planner.
# Assume the HelloAgentsLLM class in llm_client.py is already defined
# from llm_client import HelloAgentsLLM
class Planner:
def __init__(self, llm_client):
self.llm_client = llm_client
def plan(self, question: str) -> list[str]:
"""
Generate an action plan based on user question.
"""
prompt = PLANNER_PROMPT_TEMPLATE.format(question=question)
# To generate a plan, we build a simple message list
messages = [{"role": "user", "content": prompt}]
print("--- Generating Plan ---")
# Use streaming output to get the complete plan
response_text = self.llm_client.think(messages=messages) or ""
print(f"✅ Plan Generated:\n{response_text}")
# Parse the list string output by LLM
try:
# Find content between ```python and ```
plan_str = response_text.split("```python")[1].split("```")[0].strip()
# Use ast.literal_eval to safely execute the string and convert it to a Python list
plan = ast.literal_eval(plan_str)
return plan if isinstance(plan, list) else []
except (ValueError, SyntaxError, IndexError) as e:
print(f"❌ Error parsing plan: {e}")
print(f"Raw response: {response_text}")
return []
except Exception as e:
print(f"❌ Unknown error occurred while parsing plan: {e}")
return []4.3.3 Executor and State Management
After the planner (Planner) generates a clear action blueprint, we need an executor (Executor) to complete the tasks in the plan one by one. The executor is not only responsible for calling the large language model to solve each sub-problem but also plays a crucial role: state management. It must record the execution results of each step and provide them as context for subsequent steps, ensuring information flows smoothly throughout the entire task chain.
The executor's prompt is different from the planner's. Its goal is not to decompose problems but to focus on solving the current step based on existing context. Therefore, the prompt needs to include the following key information:
- Original Question: Ensure the model always understands the ultimate goal.
- Complete Plan: Let the model understand the current step's position in the entire task.
- Historical Steps and Results: Provide work completed so far as direct input for the current step.
- Current Step: Clearly instruct the model which specific task it needs to solve now.
EXECUTOR_PROMPT_TEMPLATE = """
You are a top AI execution expert. Your task is to strictly follow the given plan and solve the problem step by step.
You will receive the original question, the complete plan, and the steps and results completed so far.
Please focus on solving the "current step" and only output the final answer for that step, without any additional explanations or dialogue.
# Original Question:
{question}
# Complete Plan:
{plan}
# Historical Steps and Results:
{history}
# Current Step:
{current_step}
Please only output the answer for the "current step":
"""We encapsulate the execution logic into the Executor class. This class will loop through the plan, call the LLM, and maintain a history (state).
class Executor:
def __init__(self, llm_client):
self.llm_client = llm_client
def execute(self, question: str, plan: list[str]) -> str:
"""
Execute step by step according to the plan and solve the problem.
"""
history = "" # String to store historical steps and results
print("\n--- Executing Plan ---")
for i, step in enumerate(plan):
print(f"\n-> Executing step {i+1}/{len(plan)}: {step}")
prompt = EXECUTOR_PROMPT_TEMPLATE.format(
question=question,
plan=plan,
history=history if history else "None", # If it's the first step, history is empty
current_step=step
)
messages = [{"role": "user", "content": prompt}]
response_text = self.llm_client.think(messages=messages) or ""
# Update history for the next step
history += f"Step {i+1}: {step}\nResult: {response_text}\n\n"
print(f"✅ Step {i+1} completed, result: {response_text}")
# After the loop ends, the last step's response is the final answer
final_answer = response_text
return final_answerNow we have separately built the Planner responsible for "planning" and the Executor responsible for "execution." The last step is to integrate these two components into a unified agent PlanAndSolveAgent and give it complete problem-solving capabilities. We will create a main class PlanAndSolveAgent whose responsibility is very clear: receive an LLM client, initialize internal planner and executor, and provide a simple run method to start the entire process.
class PlanAndSolveAgent:
def __init__(self, llm_client):
"""
Initialize the agent and create planner and executor instances.
"""
self.llm_client = llm_client
self.planner = Planner(self.llm_client)
self.executor = Executor(self.llm_client)
def run(self, question: str):
"""
Run the agent's complete process: plan first, then execute.
"""
print(f"\n--- Starting to Process Question ---\nQuestion: {question}")
# 1. Call planner to generate plan
plan = self.planner.plan(question)
# Check if plan was successfully generated
if not plan:
print("\n--- Task Terminated --- \nUnable to generate valid action plan.")
return
# 2. Call executor to execute plan
final_answer = self.executor.execute(question, plan)
print(f"\n--- Task Completed ---\nFinal Answer: {final_answer}")The design of this PlanAndSolveAgent class embodies the principle of "composition over inheritance." It doesn't contain complex logic itself but acts as an orchestrator, clearly calling its internal components to complete tasks.
4.3.4 Running Instance and Analysis
The complete code can also be found in the code folder of this book's accompanying code repository; here we only demonstrate the final results.
--- Starting to Process Question ---
Question: A fruit store sold 15 apples on Monday. The number of apples sold on Tuesday was twice that of Monday. The number sold on Wednesday was 5 fewer than Tuesday. How many apples were sold in total over these three days?
--- Generating Plan ---
🧠 Calling xxxx model...
✅ Large language model response successful:
```python
["Calculate Monday's apple sales: 15", "Calculate Tuesday's apple sales: Monday's quantity × 2 = 15 × 2 = 30", "Calculate Wednesday's apple sales: Tuesday's quantity - 5 = 30 - 5 = 25", "Calculate total sales for three days: Monday + Tuesday + Wednesday = 15 + 30 + 25 = 70"]
```
✅ Plan Generated:
```python
["Calculate Monday's apple sales: 15", "Calculate Tuesday's apple sales: Monday's quantity × 2 = 15 × 2 = 30", "Calculate Wednesday's apple sales: Tuesday's quantity - 5 = 30 - 5 = 25", "Calculate total sales for three days: Monday + Tuesday + Wednesday = 15 + 30 + 25 = 70"]
```
--- Executing Plan ---
-> Executing step 1/4: Calculate Monday's apple sales: 15
🧠 Calling xxxx model...
✅ Large language model response successful:
15
✅ Step 1 completed, result: 15
-> Executing step 2/4: Calculate Tuesday's apple sales: Monday's quantity × 2 = 15 × 2 = 30
🧠 Calling xxxx model...
✅ Large language model response successful:
30
✅ Step 2 completed, result: 30
-> Executing step 3/4: Calculate Wednesday's apple sales: Tuesday's quantity - 5 = 30 - 5 = 25
🧠 Calling xxxx model...
✅ Large language model response successful:
25
✅ Step 3 completed, result: 25
-> Executing step 4/4: Calculate total sales for three days: Monday + Tuesday + Wednesday = 15 + 30 + 25 = 70
🧠 Calling xxxx model...
✅ Large language model response successful:
70
✅ Step 4 completed, result: 70
--- Task Completed ---
Final Answer: 70From the above output log, we can clearly see the workflow of the Plan-and-Solve paradigm:
- Planning Phase: The agent first calls
Plannerand successfully decomposes the complex word problem into a Python list containing four logical steps. This structured plan lays the foundation for subsequent execution. - Execution Phase:
Executorstrictly executes step by step according to the generated plan. In each step, it uses historical results as context, ensuring correct information transfer (for example, step 2 correctly uses step 1's result "15", and step 3 also correctly uses step 2's result "30"). - Result: The entire process is logically clear with explicit steps, and the agent accurately arrives at the correct answer "70".