# Install required packages
!pip install openai python-dotenv -q
import openai
import os
import json
from dotenv import load_dotenv
# Load environment variables
load_dotenv()
openai.api_key = os.getenv("OPENAI_API_KEY")
print("β
Environment setup complete!")
Part 1: What is an AI Agent?ΒΆ
DefinitionΒΆ
AI Agent: A software system that can autonomously perceive its environment, reason about actions, and take steps to achieve goals.
Key CharacteristicsΒΆ
Autonomy: Makes decisions without constant human input
Reactivity: Responds to environmental changes
Pro-activeness: Takes initiative to achieve goals
Social Ability: Interacts with other agents/humans
Agent ArchitectureΒΆ
βββββββββββββββββββββββββββββββββββββββββββ
β AI Agent β
β β
β ββββββββββββ ββββββββββββ ββββββββββ
β β Perceive ββ β Reason ββ β Act ββ
β β(Inputs) β β(Planning)β β(Tools)ββ
β ββββββββββββ ββββββββββββ ββββββββββ
β β β β β
β βββββββ[Memory]ββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββ
Part 2: Chatbot vs AgentΒΆ
Comparison TableΒΆ
Feature |
Chatbot |
AI Agent |
|---|---|---|
Primary Function |
Converse |
Take actions |
Tools |
None |
External tools/APIs |
Autonomy |
Reactive only |
Proactive |
Planning |
No |
Yes (multi-step) |
Memory |
Conversation only |
Persistent state |
Example |
Customer FAQ bot |
Customer service agent |
ExamplesΒΆ
Chatbot:
User: "What's the weather in Boston?"
Bot: "I don't have access to real-time weather data."
AI Agent:
User: "What's the weather in Boston?"
Agent: [Calls weather API]
"It's currently 72Β°F and sunny in Boston, MA."
Part 3: Build Your First Simple AgentΒΆ
An agent is only as useful as its tools. The simplest agent has a single tool β in this case, a calculator β and follows a three-step loop: (1) send the user query and tool schema to the LLM, (2) if the LLM decides to call a tool, execute it and collect the result, (3) send the result back to the LLM for a natural language response. The tool_choice="auto" parameter lets the model decide whether a tool is needed: for βWhat is 45 plus 67?β it calls the calculator, but for βWhat is an AI agent?β it answers directly from its training data.
# Define tools (functions the agent can use)
def calculator(operation: str, a: float, b: float) -> float:
"""Perform basic math operations"""
operations = {
"add": lambda x, y: x + y,
"subtract": lambda x, y: x - y,
"multiply": lambda x, y: x * y,
"divide": lambda x, y: x / y if y != 0 else "Error: Division by zero"
}
if operation in operations:
return operations[operation](a, b)
else:
return f"Error: Unknown operation '{operation}'"
# Test the calculator
print("Calculator tests:")
print(f" 10 + 5 = {calculator('add', 10, 5)}")
print(f" 10 - 5 = {calculator('subtract', 10, 5)}")
print(f" 10 * 5 = {calculator('multiply', 10, 5)}")
print(f" 10 / 5 = {calculator('divide', 10, 5)}")
# Define tool schema for OpenAI function calling
tools = [
{
"type": "function",
"function": {
"name": "calculator",
"description": "Perform basic mathematical operations (add, subtract, multiply, divide)",
"parameters": {
"type": "object",
"properties": {
"operation": {
"type": "string",
"enum": ["add", "subtract", "multiply", "divide"],
"description": "The mathematical operation to perform"
},
"a": {
"type": "number",
"description": "The first number"
},
"b": {
"type": "number",
"description": "The second number"
}
},
"required": ["operation", "a", "b"]
}
}
}
]
print("β
Tool schema defined")
print(json.dumps(tools[0], indent=2))
# Simple agent implementation
def simple_agent(user_query: str):
"""A simple agent that can use the calculator tool"""
print(f"\nπ€ User: {user_query}")
print("π€ Agent: Thinking...\n")
# Step 1: Send query to LLM with tools
response = openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": user_query}],
tools=tools,
tool_choice="auto" # Let model decide if it needs tools
)
message = response.choices[0].message
# Step 2: Check if tool was called
if message.tool_calls:
# Extract tool call
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"π§ Agent decided to use tool: {function_name}")
print(f" Arguments: {function_args}")
# Step 3: Execute the function
if function_name == "calculator":
result = calculator(**function_args)
print(f" Result: {result}\n")
# Step 4: Send result back to LLM for final response
second_response = openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": user_query},
message,
{
"role": "tool",
"tool_call_id": tool_call.id,
"name": function_name,
"content": str(result)
}
]
)
final_response = second_response.choices[0].message.content
print(f"π€ Agent: {final_response}")
return final_response
else:
# No tool needed, return direct response
print(f"π€ Agent: {message.content}")
return message.content
# Test the agent
simple_agent("What is 45 plus 67?")
# Test with a more complex query
simple_agent("If I have 100 dollars and spend 23.50, how much do I have left?")
# Test without needing the calculator
simple_agent("What is an AI agent?")
π§ Knowledge CheckΒΆ
Question 1: What are the three main steps in the agent loop?
Click for answer
Answer:
Perceive - Receive user input
Reason - Decide what action to take (which tool to use)
Act - Execute the tool and return results
Then the cycle repeats with feedback from the action.
Question 2: When does the agent decide to use a tool vs. answer directly?
Click for answer
Answer: The LLM analyzes the user query and determines if it needs external tools to answer:
Use tool: When the query requires actions or external data (e.g., βWhat is 45 + 67?β)
Answer directly: When the LLM has the knowledge in its training data (e.g., βWhat is an AI agent?β)
This is controlled by tool_choice="auto"
Part 4: Agent Design PatternsΒΆ
1. Single-Tool AgentΒΆ
One specialized tool (like our calculator)
Simple and focused
Example: SQL agent, weather agent
2. Multi-Tool AgentΒΆ
Multiple tools available
Agent selects appropriate tool
Example: Personal assistant (calendar + email + search)
3. ReAct Agent (Reasoning + Acting)ΒΆ
Alternates between thinking and acting
Explains reasoning before each action
Can self-correct
4. Chain AgentΒΆ
Fixed sequence of tools
Output of one tool β input to next
Example: Data pipeline (load β clean β analyze β visualize)
5. Hierarchical AgentΒΆ
Multiple agents coordinated by a master agent
Task decomposition
Example: Research agent delegates to search, summarize, write agents
Part 5: Multi-Tool Agent ExampleΒΆ
Real-world agents need access to multiple tools, and a key part of the agentβs intelligence is tool selection β choosing which tool to invoke based on the userβs intent. The multi-tool agent below has a calculator, a clock, and a random number generator. When the user asks βWhat time is it?β, the LLM analyzes the query against all tool descriptions and selects get_current_time. The available_functions dictionary maps tool names to their Python implementations, enabling dynamic dispatch. As you add more tools, clear and distinct tool descriptions become critical to prevent the model from selecting the wrong one.
# Define additional tools
def get_current_time():
"""Get the current time"""
from datetime import datetime
return datetime.now().strftime("%I:%M %p")
def get_random_number(min_val: int, max_val: int):
"""Generate a random number between min and max"""
import random
return random.randint(min_val, max_val)
# Updated tool schemas
multi_tools = [
{
"type": "function",
"function": {
"name": "calculator",
"description": "Perform basic mathematical operations",
"parameters": {
"type": "object",
"properties": {
"operation": {"type": "string", "enum": ["add", "subtract", "multiply", "divide"]},
"a": {"type": "number"},
"b": {"type": "number"}
},
"required": ["operation", "a", "b"]
}
}
},
{
"type": "function",
"function": {
"name": "get_current_time",
"description": "Get the current time",
"parameters": {"type": "object", "properties": {}}
}
},
{
"type": "function",
"function": {
"name": "get_random_number",
"description": "Generate a random number between min and max (inclusive)",
"parameters": {
"type": "object",
"properties": {
"min_val": {"type": "integer", "description": "Minimum value"},
"max_val": {"type": "integer", "description": "Maximum value"}
},
"required": ["min_val", "max_val"]
}
}
}
]
print(f"β
Multi-tool agent has {len(multi_tools)} tools available")
# Multi-tool agent
def multi_tool_agent(user_query: str):
"""Agent with multiple tools"""
# Map function names to actual functions
available_functions = {
"calculator": calculator,
"get_current_time": get_current_time,
"get_random_number": get_random_number
}
print(f"\nπ€ User: {user_query}")
print("π€ Agent: Analyzing request...\n")
# Call LLM with all tools
response = openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": user_query}],
tools=multi_tools,
tool_choice="auto"
)
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"π§ Selected tool: {function_name}")
print(f" Arguments: {function_args}")
# Execute the selected function
function_to_call = available_functions[function_name]
result = function_to_call(**function_args) if function_args else function_to_call()
print(f" Result: {result}\n")
# Get final response
second_response = openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "user", "content": user_query},
message,
{
"role": "tool",
"tool_call_id": tool_call.id,
"name": function_name,
"content": str(result)
}
]
)
final_response = second_response.choices[0].message.content
print(f"π€ Agent: {final_response}")
return final_response
else:
print(f"π€ Agent: {message.content}")
return message.content
# Test with different queries
multi_tool_agent("What time is it right now?")
multi_tool_agent("Pick a random number between 1 and 100")
multi_tool_agent("Calculate 25 times 4")
Part 6: Real-World Agent ExampleΒΆ
Moving beyond toy tools, the customer support agent demonstrates how agents solve practical business problems. It has three tools: FAQ search (information retrieval), order status lookup (database query), and ticket creation (write operation). Each tool connects to a simulated backend β in production, these would be real databases and APIs. The system prompt sets the agentβs tone (βfriendly and professionalβ), while the tool schemas guide which operations are available. This pattern scales naturally: add a refund tool, a shipping tool, or a product recommendation tool, and the agent gains new capabilities without changes to the core loop.
# Simulated database/knowledge base
faq_database = {
"return policy": "You can return items within 30 days of purchase for a full refund.",
"shipping": "Free shipping on orders over $50. Standard shipping takes 5-7 business days.",
"payment methods": "We accept credit cards, PayPal, and Apple Pay.",
"warranty": "All products come with a 1-year manufacturer's warranty."
}
order_database = {
"12345": {"status": "shipped", "tracking": "1Z999AA10123456784", "eta": "Dec 18"},
"67890": {"status": "processing", "tracking": None, "eta": "Dec 20"}
}
# Support agent tools
def search_faq(topic: str) -> str:
"""Search the FAQ database"""
topic_lower = topic.lower()
for key, value in faq_database.items():
if key in topic_lower or topic_lower in key:
return value
return "No FAQ found for that topic. Please contact support."
def check_order_status(order_id: str) -> str:
"""Check the status of an order"""
if order_id in order_database:
order = order_database[order_id]
status_msg = f"Order {order_id}: {order['status']}"
if order['tracking']:
status_msg += f" | Tracking: {order['tracking']}"
status_msg += f" | ETA: {order['eta']}"
return status_msg
else:
return f"Order {order_id} not found in system."
def create_support_ticket(issue: str, customer_email: str) -> str:
"""Create a support ticket"""
ticket_id = f"TKT-{hash(issue + customer_email) % 10000:04d}"
return f"Support ticket {ticket_id} created. We'll respond within 24 hours."
print("β
Customer support tools defined")
# Tool schemas for support agent
support_tools = [
{
"type": "function",
"function": {
"name": "search_faq",
"description": "Search the FAQ database for information on topics like return policy, shipping, payment methods, warranty",
"parameters": {
"type": "object",
"properties": {
"topic": {"type": "string", "description": "The topic to search for"}
},
"required": ["topic"]
}
}
},
{
"type": "function",
"function": {
"name": "check_order_status",
"description": "Check the status and tracking information for an order",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order ID (5 digits)"}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "create_support_ticket",
"description": "Create a support ticket for complex issues that require human assistance",
"parameters": {
"type": "object",
"properties": {
"issue": {"type": "string", "description": "Description of the issue"},
"customer_email": {"type": "string", "description": "Customer's email address"}
},
"required": ["issue", "customer_email"]
}
}
}
]
# Customer support agent
def support_agent(user_query: str, customer_email: str = "customer@example.com"):
"""Customer support agent"""
available_functions = {
"search_faq": search_faq,
"check_order_status": check_order_status,
"create_support_ticket": lambda issue: create_support_ticket(issue, customer_email)
}
print(f"\nπ€ Customer: {user_query}")
print("π§ Support Agent: Let me help you with that...\n")
response = openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful customer support agent. Be friendly and professional."},
{"role": "user", "content": user_query}
],
tools=support_tools,
tool_choice="auto"
)
message = response.choices[0].message
if message.tool_calls:
tool_call = message.tool_calls[0]
function_name = tool_call.function.name
function_args = json.loads(tool_call.function.arguments)
print(f"π§ Using tool: {function_name}")
print(f" Arguments: {function_args}")
function_to_call = available_functions[function_name]
result = function_to_call(**function_args)
print(f" Result: {result}\n")
second_response = openai.chat.completions.create(
model="gpt-3.5-turbo",
messages=[
{"role": "system", "content": "You are a helpful customer support agent. Be friendly and professional."},
{"role": "user", "content": user_query},
message,
{
"role": "tool",
"tool_call_id": tool_call.id,
"name": function_name,
"content": str(result)
}
]
)
final_response = second_response.choices[0].message.content
print(f"π§ Support Agent: {final_response}")
return final_response
else:
print(f"π§ Support Agent: {message.content}")
return message.content
# Test the support agent
support_agent("What's your return policy?")
support_agent("Where is my order #12345?")
support_agent("I received a damaged item and need help", customer_email="john@email.com")
π― SummaryΒΆ
What We LearnedΒΆ
AI Agents are autonomous systems that perceive, reason, and act
Key difference from chatbots: Agents can use tools to take actions
Agent architecture: Perceive β Reason β Act β Learn (with Memory)
Tool schemas define what functions agents can use
OpenAI Function Calling enables agents to use external tools
Design patterns: Single-tool, multi-tool, ReAct, chain, hierarchical
Key TakeawaysΒΆ
β
Agents extend LLM capabilities with external tools
β
Tool selection is automatic based on user query
β
Agents can handle complex, multi-step tasks
β
Real-world applications: support, research, coding, data analysis
Next StepsΒΆ
Notebook 2: Deep dive into function calling and tool design
Notebook 3: ReAct pattern for multi-step reasoning
Notebook 4: Agent frameworks (LangChain, LangGraph)
Ready to build more sophisticated agents? Letβs continue! π