Core Components of an AI Agent
Architecture Overview
Every AI agent consists of several fundamental components that work together:
┌─────────────────────────────────────────┐
│ Agent Controller │
│ (Orchestration & Decision Making) │
└─────────────────┬───────────────────────┘
│
┌─────────────┼─────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ Memory │ │ Tools │ │ LLM │
│ System │ │ Executor│ │ Backend │
└─────────┘ └─────────┘ └─────────┘
1. Language Model (LLM) Backend
The brain of the agent that provides reasoning capabilities.
Key Responsibilities
- Understand user intent
- Generate reasoning chains
- Decide which tools to use
- Produce natural language responses
Important Considerations
class LLMBackend:
def __init__(self, model_name, temperature=0.7):
self.model = model_name
self.temperature = temperature # Controls randomness
def generate(self, prompt, max_tokens=1000):
"""Generate completion from the model"""
pass
Temperature Settings:
- Low (0.0-0.3): Deterministic, focused (good for tool selection)
- Medium (0.4-0.7): Balanced (good for general reasoning)
- High (0.8-1.0): Creative, diverse (good for brainstorming)
Model Selection:
- GPT-4/Claude Opus: Complex reasoning, expensive
- GPT-3.5/Claude Sonnet: Good balance of cost and capability
- Smaller models: Fast, cheap, limited reasoning
2. Tool System
Enables the agent to interact with external systems.
Tool Definition
from typing import Callable, Dict, Any
class Tool:
def __init__(
self,
name: str,
description: str,
function: Callable,
parameters: Dict[str, Any]
):
self.name = name
self.description = description
self.function = function
self.parameters = parameters
def execute(self, **kwargs):
"""Execute the tool with given parameters"""
return self.function(**kwargs)
Example Tools
# Calculator Tool
def calculator(expression: str) -> float:
"""
Evaluates a mathematical expression safely.
Args:
expression: A mathematical expression string (e.g., "2 + 2 * 3")
Returns:
The result of the calculation
"""
import ast
import operator
# Safe evaluation without exec/eval
ops = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
}
# Implementation...
return result
# Web Search Tool
def web_search(query: str, num_results: int = 5) -> list:
"""
Search the web for information.
Args:
query: Search query string
num_results: Number of results to return
Returns:
List of search results with title, snippet, and URL
"""
# Implementation using search API
pass
# Database Query Tool
def query_database(sql: str) -> list:
"""
Execute a read-only SQL query.
Args:
sql: SELECT query to execute
Returns:
Query results as list of dictionaries
"""
# Implementation with safety checks
pass
Tool Registry
class ToolRegistry:
def __init__(self):
self.tools: Dict[str, Tool] = {}
def register(self, tool: Tool):
"""Register a new tool"""
self.tools[tool.name] = tool
def get_tool(self, name: str) -> Tool:
"""Retrieve a tool by name"""
return self.tools.get(name)
def get_tool_descriptions(self) -> str:
"""Get formatted descriptions for LLM prompt"""
descriptions = []
for tool in self.tools.values():
descriptions.append(
f"- {tool.name}: {tool.description}\n"
f" Parameters: {tool.parameters}"
)
return "\n".join(descriptions)
3. Memory System
Enables agents to maintain context and learn from interactions.
Types of Memory
Short-Term Memory (Working Memory)
Holds information for the current task/conversation.
class ShortTermMemory:
def __init__(self, max_tokens=4000):
self.messages = []
self.max_tokens = max_tokens
def add_message(self, role: str, content: str):
"""Add a message to short-term memory"""
self.messages.append({"role": role, "content": content})
self._trim_if_needed()
def get_context(self) -> list:
"""Get all messages in context window"""
return self.messages
def _trim_if_needed(self):
"""Remove old messages if token limit exceeded"""
while self._count_tokens() > self.max_tokens:
self.messages.pop(0)
Long-Term Memory (Persistent Storage)
Stores information across sessions.
class LongTermMemory:
def __init__(self, vector_db):
self.vector_db = vector_db
def store(self, content: str, metadata: dict):
"""Store information with embeddings"""
embedding = self._get_embedding(content)
self.vector_db.insert(embedding, content, metadata)
def retrieve(self, query: str, top_k=5) -> list:
"""Retrieve relevant memories based on query"""
query_embedding = self._get_embedding(query)
return self.vector_db.search(query_embedding, top_k)
def _get_embedding(self, text: str):
"""Convert text to vector embedding"""
# Use embedding model (OpenAI, Sentence Transformers, etc.)
pass
Episodic Memory
Stores specific experiences and events.
class EpisodicMemory:
def __init__(self):
self.episodes = []
def add_episode(self, state, action, result, timestamp):
"""Record an episode"""
self.episodes.append({
"state": state,
"action": action,
"result": result,
"timestamp": timestamp
})
def get_similar_episodes(self, current_state) -> list:
"""Find similar past experiences"""
# Compare current state to past states
pass
4. Planning & Reasoning Module
Breaks down complex tasks and decides action sequences.
Task Decomposition
class Planner:
def __init__(self, llm):
self.llm = llm
def create_plan(self, goal: str) -> list:
"""Break down goal into subtasks"""
prompt = f"""
Break down the following goal into specific, actionable steps:
Goal: {goal}
Provide a numbered list of steps.
"""
response = self.llm.generate(prompt)
return self._parse_steps(response)
def _parse_steps(self, response: str) -> list:
"""Extract steps from LLM response"""
# Parse numbered list
pass
Decision Making
class DecisionMaker:
def select_action(
self,
current_state: dict,
available_actions: list,
goal: str
) -> str:
"""Choose best action given current state"""
# Evaluate each action
scores = []
for action in available_actions:
score = self._evaluate_action(action, current_state, goal)
scores.append((action, score))
# Select highest scoring action
best_action = max(scores, key=lambda x: x[1])[0]
return best_action
def _evaluate_action(self, action, state, goal) -> float:
"""Score how well action advances toward goal"""
# Use heuristics or LLM evaluation
pass
5. Observation & Perception
Processes inputs from the environment.
class Observer:
def __init__(self):
self.sensors = []
def observe(self) -> dict:
"""Gather current state information"""
observations = {}
# Collect from various sources
observations['user_input'] = self._get_user_input()
observations['tool_results'] = self._get_tool_results()
observations['system_state'] = self._get_system_state()
return observations
def _get_user_input(self):
"""Process user messages"""
pass
def _get_tool_results(self):
"""Collect results from tool executions"""
pass
6. Action Executor
Carries out actions determined by the agent.
class ActionExecutor:
def __init__(self, tool_registry: ToolRegistry):
self.tools = tool_registry
def execute(self, action: dict) -> dict:
"""Execute an action and return result"""
action_type = action['type']
if action_type == 'tool_use':
return self._execute_tool(action)
elif action_type == 'respond':
return self._generate_response(action)
else:
raise ValueError(f"Unknown action type: {action_type}")
def _execute_tool(self, action: dict) -> dict:
"""Execute a tool call"""
tool_name = action['tool']
parameters = action['parameters']
tool = self.tools.get_tool(tool_name)
result = tool.execute(**parameters)
return {
'success': True,
'result': result
}
7. Controller / Orchestrator
Coordinates all components and implements the agent loop.
class AgentController:
def __init__(
self,
llm,
tools: ToolRegistry,
memory: ShortTermMemory,
planner: Planner
):
self.llm = llm
self.tools = tools
self.memory = memory
self.planner = planner
self.executor = ActionExecutor(tools)
def run(self, user_input: str, max_iterations=10):
"""Main agent loop"""
self.memory.add_message("user", user_input)
for i in range(max_iterations):
# Observe current state
context = self.memory.get_context()
# Reason about next action
action = self._decide_next_action(context)
# Execute action
result = self.executor.execute(action)
# Update memory
self.memory.add_message("assistant", str(result))
# Check if task is complete
if self._is_complete(result):
break
return self.memory.get_context()
def _decide_next_action(self, context) -> dict:
"""Determine next action using LLM"""
# Build prompt with context and available tools
prompt = self._build_prompt(context)
response = self.llm.generate(prompt)
action = self._parse_action(response)
return action
Component Integration Example
# Initialize components
llm = LLMBackend("gpt-4", temperature=0.7)
tools = ToolRegistry()
tools.register(Tool("calculator", "Perform calculations", calculator, {}))
tools.register(Tool("search", "Search the web", web_search, {}))
memory = ShortTermMemory(max_tokens=4000)
planner = Planner(llm)
# Create agent
agent = AgentController(llm, tools, memory, planner)
# Run agent
result = agent.run("What is the population of Tokyo and how much has it grown since 2000?")
Best Practices
- Modularity: Keep components loosely coupled for easier testing and maintenance
- Error Handling: Each component should handle failures gracefully
- Logging: Track all component interactions for debugging
- Security: Validate tool inputs and sandbox execution
- Performance: Cache results and optimize token usage
- Observability: Monitor component performance and behavior
Next Steps
- Study different agent architectures that combine these components
- Learn about prompt engineering for effective component communication
- Explore memory strategies for different use cases
- Understand evaluation methods for each component