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
- Chain of Thought Paper
- ReAct Paper
- Tree of Thoughts Paper
- LangChain CoT Examples
- Prompt Engineering Guide
- Anyscale Prompt Engineering
- OpenAI Function Calling
- ReAct with LangChain
Comments