Skip to main content
โšก Calmops

Prompt Engineering Patterns: Chain of Thought, ReAct, and Tree of Thoughts

Introduction

Prompt engineering has evolved from simple instructions to sophisticated reasoning frameworks. This guide covers three powerful patterns: Chain of Thought (CoT), ReAct (Reasoning + Acting), and Tree of Thoughts (ToT).

These techniques significantly improve LLM performance on complex tasks requiring multi-step reasoning, tool use, and exploration.

Chain of Thought (CoT) Prompting

CoT encourages LLMs to show their reasoning process, leading to more accurate results.

Zero-Shot CoT

from openai import OpenAI

client = OpenAI()

# Zero-shot CoT - just add "Let's think step by step"
prompt = """Problem: A store sells 3 items for $10. 
If you buy 15 items, how much do you pay?

Let's think step by step."""

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": prompt}]
)

print(response.choices[0].message.content)
# Output:
# First, find the price per item: $10 / 3 = $3.33 per item
# Then multiply by 15: $3.33 ร— 15 = $50
# Answer: $50

Few-Shot CoT

# Few-shot CoT with reasoning examples
few_shot_prompt = """Solve the following math problems step by step.

Problem 1: If a train travels 60 mph for 2 hours, how far does it go?
Solution:
1. Speed = 60 miles per hour
2. Time = 2 hours
3. Distance = Speed ร— Time = 60 ร— 2 = 120 miles
Answer: 120 miles

Problem 2: A farmer has 17 sheep. All but 9 die. How many are left?
Solution:
1. "All but 9" means 9 survive
2. Remaining = 9 sheep
Answer: 9 sheep

Problem 3: {question}
Solution:"""

response = client.chat.completions.create(
    model="gpt-4",
    messages=[{"role": "user", "content": few_shot_prompt}]
)

CoT with Self-Consistency

# Generate multiple reasoning paths and select best
import asyncio

async def cot_self_consistency(question: str, num_samples: int = 5):
    """Generate multiple solutions and use majority vote."""
    
    prompt = f"""Solve this problem step by step:
    
{question}

Show all your reasoning."""

    responses = []
    for _ in range(num_samples):
        # Different temperature for variety
        response = await client.chat.completions.acreate(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.7
        )
        responses.append(extract_answer(response))

    # Majority vote
    from collections import Counter
    most_common = Counter(responses).most_common(1)[0]
    return most_common[0]

# Example
question = "A bat and ball cost $1.10 total. The bat costs $1.00 more than the ball. How much is the ball?"
answer = await cot_self_consistency(question)
print(f"Answer: {answer}")  # $0.05

ReAct: Reasoning + Acting

ReAct combines reasoning traces with actions, enabling LLMs to use tools while showing their thought process.

ReAct Implementation

from typing import List, Dict, Any
import json

class ReActAgent:
    def __init__(self, llm, tools: List[Dict]):
        self.llm = llm
        self.tools = tools
        self.tool_map = {t["name"]: t["function"] for t in tools}
    
    def run(self, question: str, max_iterations: int = 5):
        context = []
        
        for i in range(max_iterations):
            # Create ReAct prompt
            prompt = self._create_prompt(question, context)
            
            response = self.llm.chat.completions.create(
                model="gpt-4",
                messages=[{"role": "user", "content": prompt}]
            )
            
            text = response.choices[0].message.content
            
            # Parse ReAct format
            action, action_input, observation = self._parse_response(text)
            
            if action == "Finish":
                return observation
            
            # Execute action
            if action in self.tool_map:
                result = self.tool_map[action](action_input)
                context.append({
                    "thought": text.split("Action:")[0].split("Thought:")[-1].strip(),
                    "action": action,
                    "action_input": action_input,
                    "observation": result
                })
            else:
                context.append({
                    "thought": text,
                    "observation": "Unknown action"
                })
        
        return "Max iterations reached"
    
    def _create_prompt(self, question: str, context: List):
        context_str = "\n".join([
            f"Thought: {c['thought']}\nAction: {c['action']}\nAction Input: {c['action_input']}\nObservation: {c['observation']}"
            for c in context
        ])
        
        tool_desc = "\n".join([f"- {t['name']}: {t['description']}" for t in self.tools])
        
        return f"""Question: {question}

{context_str}

Available tools:
{tool_desc}

Format your response as:
Thought: [your reasoning]
Action: [tool name or Finish]
Action Input: [input to tool]
Observation: [result]"""

    def _parse_response(self, text: str):
        lines = text.split("\n")
        action = action_input = observation = ""
        
        for line in lines:
            if line.startswith("Thought:"):
                pass  # Already captured
            elif line.startswith("Action:"):
                action = line.split("Action:")[1].strip()
            elif line.startswith("Action Input:"):
                action_input = line.split("Action Input:")[1].strip()
            elif line.startswith("Observation:"):
                observation = line.split("Observation:")[1].strip()
        
        return action, action_input, observation or "No observation"

