Intermediate

What Are AI Agents? Architecture and Patterns

Lesson 1 of 4 Estimated Time 55 min

What Are AI Agents? Architecture and Patterns

An AI agent is a system that can perceive its environment, reason about it, and take actions to achieve goals. Unlike chains that follow fixed sequences, agents decide what to do based on their observations. This lesson covers agent fundamentals, the ReAct pattern, planning strategies, and when to use agents.

What Makes Something an Agent?

An agent has:

  1. Perception: Input from the environment (observations, user questions)
  2. Reasoning: Processing to decide what to do next (using an LLM)
  3. Action: Taking steps (calling tools, APIs, computing)
  4. Feedback loop: Observing results and adapting

Simple example:

def simple_agent(task: str):
    """Simplest agent loop."""
    observation = task

    while True:
        # Reason: ask LLM what to do
        action = ask_llm(f"What should I do about: {observation}")

        if action == "DONE":
            break

        # Act: execute the action
        result = execute_action(action)

        # Observe: see what happened
        observation = result

    return observation

The ReAct Pattern

ReAct (Reasoning + Acting) is the most popular agent pattern. The model interleaves reasoning thoughts with actions:

Thought: I need to find information about X
Action: search_wikipedia("X")
Observation: [Wikipedia article content]
Thought: Now I have information about X. Let me check for Y
Action: search_wikipedia("Y")
Observation: [Content]
Thought: I now have all information needed
Action: final_answer("The answer is...")

Implementation:

from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate

# Define tools
@tool
def calculator(expression: str) -> float:
    """Evaluate a math expression."""
    return eval(expression)

@tool
def search_wikipedia(query: str) -> str:
    """Search Wikipedia."""
    # Implementation here
    return f"Results for {query}"

# Create agent
tools = [calculator, search_wikipedia]
model = ChatOpenAI(model="gpt-3.5-turbo")

agent = create_react_agent(model, tools, PromptTemplate.from_template(
    """You are a helpful assistant. Use tools to answer questions.
    {agent_scratchpad}
    Question: {input}"""))

executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# Use agent
result = executor.invoke({"input": "What is 2+2? And who invented calculus?"})
print(result)

Tool-Using Agents

Agents become powerful with access to tools:

from langchain_core.tools import tool
from typing import Any

class ToolLibrary:
    """Collection of tools for agents."""

    @staticmethod
    @tool
    def get_weather(city: str) -> str:
        """Get current weather for a city."""
        # Mock implementation
        return f"Weather in {city}: Sunny, 72F"

    @staticmethod
    @tool
    def search_web(query: str) -> str:
        """Search the web."""
        return f"Search results for: {query}"

    @staticmethod
    @tool
    def calculate(operation: str) -> Any:
        """Perform calculation."""
        return eval(operation)

    @staticmethod
    @tool
    def send_email(to: str, subject: str, body: str) -> str:
        """Send an email."""
        return f"Email sent to {to}"

# Create agent with tools
tools = [
    ToolLibrary.get_weather,
    ToolLibrary.search_web,
    ToolLibrary.calculate,
    ToolLibrary.send_email
]

agent = create_react_agent(model, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools)

# Agent can now use any of these tools
result = executor.invoke({
    "input": "What's the weather in NYC and send me the forecast?"
})

Single vs. Multi-Agent Systems

Single agent: One agent handles everything

  • Simpler, fewer moving parts
  • Limited specialization
  • Good for straightforward tasks
# Single agent doing everything
agent = create_react_agent(model, [tool1, tool2, tool3, ...])
result = agent.invoke({"input": "Complex task..."})

Multi-agent: Multiple specialized agents

  • Better specialization
  • Can handle complex workflows
  • More complex coordination
# Multiple specialized agents
research_agent = Agent(tools=[search, analyze], role="researcher")
writing_agent = Agent(tools=[write, edit], role="writer")
manager_agent = Agent(role="manager", agents=[research_agent, writing_agent])

