Building AI Agents That Actually Work: 5 Architecture Patterns with Code Examples
Learn five practical AI agent architecture patterns - from simple reactive agents to sophisticated multi-agent systems. Includes Python examples and real-world use cases for each pattern.
Building AI Agents That Actually Work: 5 Architecture Patterns with Code Examples
AI agents are everywhere, but most implementations fall into predictable patterns. Understanding these patterns helps you choose the right architecture for your specific use case and avoid common pitfalls.
Here are five proven patterns, each with strengths, weaknesses, and practical examples.
1. Reactive Agent Pattern
The simplest pattern: respond to inputs without internal state or memory.
class ReactiveAgent:
def __init__(self, llm_client):
self.llm = llm_client
def respond(self, user_input):
prompt = f"User says: {user_input}\nRespond helpfully:"
return self.llm.generate(prompt)
Best for: Chatbots, content generation, simple Q&A Pros: Fast, stateless, easy to scale Cons: No memory, can't handle complex multi-step tasks
2. State Machine Agent Pattern
Agents that move through defined states based on inputs and conditions.
class OrderAgent:
def __init__(self):
self.state = "greeting"
self.order_data = {}
def process(self, user_input):
if self.state == "greeting":
return self._handle_greeting(user_input)
elif self.state == "taking_order":
return self._handle_order(user_input)
elif self.state == "confirming":
return self._handle_confirmation(user_input)
def _handle_greeting(self, input_text):
self.state = "taking_order"
return "What would you like to order?"
def _handle_order(self, input_text):
# Extract items from input_text
self.order_data['items'] = self._extract_items(input_text)
self.state = "confirming"
return f"Got it: {self.order_data['items']}. Confirm?"
Best for: Structured workflows, customer service, form filling Pros: Predictable behavior, easy to debug Cons: Rigid, requires predefined states
3. Planning Agent Pattern
Agents that break down complex goals into actionable steps.
class PlanningAgent:
def __init__(self, llm_client, tools):
self.llm = llm_client
self.tools = tools
def execute_goal(self, goal):
# Step 1: Generate plan
plan = self._create_plan(goal)
# Step 2: Execute each step
results = []
for step in plan:
result = self._execute_step(step)
results.append(result)
# Re-plan if needed
if result.get('failed'):
plan = self._replan(goal, results)
return results
def _create_plan(self, goal):
prompt = f"""
Goal: {goal}
Available tools: {list(self.tools.keys())}
Create a step-by-step plan:
"""
response = self.llm.generate(prompt)
return self._parse_plan(response)
Best for: Research tasks, data analysis, complex automation Pros: Handles complex goals, adapts to failures Cons: Slower, can get stuck in planning loops
4. Tool-Using Agent Pattern
Agents that can call external functions and APIs to accomplish tasks.
class ToolAgent:
def __init__(self, llm_client):
self.llm = llm_client
self.tools = {
"search_web": self._search_web,
"send_email": self._send_email,
"get_weather": self._get_weather
}
def respond(self, user_input):
# Decide which tool to use
tool_choice = self._choose_tool(user_input)
if tool_choice == "none":
return self._generate_response(user_input)
# Execute tool
tool_result = self.tools[tool_choice](user_input)
# Generate response using tool result
return self._generate_response_with_context(user_input, tool_result)
def _choose_tool(self, user_input):
prompt = f"""
User input: {user_input}
Available tools: {list(self.tools.keys())}
Which tool should be used? Return tool name or 'none'.
"""
return self.llm.generate(prompt).strip()
def _search_web(self, query):
# Implementation for web search
pass
Best for: Personal assistants, customer support, information retrieval Pros: Very capable, can interact with external systems Cons: Security concerns, tool selection can be unreliable
5. Multi-Agent System Pattern
Multiple specialized agents working together on complex tasks.
class MultiAgentSystem:
def __init__(self):
self.researcher = ResearchAgent()
self.writer = WriterAgent()
self.reviewer = ReviewAgent()
self.coordinator = CoordinatorAgent()
def create_report(self, topic):
# Coordinator decides task flow
plan = self.coordinator.plan_report_creation(topic)
# Research phase
research_data = self.researcher.gather_information(topic)
# Writing phase
draft = self.writer.create_draft(topic, research_data)
# Review phase
feedback = self.reviewer.review_draft(draft)
# Revision if needed
if feedback.get('needs_revision'):
final_draft = self.writer.revise(draft, feedback)
else:
final_draft = draft
return final_draft
class ResearchAgent:
def gather_information(self, topic):
# Specialized research logic
return {"sources": [...], "key_facts": [...]}
Best for: Content creation, complex analysis, software development Pros: Specialized expertise, can handle very complex tasks Cons: Complex coordination, potential conflicts between agents
Choosing the Right Pattern
Pattern Complexity Speed Memory Best Use Case Reactive Low Fast None Simple chat, content generation State Machine Medium Fast Session Structured workflows Planning High Slow Task context Complex goal achievement Tool-Using Medium Medium Tool context Interactive assistants Multi-Agent Very High Slow System-wide Enterprise automationReal-World Example: Customer Support Agent
Here's how you might combine patterns for a customer support system:
class CustomerSupportAgent:
def __init__(self):
# State machine for conversation flow
self.conversation_state = "greeting"
# Tools for external operations
self.tools = {
"lookup_order": OrderLookupTool(),
"create_ticket": TicketTool(),
"escalate": EscalationTool()
}
# Planning for complex issues
self.planner = PlanningAgent()
def handle_customer_message(self, message, customer_id):
# Simple reactive response for greetings
if self.conversation_state == "greeting":
self.conversation_state = "helping"
return "Hi! How can I help you today?"
# Tool usage for specific requests
if "order" in message.lower():
order_info = self.tools["lookup_order"].execute(customer_id)
return f"Here's your order status: {order_info}"
# Planning for complex issues
if "problem" in message.lower():
resolution_plan = self.planner.create_resolution_plan(message)
return self._execute_resolution(resolution_plan)
# Default reactive response
return self._generate_helpful_response(message)
This hybrid approach gives you the speed of reactive responses, the structure of state machines, the power of tools, and the sophistication of planning when needed.
Key Takeaways
- Start simple - Begin with reactive agents and add complexity only when needed
- Match patterns to use cases - Don't use planning agents for simple Q&A
- Combine patterns - Real systems often use multiple patterns together
- Focus on reliability - Complex agents fail in complex ways
- Test thoroughly - Agent behavior can be unpredictable
The goal isn't to build the most sophisticated agent possible, but to build one that reliably solves your specific problem. Choose patterns that match your requirements, not your ambitions.