# Define tools
def search(query: str) -> str:
    """Search the web for information."""
    # Implementation
    return f"Search results for: {query}"

def calculate(expression: str) -> str:
    """Calculate mathematical expression."""
    return str(eval(expression))

tools = [
    {"name": "search", "description": "Search the web", "function": search},
    {"name": "calculate", "description": "Calculate math", "function": calculate}
]

# Run ReAct agent
agent = ReActAgent(client, tools)
result = agent.run("What is the population of Tokyo squared?")

ReAct with Structured Output

# Using ReAct with typed outputs
from pydantic import BaseModel
from typing import Literal

class ReActStep(BaseModel):
    thought: str
    action: Literal["search", "calculate", "finish"]
    action_input: str
    observation: str | None = None

def react_with_parsing(question: str):
    prompt = f"""Solve this problem using the ReAct format.
    
Question: {question}

Available actions: search, calculate, finish

Respond in JSON format:
{{"thought": "...", "action": "...", "action_input": "..."}}"""

    response = client.chat.completions.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"}
    )
    
    step = json.loads(response.choices[0].message.content)
    return step

Tree of Thoughts (ToT)

ToT explores multiple reasoning paths simultaneously, enabling better decision-making for complex problems.

ToT Implementation

import asyncio
from typing import List, Callable

class ToTAgent:
    def __init__(self, llm, num_thoughts: int = 3, max_depth: int = 3):
        self.llm = llm
        self.num_thoughts = num_thoughts
        self.max_depth = max_depth
    
    def generate_thoughts(self, prompt: str) -> List[str]:
        """Generate multiple solution paths."""
        
        full_prompt = f"""Generate {self.num_thoughts} different approaches to solve this problem:

{prompt}

For each approach, provide:
1. The reasoning steps
2. A potential solution
3. Any assumptions made"""

        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": full_prompt}],
            temperature=0.8
        )
        
        # Parse into separate thoughts
        thoughts = response.choices[0].message.content.split("\n\n")
        return thoughts[:self.num_thoughts]
    
    def evaluate_thought(self, thought: str) -> float:
        """Score a thought based on quality."""
        
        eval_prompt = f"""Evaluate this solution approach:

{thought}

Score from 0-10 based on:
- Logical soundness
- Completeness
- Feasibility

Provide just the score and brief reason."""

        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": eval_prompt}],
            temperature=0
        )
        
        # Extract score
        return self._extract_score(response.choices[0].message.content)
    
    def search(self, problem: str) -> str:
        """Perform tree of thoughts search."""
        
        # Initial thought generation
        thoughts = self.generate_thoughts(problem)
        evaluated = [(t, self.evaluate_thought(t)) for t in thoughts]
        
        # Expand best thoughts
        for depth in range(self.max_depth):
            best_thought = max(evaluated, key=lambda x: x[1])[0]
            
            # Expand the best thought
            expansion = self._expand_thought(best_thought)
            new_evaluated = [(t, self.evaluate_thought(t)) for t in expansion]
            
            # Merge with parent score
            parent_score = dict(evaluated).get(best_thought, 0)
            evaluated.extend([(t, (s + parent_score) / 2) for t, s in new_evaluated])
        
        # Return best solution
        return max(evaluated, key=lambda x: x[1])[0]
    
    def _expand_thought(self, thought: str) -> List[str]:
        """Generate variations of a thought."""
        prompt = f"""Expand this solution approach in 3 different ways:

{thought}"""

        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.8
        )
        
        return response.choices[0].message.content.split("\n\n")[:3]
    
    def _extract_score(self, text: str) -> float:
        """Extract numeric score from text."""
        import re
        numbers = re.findall(r'\d+', text)
        if numbers:
            return min(float(numbers[0]), 10) / 10
        return 0.5

