Plan-and-Execute RAG Agents
Implement plan-and-execute patterns where the agent plans retrieval steps before executing. Combine multi-step planning with iterative retrieval for complex questions.
Learning Goals
- Implement plan-and-execute with retrieval
- Build agents that plan retrieval steps before execution
Plan-and-Execute RAG Agents
While the ReAct pattern is great for simple multi-step tasks, it can sometimes get "lost" on complex questions that require long-term planning. The Plan-and-Execute pattern separates the reasoning into two distinct roles: a Planner that breaks the complex query into a list of sub-tasks, and an Executor that carries out those tasks (like searching different databases) one by one.
This architecture is more stable for deep research tasks where the agent needs to maintain a consistent strategy across many steps.
Learning Goals
- Contrast the Plan-and-Execute pattern with the ReAct pattern.
- Build a planning node that decomposes complex queries into sub-steps.
- Implement a task execution loop in LangGraph.
Core Concepts
1. The Planner (The Architect)
The Planner is an LLM that looks at the user's high-level goal and creates a structured plan.
- Goal: "Analyze the financial health of Company X and Company Y."
- Plan:
- Search for Company X's 2023 revenue.
- Search for Company X's 2023 debt.
- Search for Company Y's 2023 revenue.
- Search for Company Y's 2023 debt.
- Compare the results and summarize.
2. The Executor (The Worker)
The Executor takes a single task from the plan and uses its tools (retrievers, calculators) to complete it. It updates the state with the result of that specific task.
3. Re-Planning
After each step, the Planner can review the results and decide if the plan needs to be changed based on new information discovered during execution.
Plan-and-Execute Architecture
Building the Planning Loop
- 1Step 1
Extend your state to include a list of steps:
1class PlanState(TypedDict): 2 messages: Annotated[list, add_messages] 3 plan: list[str] 4 past_steps: list[tuple[str, str]] - 2Step 2
The planner outputs a structured list of strings:
1# Using structured output for the plan 2class Plan(BaseModel): 3 steps: list[str] 4 5planner = llm.with_structured_output(Plan) - 3Step 3
Route the flow back to the planner after each task is completed to update the progress:
1def should_end(state: PlanState): 2 if not state["plan"]: 3 return END 4 return "executor" 5 6workflow.add_conditional_edges("re-planner", should_end)
Example: Comparative Tech Analysis
User: "Which RAG pattern is better for low-latency apps: HyDE or RAG Fusion?"
- Plan Step 1: Retrieve latency benchmarks for HyDE.
- Plan Step 2: Retrieve latency benchmarks for RAG Fusion.
- Plan Step 3: Compare the counts and synthesize the answer. The Planner ensures that both patterns are researched before the synthesis step starts, preventing a biased or incomplete answer.
Common Mistakes
- Rigid Planning: A plan created at the start might become obsolete if the first search reveals the company doesn't exist. Always use a Re-planner node to allow the agent to pivot.
- Task Granularity: If tasks are too broad (e.g., "Research everything"), the Executor will fail. Ensure the Planner is prompted to create "atomic, tool-executable sub-tasks."
Recap
- Plan-and-Execute is better for complex, multi-step queries than ReAct.
- It provides a clear "Audit Trail" of what the agent intended to do vs. what it actually did.
- LangGraph facilitates the complex state transitions required for task management.
Knowledge Check
When should you choose a Plan-and-Execute agent over a simple ReAct agent?