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 |