ToT for Decision Making

# ToT for complex decisions
class DecisionToT:
    def __init__(self, llm):
        self.llm = llm
    
    def make_decision(self, options: List[str], criteria: List[str]) -> str:
        """Use ToT to make best decision."""
        
        # Generate evaluation tree
        evaluations = []
        
        for option in options:
            for criterion in criteria:
                prompt = f"""Evaluate how well \"{option}\" meets the criterion \"{criterion}\"
                
Provide a score (1-10) and brief justification."""

                response = self.llm.chat.completions.create(
                    model="gpt-4",
                    messages=[{"role": "user", "content": prompt}]
                )
                
                evaluations.append({
                    "option": option,
                    "criterion": criterion,
                    "evaluation": response.choices[0].message.content
                })
        
        # Aggregate scores
        option_scores = {}
        for eval in evaluations:
            score = self._extract_score(eval["evaluation"])
            option_scores[eval["option"]] = option_scores.get(eval["option"], 0) + score
        
        # Return best option
        return max(option_scores, key=option_scores.get)

Combining Patterns

CoT + ReAct + Tool Use

# Advanced: Combine all patterns
class AdvancedReasoningAgent:
    def __init__(self, llm, tools):
        self.llm = llm
        self.tools = tools
    
    def solve(self, problem: str) -> str:
        """Combine CoT reasoning with ReAct and tool use."""
        
        # Step 1: Decompose problem (CoT)
        decomposition = self._decompose(problem)
        
        # Step 2: Solve each subproblem with ReAct
        results = []
        for subproblem in decomposition:
            result = self._solve_with_react(subproblem)
            results.append(result)
        
        # Step 3: Synthesize (CoT)
        final_answer = self._synthesize(results)
        
        return final_answer
    
    def _decompose(self, problem: str) -> List[str]:
        prompt = f"""Break down this problem into smaller, solvable parts:

{problem}

List each subproblem on a separate line."""
        
        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}]
        )
        
        return response.choices[0].message.content.strip().split("\n")
    
    def _solve_with_react(self, subproblem: str) -> str:
        # ReAct implementation (from above)
        return react_solve(subproblem, self.tools)
    
    def _synthesize(self, results: List[str]) -> str:
        prompt = f"""Synthesize these solutions into a final answer:

{chr(10).join(results)}"""

        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}]
        )
        
        return response.choices[0].message.content

Comparison of Patterns

Pattern Best For Complexity Tool Use Parallel
CoT Math, logic Low No No
ReAct Research, QA Medium Yes No
ToT Planning, decisions High Optional Yes

Bad Practices to Avoid

Bad Practice 1: Overcomplicating Simple Tasks

# Bad: Using ToT for simple math
tot = ToTAgent(llm)
result = tot.search("What is 2 + 2?")  # Overkill!

# Good: Use appropriate complexity
if is_simple_math(question):
    result = cot_solve(question)

Bad Practice 2: Infinite Tool Use

# Bad: No limits on ReAct iterations
agent = ReActAgent(llm, tools)
result = agent.run(question)  # Could loop forever!

# Good: Set clear limits
agent = ReActAgent(llm, tools, max_iterations=5)

Bad Practice 3: Ignoring Token Limits

# Bad: Very deep ToT tree
tot = ToTAgent(llm, max_depth=10)  # Expensive!

# Good: Limit depth based on complexity
depth = min(3, estimate_complexity(problem))
tot = ToTAgent(llm, max_depth=depth)

Good Practices Summary

When to Use Each Pattern

Task Type Pattern Example
Math/Logic CoT “2 + 2 = 4, so…”
Research ReAct Search, then answer
Planning ToT Multiple options
Complex Combined Decompose โ†’ ReAct โ†’ Synthesize

Prompt Templates

# CoT template
COT_PROMPT = """Solve step by step:
{question}

Show your reasoning at each step."""

# ReAct template
REACT_PROMPT = """Available tools: {tools}
Question: {question}

Think step by step, using tools when needed."""

# ToT template
TOT_PROMPT = """Generate {n} different approaches:
{question}

Evaluate each approach and pick the best."""

External Resources

Comments