# Manager coordinates
result = manager_agent.invoke({"task": "Write about AI"})

Agent Loops and Control Flow

Agents follow loops: observe → think → act → repeat

class AgentLoop:
    """Control agent execution loop."""

    def __init__(self, model, tools, max_iterations=10):
        self.model = model
        self.tools = tools
        self.max_iterations = max_iterations
        self.iteration = 0

    def run(self, task: str):
        """Run agent loop."""
        state = {"task": task, "history": []}

        while self.iteration < self.max_iterations:
            # Think: ask model what to do
            thought = self.model.invoke(
                f"Task: {state['task']}\nHistory: {state['history']}\nWhat's next?"
            )

            # Check if done
            if "DONE" in thought.content or "FINAL_ANSWER" in thought.content:
                return thought.content

            # Act: execute tool
            action = self.extract_action(thought.content)
            result = self.execute_tool(action)

            # Observe: update history
            state["history"].append({"action": action, "result": result})
            self.iteration += 1

        return "Max iterations reached"

    def extract_action(self, response: str) -> str:
        """Parse action from model response."""
        if "ACTION:" in response:
            return response.split("ACTION:")[1].split("\n")[0].strip()
        return None

    def execute_tool(self, action: str) -> Any:
        """Execute the action."""
        # Parse action and call appropriate tool
        return f"Result of {action}"

Planning vs. Acting

Plan-then-execute: Create full plan first, then execute steps

  • Predictable, fewer errors
  • Less flexible if plan becomes invalid
  • Good for defined processes
def plan_and_execute(task: str):
    """Create plan first, then execute."""
    # Step 1: Create plan
    plan = model.invoke(f"Create a detailed plan for: {task}")

    # Step 2: Execute plan
    for step in plan.steps:
        result = execute_step(step)
        if not result.success:
            # Adapt if needed
            pass

    return result

Think-act-observe: Decide next step iteratively

  • Flexible, adaptive
  • More iterations needed
  • Good for open-ended problems
def think_act_observe(task: str):
    """Decide each step based on progress."""
    observation = task

    while not done:
        # Think about what to do next
        next_action = model.invoke(f"What's next? Current state: {observation}")

        # Act
        result = execute_action(next_action)

        # Observe
        observation = result

    return observation

Error Handling and Recovery

Agents need to handle and recover from errors:

class RobustAgent:
    """Agent with error handling."""

    def __init__(self, model, tools):
        self.model = model
        self.tools = tools
        self.max_retries = 3

    def execute_with_retry(self, action: str):
        """Execute action with retry on failure."""
        for attempt in range(self.max_retries):
            try:
                result = self.execute_tool(action)
                return result
            except Exception as e:
                if attempt < self.max_retries - 1:
                    # Ask model how to recover
                    recovery = self.model.invoke(
                        f"Tool failed: {e}. How to recover?"
                    )
                else:
                    raise

    def execute_tool(self, action: str):
        """Execute a tool, raise on error."""
        # Implementation
        pass

Key Takeaway

Agents perceive, reason, and act iteratively. The ReAct pattern interleaves thoughts and actions. Tools give agents capabilities. Plan-then-execute works for structured tasks; think-act-observe for open-ended problems. Single agents are simpler; multi-agents handle complexity. Build robust agents with error handling and recovery.

Exercises

  1. Simple agent: Build a basic agent that uses 2-3 tools to solve problems.

  2. ReAct: Implement a ReAct agent with proper thought/action/observation formatting.

  3. Tool design: Create 3 custom tools for agents. Ensure they’re well-defined with clear inputs/outputs.

  4. Control flow: Implement agent loops with iteration limits and stopping conditions.

  5. Error handling: Add retry logic and recovery to agent execution.

  6. Plan vs. Act: Compare plan-then-execute vs. think-act-observe on the same task.