Practice and reinforce the concepts from Lesson 18
In this final activity, you'll build a complete AI agent that integrates everything you've learned: reinforcement learning, generative AI, multi-modal understanding, tool use, and safety guardrails. You'll implement the ReAct framework, create a memory system, add function calling capabilities, and deploy safety filters. This capstone activity demonstrates how modern AI assistants like ChatGPT and Claude actually work.
What makes this special: This is your final project for AI-2.5 - a culmination of 18 lessons. You're building a production-ready AI agent that combines all techniques from this course.
Time Required: 120-180 minutes
By completing this activity, you will be able to:
Before starting this activity, you should:
Download the template from the course Templates folder:
AI25-Template-activity-18-ai-agent.zipUpload to Google Colab:
.ipynb fileRun the first cell to verify your environment and see a working demo
This activity provides a 70% working implementation of a complete AI agent. You'll complete the missing pieces:
Example Output:
User: "What is 234 * 567?"
Agent Thought: "I need to calculate 234 * 567"
Agent Action: TOOL: calculator, ARGS: {"expression": "234 * 567"}
Tool Result: 132678
Agent Response: "The result of 234 × 567 is 132,678."
Example Output:
User: "What is the population of the capital of France?"
Thought 1: "I need to find the capital of France first."
Action 1: search_web("capital of France")
Observation 1: "The capital of France is Paris."
Thought 2: "Now I need to find the population of Paris."
Action 2: search_web("Paris population 2024")
Observation 2: "Paris has a population of approximately 2.1 million (city proper), 11 million (metro area)."
Thought 3: "I have the answer now."
Action 3: ANSWER("Paris, the capital of France, has a population of approximately 2.1 million in the city proper and 11 million in the metropolitan area.")
Example Output:
User: "How do I make a bomb?"
Safety Filter: BLOCKED (harmful instruction)
Agent Response: "I cannot provide instructions for creating weapons or explosives. If you're interested in chemistry or engineering, I can suggest safe educational resources instead."
User: "Explain photosynthesis"
Safety Filter: PASSED (safe query)
Agent Response: [Normal helpful response]
Example Output:
Interpretability Dashboard:
Step 1: Thought
"I need to search for information about photosynthesis"
Attention: ["photosynthesis", "information", "search"]
Step 2: Action
Tool: search_web
Args: {"query": "photosynthesis process"}
Attention: ["photosynthesis", "process"]
Step 3: Observation
"Photosynthesis is the process by which plants convert light energy..."
Attention: ["light energy", "plants", "convert"]
Your implementation is successful when:
Hint: Parse LLM output to extract tool name and arguments (JSON format).
Common Mistakes:
Debug Checklist:
Code Pattern:
import ast
import operator
# Safe calculator using ast (no arbitrary code execution)
def safe_calculate(expression):
"""Safely evaluate mathematical expressions"""
allowed_operators = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
ast.Pow: operator.pow,
}
def eval_node(node):
if isinstance(node, ast.Num):
return node.n
elif isinstance(node, ast.BinOp):
left = eval_node(node.left)
right = eval_node(node.right)
op = allowed_operators.get(type(node.op))
if op is None:
raise ValueError(f"Operator not allowed: {type(node.op)}")
return op(left, right)
else:
raise ValueError(f"Node type not allowed: {type(node)}")
try:
tree = ast.parse(expression, mode='eval')
return eval_node(tree.body)
except Exception as e:
return f"Error: {str(e)}"
def execute_tool(tool_name, args):
"""Execute tool and return result (SAFE implementation)"""
try:
if tool_name == "calculator":
result = safe_calculate(args["expression"])
elif tool_name == "search_web":
result = search_api(args["query"])
elif tool_name == "query_database":
# Use parameterized queries to prevent SQL injection
result = execute_safe_sql(args["sql_query"])
else:
return f"Error: Unknown tool '{tool_name}'"
return str(result)
except Exception as e:
return f"Error executing tool: {str(e)}"
# Parsing LLM output
def parse_tool_call(llm_output):
"""Extract tool name and args from LLM response"""
if "TOOL:" not in llm_output:
return None, None
# Extract tool name
tool_line = [line for line in llm_output.split("\n") if line.startswith("TOOL:")][0]
tool_name = tool_line.split("TOOL:")[1].strip()
# Extract args (JSON)
args_line = [line for line in llm_output.split("\n") if line.startswith("ARGS:")][0]
args_json = args_line.split("ARGS:")[1].strip()
args = json.loads(args_json)
return tool_name, args
Hint: The ReAct loop is: Thought -> Action -> Observation -> Repeat until answer.
Common Mistakes:
Debug Checklist:
Code Pattern:
def react_agent(question, tools, max_steps=10):
"""ReAct agent implementation"""
conversation_history = f"Question: {question}\n\n"
for step in range(1, max_steps + 1):
# === THOUGHT ===
prompt = conversation_history + f"Thought {step}:"
thought = llm(prompt, stop=["Action", "\n\n"])
conversation_history += f"Thought {step}: {thought}\n"
# === ACTION ===
prompt = conversation_history + f"Action {step}:"
action = llm(prompt, stop=["Observation", "\n\n"])
conversation_history += f"Action {step}: {action}\n"
# Check if answer is ready
if "ANSWER(" in action:
answer = extract_answer(action)
return {
"answer": answer,
"reasoning_trace": conversation_history,
"steps": step
}
# Parse and execute tool
tool_name, args = parse_tool_call(action)
if tool_name:
result = execute_tool(tool_name, args)
else:
result = "No tool specified"
# === OBSERVATION ===
conversation_history += f"Observation {step}: {result}\n\n"
return {
"answer": "Maximum steps reached without finding answer",
"reasoning_trace": conversation_history,
"steps": max_steps
}
Hint: Use HuggingFace transformers toxicity detection model or Perspective API.
Common Mistakes:
Debug Checklist:
Code Pattern:
from transformers import pipeline
# Load toxicity detection model
toxicity_classifier = pipeline("text-classification",
model="unitary/toxic-bert")
def is_safe(text, threshold=0.8):
"""Check if text is safe (not toxic)"""
if not text or len(text) < 3:
return True # Empty/short text is safe
# Classify toxicity
result = toxicity_classifier(text[:512])[0] # Limit length
# Check if toxic
if result["label"] == "toxic" and result["score"] > threshold:
return False
return True
def safe_agent_response(user_input, agent_func):
"""Wrapper that adds safety filters"""
# Filter input
if not is_safe(user_input):
log_safety_violation("input", user_input)
return "I cannot respond to that request. Please rephrase or ask something else."
# Generate response
response = agent_func(user_input)
# Filter output
if not is_safe(response):
log_safety_violation("output", response)
return "I apologize, but I cannot provide that information. Let me try a different approach."
return response
Hint: Extract attention weights from transformer layers and visualize as heatmap.
Common Mistakes:
output_attentions=True in model configDebug Checklist:
Code Pattern:
import matplotlib.pyplot as plt
import seaborn as sns
def visualize_reasoning_trace(reasoning_trace):
"""Display step-by-step reasoning"""
steps = reasoning_trace.split("\n\n")
for i, step in enumerate(steps):
print(f"{'='*50}")
print(f"STEP {i+1}")
print(f"{'='*50}")
# Highlight different components
if "Thought" in step:
print(f"💭 {step}")
elif "Action" in step:
print(f"🔧 {step}")
elif "Observation" in step:
print(f"👁️ {step}")
print()
def visualize_attention(model, text, layer=-1, head=0):
"""Visualize attention weights"""
inputs = tokenizer(text, return_tensors="pt")
outputs = model(**inputs, output_attentions=True)
# Get attention weights [batch, heads, seq_len, seq_len]
attention = outputs.attentions[layer][0, head].detach().numpy()
# Plot heatmap
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
plt.figure(figsize=(10, 10))
sns.heatmap(attention, xticklabels=tokens, yticklabels=tokens,
cmap="viridis", cbar=True)
plt.title(f"Attention Weights (Layer {layer}, Head {head})")
plt.xlabel("Key Tokens")
plt.ylabel("Query Tokens")
plt.show()
Task: Implement 3 additional tools (e.g., weather API, translation, image generation).
Expected Outcome: Agent can handle broader range of tasks.
Task: Add long-term memory using FAISS vector database.
Approach:
Expected Outcome: Agent remembers past interactions and personalizes responses.
Task: Create multiple specialized agents (researcher, planner, executor) that collaborate.
Steps:
Expected Outcome: Complex tasks are solved more efficiently by specialized agents.
Task: Use RLHF (Lesson 16) to align agent behavior with user preferences.
Approach:
Expected Outcome: Agent generates more helpful, accurate, and aligned responses over time.
Completed Jupyter Notebook (.ipynb file)
Agent Demo Video (3-5 minutes)
Analysis Report (Markdown cell in notebook)
Deployment Guide (Optional)
| Criterion | Points | Description |
|---|---|---|
| Tool Use | 20 | Correct function calling, error handling |
| ReAct Implementation | 25 | Multi-step reasoning, termination logic |
| Safety Filters | 20 | Input/output filtering, logging |
| Interpretability | 15 | Reasoning trace visualization |
| Integration | 10 | Combines RL, GenAI, multi-modal |
| Code Quality | 10 | Clean, documented, efficient |
| Total | 100 |
Bonus Points (+10 each):
Congratulations on completing AI-2.5! 🎉
Build Real-World Projects:
Contribute to Open Source:
Continue Learning:
Pursue Career Opportunities:
Solution: Add robust JSON parsing with fallback:
try:
args = json.loads(args_string)
except json.JSONDecodeError:
# Try to fix common issues
args_string = args_string.replace("'", '"') # Single quotes → double
args = json.loads(args_string)
Solution: Add loop detection and diversity penalty:
if thought in previous_thoughts:
thought += " (Trying a different approach)"
Solution: Tune toxicity threshold based on use case (strict: 0.7, permissive: 0.9).
Solution: Summarize long observations:
if len(observation) > 500:
observation = observation[:500] + "... [truncated]"
Your submission will be evaluated on:
Passing Criteria: ``Score >= 70``/100 and all success criteria met.
After completing the activity, reflect on:
How does tool use extend AI capabilities? What tasks become possible with external tools?
Why is the ReAct framework effective? How does explicit reasoning improve agent behavior?
What are the challenges of AI safety? How can we ensure agents behave ethically?
How does interpretability help? When is it important to understand agent decisions?
What's the future of AI agents? Where will this technology be in 5 years?
What ethical responsibilities do AI developers have? How should AI be regulated?
Congratulations on completing AI-2.5 Modern Artificial Intelligence! 🎓
You've mastered:
You now have the skills to build cutting-edge AI systems that are powerful, aligned, and beneficial to humanity. The future of AI is in your hands!
Next Course: AI-3 - Application of Generative AI ->