Tree-of-Thought¶
Overview¶
Tree-of-Thought (ToT) extends Chain-of-Thought by exploring multiple reasoning branches simultaneously, then selecting the most promising path.
Instead of one linear path, the agent considers many possibilities and picks the best one.
-
CoT vs ToT¶
Chain-of-Thought (Linear)¶
Question: "What's a good name for a startup?"
Single path:
→ Generate 1 name
→ Evaluate it
→ Return result
Problem: Only one idea explored
Quality: Medium (depends on first idea)
Tree-of-Thought (Branching)¶
Question: "What's a good name for a startup?"
Multiple paths:
Root [Generate names]
/| \
Branch1 Branch2 Branch3
"TechFlow" "DataSy" "CloudSync"
/| \
Evaluate Evaluate Evaluate
Good Bad Better
Result: "CloudSync" (best option)
Benefit: Multiple ideas explored, best selected
Quality: Higher (picks best from many)
How Tree-of-Thought Works¶
4-Step Process¶
Step 1: Generate Candidates
LLM creates multiple possible approaches
Example: 5 different solutions
Step 2: Evaluate Each Branch
Rate each approach on criteria
Example: Calculate success probability
Step 3: Prune Weak Branches
Remove unlikely candidates
Example: Keep top 3 of 5
Step 4: Explore Winners
Deep-dive on promising branches
Example: Develop the 3 finalists
Result: Return best path
-
Implementation¶
Basic Tree-of-Thought¶
class TreeOfThoughtReasoner:
def reason(self, question: str, branching_factor: int = 3, depth: int = 3) -> dict:
"""Explore multiple reasoning paths"""
# Step 1: Initialize root
root = {
'question': question,
'state': 'initial',
'children': [],
'score': None
}
# Step 2: Build tree by BFS
queue = [root]
level = 0
while queue and level < depth:
next_level = []
for node in queue:
# Generate branching_factor children
children = self.generate_candidates(node, count=branching_factor)
for child in children:
# Score each child
child['score'] = self.evaluate_candidate(child)
node['children'].append(child)
next_level.append(child)
# Prune weak branches (keep top half)
next_level = sorted(next_level, key=lambda x: x['score'], reverse=True)
next_level = next_level[:len(next_level)//2]
queue = next_level
level += 1
# Step 3: Find best path
best_path = self.find_best_path(root)
return {
'answer': best_path[-1]['content'],
'path': best_path,
'score': best_path[-1]['score'],
'explored_paths': self.count_paths(root)
}
def generate_candidates(self, node: dict, count: int = 3) -> List[dict]:
"""Generate candidate next steps"""
candidates = self.llm.generate(f"""
Current state: {node['state']}
Question: {node['question']}
Generate {count} different next steps or approaches:
""")
return [
{
'content': cand,
'state': node['state'] + ' -> ' + cand,
'parent': node,
'children': [],
'score': None
}
for cand in candidates
]
def evaluate_candidate(self, candidate: dict) -> float:
"""Score a candidate step"""
score = self.llm.evaluate(f"""
Evaluate this step on a scale of 0-100:
{candidate['content']}
Consider: feasibility, relevance, quality
Return just a number.
""")
return float(score)
def find_best_path(self, root: dict) -> List[dict]:
"""Find path with highest score"""
def traverse(node):
if not node['children']:
return [node] # Leaf node
# Recursively find best path in children
best_child_path = None
best_child_score = -1
for child in node['children']:
child_path = traverse(child)
child_score = child_path[-1]['score']
if child_score > best_child_score:
best_child_score = child_score
best_child_path = child_path
return [node] + best_child_path
return traverse(root)
def count_paths(self, node: dict) -> int:
"""Count total paths explored"""
if not node['children']:
return 1
total = 0
for child in node['children']:
total += self.count_paths(child)
return total
# Usage
reasoner = TreeOfThoughtReasoner()
result = reasoner.reason(
question="Design an architecture for a real-time chat application",
branching_factor=3, # Explore 3 options at each step
depth=3 # Explore 3 levels deep
)
print(f"Best solution: {result['answer']}")
print(f"Score: {result['score']}")
print(f"Paths explored: {result['explored_paths']}")
-
ToT Strategies¶
Strategy 1: Breadth-First Exploration¶
Level 1: [Question]
/| \
Level 2: A B C (Generate 3 options)
|||
Level 3: A1 B1 C1 (Expand each)
Pros: Good for finding diverse solutions Cons: Expensive (many branches)
Strategy 2: Best-First Exploration¶
Level 1: [Question]
/| \
Level 2: A B C (Score: A=90, B=70, C=50)
↓
Only expand A
/| \
Level 3: A1 A2 A3 (Explore best option)
Pros: Efficient (focuses on promising branches) Cons: May miss good alternatives
Strategy 3: Iterative Deepening¶
Iteration 1: Explore depth 1 (quick)
Iteration 2: Explore depth 2 (slower)
Iteration 3: Explore depth 3 (most thorough)
Pros: Adaptive (fast if answer found early) Cons: Redundant exploration
Real-World Example¶
class ChessAgentWithToT:
"""Chess AI using Tree-of-Thought for move selection"""
def select_move(self, board_state: dict) -> str:
"""Select best move by exploring move tree"""
# Generate candidate moves
candidates = self.generate_legal_moves(board_state)
# [move1, move2, move3,...]
# Build tree of positions
tree = self.build_move_tree(board_state, candidates, depth=4)
# Evaluate each move
for move in candidates:
# Simulate move and evaluate resulting position
new_state = self.apply_move(board_state, move)
# Score the position (material count, piece safety, etc.)
score = self.evaluate_position(new_state)
move['score'] = score
# Select highest-scoring move
best_move = max(candidates, key=lambda x: x['score'])
return best_move
class CustomerServiceAgentWithToT:
"""Customer service using ToT to find best resolution"""
def resolve_issue(self, issue: str) -> dict:
"""Resolve customer issue by exploring options"""
# Generate possible solutions
solutions = [
"Provide refund",
"Send replacement",
"Offer discount",
"Escalate to manager"
]
# Evaluate each solution
evaluations = {}
for solution in solutions:
# Score based on: customer satisfaction, cost, efficiency
score = self.evaluate_solution(issue, solution)
evaluations[solution] = score
# Select best solution
best = max(evaluations.items(), key=lambda x: x[1])
return {
'issue': issue,
'solution': best[0],
'confidence': best[1],
'alternatives': sorted(evaluations.items(), key=lambda x: x[1], reverse=True)
}
ToT Warnings¶
Warning 1: Exponential Cost¶
# EXPENSIVE
branching_factor = 5 # 5 options at each level
depth = 5 # 5 levels deep
total_nodes = 5^5 = 3,125 nodes
cost = 3,125 × llm_cost_per_call = VERY EXPENSIVE
# SMART
branching_factor = 3 # Fewer options
depth = 3 # Fewer levels
pruning = 50% # Remove weak branches
total_nodes = ~50 nodes (with pruning)
cost = 50 × llm_cost_per_call = reasonable
Key Lesson: Be aggressive with pruning.
Warning 2: Pruning Too Early¶
# WRONG
candidates = generate_options()
quick_score = first_impression_score(candidates) # Biased!
keep_only_top_1(candidates) # Might discard good options
# RIGHT
candidates = generate_options()
thorough_score = comprehensive_evaluation(candidates) # Better
keep_top_3(candidates) # Keep promising ones
Key Lesson: Evaluation quality matters for pruning.
Warning 3: Over-Confidence in Tree Result¶
# WRONG
result = tree_reasoner.reason(question)
answer = result['answer'] # Trust blindly
# The issue
# doesn't mean the best is correct
# RIGHT
result = tree_reasoner.reason(question)
answer = result['answer']
verify_answer = verify_independently(answer)
if verify_answer != answer:
# Re-explore with different scoring
result2 = tree_reasoner.reason(question, depth=4)
Key Lesson: Exploration helps but doesn't guarantee correctness.
Best Practices¶
1. Adaptive Depth¶
# Good
complexity = assess_question_complexity(question)
if complexity == "simple":
depth = 1 # Quick answer
elif complexity == "moderate":
depth = 2 # Balanced
else:
depth = 3 # Thorough
result = reasoner.reason(question, depth=depth)
# Bad
result = reasoner.reason(question, depth=3) # Overkill for simple
2. Smart Pruning¶
# Good
def should_prune(candidate):
if candidate['feasibility'] < 0.3:
return True # Not feasible
if candidate['relevance'] < 0.4:
return True # Not relevant
return False # Keep it
keep_candidates = [c for c in candidates if not should_prune(c)]
# Bad
keep_candidates = [c for c in candidates if c['score'] > threshold]
3. Cost Control¶
# Good
cost_budget = 1000 tokens
cost_used = 0
for candidate in candidates:
cost_estimate = estimate_evaluation_cost(candidate)
if cost_used + cost_estimate > cost_budget:
break # Stop if over budget
score = evaluate(candidate)
cost_used += cost_estimate
# Bad
for candidate in candidates:
score = evaluate(candidate) # Could be expensive
ToT vs CoT Performance¶
Problem Type| CoT Accuracy| ToT Accuracy| Speedup
Simple Arithmetic| 95%| 96%| 1x
Multi-step Logic| 80%| 92%| 2-3x
Complex Planning| 60%| 85%| 3-5x
Creative Solutions| 70%| 90%| 2-4x
Key Insight: ToT shines on problems where exploration helps.
Key Takeaways¶
- ToT explores multiple branches - Better than single path
- Expensive if not careful - Use pruning and depth limits
- Good evaluation critical - Pruning quality determines result
- Best for hard problems - Overkill for trivial questions
- Adapt to complexity - Match depth to problem difficulty
- Verify answers - Exploration doesn't guarantee correctness
- Monitor cost - Keep token usage reasonable
-
Next Steps¶
- Read Goal Oriented Reasoning - Reasoning toward objectives
- Read Adaptive Planning - Adjusting plans dynamically
-
Last Updated: August 9, 2026