Different agent architectures excel at different tasks. Understanding each type's strengths and weaknesses is crucial for building effective AI systems. This guide covers the major agent patterns and when to use each one.
ReAct agents interleave reasoning and action-taking, creating a dynamic problem-solving approach. They think about what to do, take action, observe results, and adjust their approach based on feedback.
# ReAct Agent Implementation
class ReActAgent:
def solve(self, question):
max_iterations = 5
for i in range(max_iterations):
# Thought: Reason about what to do
thought = self.think(question, self.history)
print(f"Thought {i+1}: {thought}")
# Action: Decide and execute action
action = self.decide_action(thought)
print(f"Action {i+1}: {action}")
# Observation: Get results
observation = self.execute_action(action)
print(f"Observation {i+1}: {observation}")
# Check if task is complete
if self.is_complete(observation):
return self.format_answer(observation)
# Update history for next iteration
self.history.append({
"thought": thought,
"action": action,
"observation": observation
})
return "Max iterations reached"
# Example execution:
"""
Question: What is the population of Tokyo's largest district?
Thought 1: I need to find information about Tokyo's districts
Action 1: search("Tokyo largest district by area")
Observation 1: Εta is Tokyo's largest district by area (60.83 kmΒ²)
Thought 2: Now I need to find the population of Εta district
Action 2: search("Εta district Tokyo population")
Observation 2: Εta has a population of approximately 734,000 (2023)
Thought 3: I have the answer - Εta district has 734,000 people
Action 3: return_answer("734,000")
"""
Chain of Thought agents break down complex problems into a series of intermediate reasoning steps, making their thought process explicit and verifiable. They excel at mathematical problems, logical reasoning, and analytical tasks.
# Chain of Thought Agent
class CoTAgent:
def __init__(self, model):
self.model = model
self.cot_prompt = """
Let's approach this step-by-step:
1. First, identify what we're asked to find
2. Break down the problem into components
3. Solve each component
4. Combine results for the final answer
"""
def solve_with_cot(self, problem):
prompt = f"""
Problem: {problem}
{self.cot_prompt}
Step-by-step solution:
"""
response = self.model.generate(prompt)
return response
# Example: Math Word Problem
"""
Problem: A bakery sells cupcakes for $3 each and cookies for $2 each.
Sarah buys 5 cupcakes and 8 cookies. If she pays with a $50 bill,
how much change does she get?
Step-by-step solution:
1. Calculate cost of cupcakes: 5 Γ $3 = $15
2. Calculate cost of cookies: 8 Γ $2 = $16
3. Calculate total cost: $15 + $16 = $31
4. Calculate change: $50 - $31 = $19
Answer: Sarah gets $19 in change.
"""
# Zero-Shot CoT
zero_shot_prompt = "Let's think step by step."
# Few-Shot CoT with examples
few_shot_prompt = """
Example 1: [Problem] β [Step-by-step solution]
Example 2: [Problem] β [Step-by-step solution]
Now solve: [New Problem]
"""
Tool-Use agents can interact with external tools, APIs, and functions to extend their capabilities beyond text generation. They decide when and how to use tools to accomplish tasks.
# Tool-Use Agent Implementation
from typing import Dict, List, Any
import json
class ToolUseAgent:
def __init__(self, llm, tools: Dict[str, callable]):
self.llm = llm
self.tools = tools
self.tool_descriptions = self._generate_tool_descriptions()
def _generate_tool_descriptions(self):
descriptions = []
for name, func in self.tools.items():
descriptions.append({
"name": name,
"description": func.__doc__,
"parameters": self._extract_parameters(func)
})
return descriptions
def process(self, user_input: str):
# Step 1: Determine if tools are needed
system_prompt = f"""
You have access to these tools:
{json.dumps(self.tool_descriptions, indent=2)}
For the user's request, determine:
1. Which tool(s) to use
2. What parameters to pass
3. In what order to use them
Respond in JSON format:
{{"tools": [{{"name": "tool_name", "params": {{...}}}}]}}
"""
plan = self.llm.generate(system_prompt, user_input)
tool_calls = json.loads(plan)["tools"]
# Step 2: Execute tools
results = []
for call in tool_calls:
tool_name = call["name"]
params = call["params"]
if tool_name in self.tools:
result = self.tools[tool_name](**params)
results.append({
"tool": tool_name,
"result": result
})
# Step 3: Synthesize final response
final_prompt = f"""
User request: {user_input}
Tool results: {json.dumps(results, indent=2)}
Provide a comprehensive response based on the tool results.
"""
return self.llm.generate(final_prompt)
# Example tools
def web_search(query: str) -> str:
"""Search the web for information"""
# Implementation
return f"Search results for: {query}"
def calculator(expression: str) -> float:
"""Evaluate mathematical expressions"""
return eval(expression) # Note: Use safe eval in production
def send_email(to: str, subject: str, body: str) -> bool:
"""Send an email"""
# Implementation
return True
# Usage
agent = ToolUseAgent(
llm=language_model,
tools={
"web_search": web_search,
"calculator": calculator,
"send_email": send_email
}
)
response = agent.process("Find the latest Tesla stock price and calculate 15% of it")
Tree of Thoughts agents explore multiple reasoning paths simultaneously, evaluating different approaches before committing to the best solution. They maintain a search tree of partial solutions.
# Tree of Thoughts Implementation
class TreeOfThoughts:
def __init__(self, llm, max_depth=3, beam_width=3):
self.llm = llm
self.max_depth = max_depth
self.beam_width = beam_width
def solve(self, problem):
# Initialize root node
root = {"state": problem, "value": 0, "children": []}
# Build tree
self._expand_node(root, depth=0)
# Find best path
best_path = self._find_best_path(root)
return best_path
def _expand_node(self, node, depth):
if depth >= self.max_depth:
return
# Generate multiple next steps
prompt = f"""
Current state: {node['state']}
Generate {self.beam_width} different approaches to proceed.
"""
approaches = self.llm.generate(prompt)
# Evaluate each approach
for approach in approaches:
child = {
"state": approach,
"value": self._evaluate(approach),
"children": []
}
node["children"].append(child)
# Recursively expand promising nodes
if child["value"] > threshold:
self._expand_node(child, depth + 1)
def _evaluate(self, state):
"""Evaluate the promise of a partial solution"""
prompt = f"""
Evaluate this approach on a scale of 0-10:
{state}
Consider: correctness, efficiency, completeness
"""
score = self.llm.generate(prompt)
return float(score)
def _find_best_path(self, root):
"""DFS to find the highest-scoring complete path"""
# Implementation of path finding
pass
# Example: Creative Writing with ToT
"""
Problem: Write an engaging opening for a mystery novel
Branch 1: Start with dialogue
β "The body's been here for three days," the detective said.
β Score: 7/10
Branch 2: Start with setting description
β The lighthouse stood abandoned on the cliff...
β Score: 6/10
Branch 3: Start with action
β Sarah ran through the rain-soaked streets...
β Score: 8/10
Selected: Branch 3, then continue exploring...
"""
Plan-and-Execute agents first create a comprehensive plan, then systematically execute each step. They separate the planning phase from execution, allowing for more strategic approaches.
# Plan-and-Execute Agent
class PlanAndExecuteAgent:
def __init__(self, planner_llm, executor_llm, tools):
self.planner = planner_llm
self.executor = executor_llm
self.tools = tools
def solve(self, objective):
# Phase 1: Planning
plan = self.create_plan(objective)
print("Generated Plan:")
for i, step in enumerate(plan, 1):
print(f" {i}. {step}")
# Phase 2: Execution
results = []
for step in plan:
result = self.execute_step(step, results)
results.append(result)
# Re-plan if needed
if self.should_replan(result):
remaining_objective = self.get_remaining_objective(
objective, results
)
plan = self.create_plan(remaining_objective)
return self.synthesize_results(results)
def create_plan(self, objective):
prompt = f"""
Objective: {objective}
Create a detailed step-by-step plan to achieve this objective.
Each step should be specific and actionable.
Output as a JSON list of steps.
"""
plan_json = self.planner.generate(prompt)
return json.loads(plan_json)
def execute_step(self, step, previous_results):
prompt = f"""
Execute this step: {step}
Previous results: {previous_results}
Available tools: {list(self.tools.keys())}
"""
return self.executor.generate(prompt)
# Example execution:
"""
Objective: Create a market analysis report for electric vehicles
Generated Plan:
1. Research current EV market size and growth rate
2. Identify top 5 EV manufacturers and their market share
3. Analyze consumer sentiment and adoption barriers
4. Research government policies and incentives
5. Project future trends for next 5 years
6. Compile findings into structured report
Executing step 1: Research current EV market size...
Executing step 2: Identify top manufacturers...
[continues...]
"""
Reflexion agents can reflect on their failures, learn from mistakes, and improve their performance over successive attempts. They maintain a memory of past attempts and reflections.
# Reflexion Agent Implementation
class ReflexionAgent:
def __init__(self, llm, evaluator):
self.llm = llm
self.evaluator = evaluator
self.memory = []
self.max_iterations = 3
def solve(self, task):
for iteration in range(self.max_iterations):
# Generate solution
solution = self.generate_solution(task)
# Evaluate solution
evaluation = self.evaluator.evaluate(solution, task)
if evaluation["success"]:
return solution
# Reflect on failure
reflection = self.reflect(task, solution, evaluation)
# Store in memory
self.memory.append({
"attempt": iteration + 1,
"solution": solution,
"evaluation": evaluation,
"reflection": reflection
})
return self.best_attempt()
def generate_solution(self, task):
prompt = f"""
Task: {task}
Previous attempts and reflections:
{self.format_memory()}
Generate an improved solution based on past learnings.
"""
return self.llm.generate(prompt)
def reflect(self, task, solution, evaluation):
prompt = f"""
Task: {task}
Attempted solution: {solution}
Evaluation: {evaluation}
Reflect on what went wrong and how to improve:
1. What specific errors were made?
2. What assumptions were incorrect?
3. What approach should be tried next?
"""
return self.llm.generate(prompt)
# Example: Code debugging with Reflexion
"""
Iteration 1:
Solution: def factorial(n): return n * factorial(n-1)
Evaluation: Failed - No base case, infinite recursion
Reflection: Need to add base case for n=0 or n=1
Iteration 2:
Solution: def factorial(n):
if n == 0: return 1
return n * factorial(n-1)
Evaluation: Success - Correctly handles all test cases
"""
Modern AI systems often combine multiple agent patterns for optimal performance. Here are common hybrid approaches:
Combines structured reasoning with action-taking for complex problem-solving.
# Hybrid ReAct-CoT Agent
class ReActCoTAgent:
def process(self, query):
# Use CoT for initial reasoning
thought_chain = self.chain_of_thought(query)
# Use ReAct for execution
for thought in thought_chain:
action = self.determine_action(thought)
observation = self.execute(action)
# Refine reasoning based on observation
thought = self.refine_thought(thought, observation)
return self.synthesize_answer()
Creates strategic plans that involve tool usage for information gathering and action execution.
Explores multiple paths while learning from failed branches to improve future explorations.
| Agent Type | Best For | Avoid When | Complexity | Cost |
|---|---|---|---|---|
| ReAct | Multi-step tasks, Research | Simple queries | Medium | High |
| Chain of Thought | Math, Logic puzzles | Creative tasks | Low | Medium |
| Tool-Use | Real-world actions | Pure reasoning | High | Variable |
| Tree of Thoughts | Complex planning | Time-sensitive tasks | Very High | Very High |
| Plan-and-Execute | Project management | Dynamic environments | Medium | Medium |
| Reflexion | Iterative improvement | One-shot tasks | Medium | High |