Tutorial 25: ReWOO Pattern
Overview
ReWOO (Reasoning WithOut Observation) is a token-efficient agent pattern that decouples planning from execution. Instead of interleaving reasoning with tool calls like ReAct, ReWOO generates a complete plan upfront, executes all tools sequentially with variable substitution, and synthesizes a final answer.
Key advantage: Reduces LLM calls from ~10 (ReAct) to 2 (ReWOO) for multi-step tasks.
Architecture
When to Use
Use ReWOO when:
- Token efficiency is critical: Minimize LLM calls for cost/latency
- Task structure is predictable: Can plan all steps upfront
- Tool execution is fast: Sequential execution is acceptable
- Multi-step workflows: Task requires multiple tool calls
Don't use ReWOO when:
- Adaptive planning needed: Tool results determine next steps
- Highly exploratory tasks: Cannot predict plan in advance
- Real-time interaction: Need immediate feedback after each tool
- Complex branching: Tool results lead to different paths
Comparison: ReAct vs ReWOO
| Aspect | ReAct | ReWOO |
|---|---|---|
| LLM Calls | ~10 (Think → Act → Observe × N) | 2 (Plan + Synthesize) |
| Token Usage | High (repeated context) | Low (single planning context) |
| Adaptability | High (replan after each step) | Low (fixed plan) |
| Latency | Higher (many LLM calls) | Lower (fewer LLM calls) |
| Use Case | Exploratory, adaptive tasks | Predictable, multi-step tasks |
Key Components
1. State Schema
from typing_extensions import TypedDict
class ReWOOState(TypedDict):
task: str # Original user query
plan_string: str # Raw plan from planner
steps: list[tuple[str, str, str, str]] # Parsed: (reasoning, var, tool, args)
results: dict[str, str] # Evidence map: {"#E1": "...", "#E2": "..."}
result: str # Final synthesized answer2. Plan Format
The planner generates plans in this specific format:
Plan: [reasoning for this step]
#E[number] = [ToolName][input]
Plan: [reasoning for next step, can reference previous variables]
#E[number] = [ToolName][input that may include #E1, #E2, etc.]Example:
Plan: Search for information about the 2024 NBA championship
#E1 = Google[2024 NBA championship winner]
Plan: Find out who was the Finals MVP from the search results
#E2 = LLM[According to #E1, who was the Finals MVP?]3. Plan Parsing
The plan is parsed using regex to extract structured steps:
from langgraph_ollama_local.patterns.rewoo import parse_plan
plan_string = """
Plan: Search for NBA information
#E1 = Google[2024 NBA finals winner]
Plan: Analyze the results
#E2 = LLM[Based on #E1, who won?]
"""
steps = parse_plan(plan_string)
# [
# ('Search for NBA information', '#E1', 'Google', '2024 NBA finals winner'),
# ('Analyze the results', '#E2', 'LLM', 'Based on #E1, who won?')
# ]4. Planner Node
Generates complete plan with evidence variables:
from langgraph_ollama_local.patterns.rewoo import create_planner_node
planner = create_planner_node(llm)
state = {"task": "Who won the 2024 NBA championship?"}
result = planner(state)
# result["plan_string"] contains the full plan
# result["steps"] contains parsed steps5. Tool Executor
Executes tools with variable substitution:
from langgraph_ollama_local.patterns.rewoo import create_tool_executor
tools = {
"Google": search_tool,
"Calculator": calc_tool,
}
executor = create_tool_executor(tools, llm)
# If current step is: #E2 = LLM[Analyze #E1]
# And results = {"#E1": "Boston Celtics won"}
# Then the executor will substitute #E1 with actual value:
# "Analyze Boston Celtics won"Variable Substitution:
- Before execution:
#E2 = LLM[Analyze #E1] - After substitution:
#E2 = LLM[Analyze Boston Celtics won the 2024 championship] - Result stored:
{"#E1": "...", "#E2": "The Celtics won..."}
6. Solver Node
Synthesizes final answer from all evidence:
from langgraph_ollama_local.patterns.rewoo import create_solver_node
solver = create_solver_node(llm)
# Takes all evidence (#E1, #E2, ...) and generates final answer
# This is the second and final LLM callUsage
Basic Usage
from langgraph_ollama_local import LocalAgentConfig
from langgraph_ollama_local.patterns.rewoo import (
create_rewoo_graph,
run_rewoo_task,
format_tool_descriptions,
)
from langchain_core.tools import tool
# Setup
config = LocalAgentConfig()
llm = config.create_chat_client()
# Define tools
@tool
def search(query: str) -> str:
"""Search for information."""
# Implementation
return "Search results..."
@tool
def calculator(expression: str) -> str:
"""Calculate mathematical expressions."""
return str(eval(expression))
tools = {
"Google": search,
"Calculator": calculator,
}
# Create graph
graph = create_rewoo_graph(llm, tools)
# Format tool descriptions for planner
tool_descriptions = format_tool_descriptions(tools)
# Run task
result = run_rewoo_task(
graph,
"How many days until the next leap year from 2024?",
tool_descriptions=tool_descriptions
)
print(result["result"])Inspecting the Plan
result = run_rewoo_task(graph, task, tool_descriptions=tool_descriptions)
# View the generated plan
print("Plan:")
print(result["plan_string"])
# View parsed steps
print("\nSteps:")
for reasoning, var, tool, args in result["steps"]:
print(f"{var} = {tool}[{args}]")
print(f" Reasoning: {reasoning}")
# View collected evidence
print("\nEvidence:")
for var, value in result["results"].items():
print(f"{var}: {value[:100]}...")Custom Tool Descriptions
Provide custom descriptions to guide the planner:
tool_descriptions = """
(1) Google[input]: Searches the web for current information.
(2) Calculator[input]: Performs arithmetic calculations.
(3) LLM[input]: Reasons about information and answers questions.
"""
result = run_rewoo_task(
graph,
task,
tool_descriptions=tool_descriptions
)Advanced Patterns
With Real Search Tool
from langchain_community.tools import DuckDuckGoSearchRun
search_tool = DuckDuckGoSearchRun()
tools = {
"Google": search_tool,
}
graph = create_rewoo_graph(llm, tools)
tool_descriptions = format_tool_descriptions(tools)
result = run_rewoo_task(
graph,
"What is the current price of Bitcoin and has it increased this week?",
tool_descriptions=tool_descriptions
)Multi-Step Calculations
task = """
Calculate the following:
1. (25 + 15) × 2
2. Add 10 to the result
3. Divide by 5
What's the final answer?
"""
result = run_rewoo_task(graph, task, tool_descriptions=tool_descriptions)
# The planner will create:
# Plan: Calculate first part
# #E1 = Calculator[(25 + 15) * 2]
#
# Plan: Add 10 to the result
# #E2 = Calculator[#E1 + 10]
#
# Plan: Divide by 5
# #E3 = Calculator[#E2 / 5]Error Handling
The executor handles tool errors gracefully:
# If a tool fails, the error message is stored in results
# The solver receives the error and can work around it
result = run_rewoo_task(graph, task, tool_descriptions=tool_descriptions)
# Check for errors in evidence
for var, value in result["results"].items():
if "Error" in value:
print(f"Tool execution failed for {var}: {value}")Token Efficiency Analysis
For a task requiring 3 tool calls:
ReAct Pattern:
- Think (LLM call)
- Act with Tool 1 (LLM call to decide)
- Observe Tool 1 result
- Think (LLM call)
- Act with Tool 2 (LLM call to decide)
- Observe Tool 2 result
- Think (LLM call)
- Act with Tool 3 (LLM call to decide)
- Observe Tool 3 result
- Final answer (LLM call)
Total: ~7-10 LLM calls
ReWOO Pattern:
- Plan all steps (1 LLM call)
- Execute Tool 1 (no LLM call)
- Execute Tool 2 (no LLM call)
- Execute Tool 3 (no LLM call)
- Synthesize answer (1 LLM call)
Total: 2 LLM calls
Token savings: ~70-80% fewer LLM calls
Best Practices
1. Clear Tool Descriptions
Provide clear, concise tool descriptions:
tool_descriptions = """
(1) Google[input]: Searches web for recent information. Use for facts, news, current events.
(2) Calculator[input]: Evaluates math expressions. Use for any numerical calculations.
(3) Weather[input]: Gets weather for a location. Input should be city name.
(4) LLM[input]: Analyzes information and answers questions. Use to combine evidence.
"""2. Validate Plans
Check that the planner generates valid plans:
from langgraph_ollama_local.patterns.rewoo import parse_plan
result = run_rewoo_task(graph, task, tool_descriptions=tool_descriptions)
if not result["steps"]:
print("Warning: No valid steps parsed from plan")
print("Raw plan:", result["plan_string"])3. Handle Missing Variables
Ensure all referenced variables exist:
# In custom executor, check for undefined variables
def safe_substitute(tool_input, results):
for var in re.findall(r'#E\d+', tool_input):
if var not in results:
return f"Error: {var} not found in results"
# Continue with substitution4. Limit Plan Complexity
For complex tasks, consider breaking into subtasks:
# Instead of one massive plan, break into phases
phase1 = run_rewoo_task(graph, "Gather initial data...", ...)
phase2 = run_rewoo_task(graph, f"Analyze {phase1['result']}...", ...)Integration with Checkpointing
from langgraph.checkpoint.memory import MemorySaver
checkpointer = MemorySaver()
graph = create_rewoo_graph(llm, tools, checkpointer=checkpointer)
# Run with persistence
config = {"configurable": {"thread_id": "rewoo-session-1"}}
result = graph.invoke(initial_state, config=config)Debugging
Enable Verbose Output
# Add print statements to trace execution
def trace_executor(state):
print(f"Executing step: {state['steps'][len(state['results'])]}")
result = executor(state)
print(f"Result: {result}")
return resultInspect Intermediate Results
# After each invocation, check state
for event in graph.stream(initial_state, config=config):
print(event)
# Shows state after each nodeLimitations
- Fixed Plans: Cannot adapt based on tool results
- Error Propagation: Errors in early steps affect later steps
- No Branching: Cannot conditionally execute different paths
- Context Size: Very long plans may exceed context limits
When to Upgrade to Other Patterns
- Need adaptive planning → Use Plan-and-Execute (Tutorial 21)
- Need error recovery → Use Reflexion (Tutorial 23)
- Need exploration → Use LATS (Tutorial 24)
- Need self-improvement → Use Reflection (Tutorial 22)
References
- Paper: ReWOO: Decoupling Reasoning from Observations for Efficient Augmented Language Models
- LangGraph Tutorial: Official ReWOO Implementation
- Related Patterns:
- Plan-and-Execute (Tutorial 21)
- ReAct (Tutorial 02)
Examples
See the complete examples in:
- Tutorial Notebook:
/examples/advanced_reasoning/25_rewoo.ipynb - Pattern Implementation:
/langgraph_ollama_local/patterns/rewoo.py - Tests:
/tests/test_rewoo.py
Quiz
Test your understanding of the ReWOO pattern:
Knowledge Check
What is the main advantage of ReWOO over ReAct for multi-step tasks?
Knowledge Check
What is the purpose of evidence variables like #E1, #E2 in ReWOO plans?
Knowledge Check
When should you NOT use ReWOO?
Knowledge Check
What is the standard plan format that ReWOO uses?
Knowledge Check Fill In
How many LLM calls does ReWOO make for a task requiring 3 tool executions?
Knowledge Check
What should you do if ReWOO is insufficient because you need error recovery or adaptive planning?