Practice Exercises — AI Agents¶
Exercise 1 — ReAct agent with step tracing (Warm-up)¶
Build a ReAct agent and instrument it to output a clean step trace that shows Thought, Action, Observation for each step.
import os
import re
from openai import OpenAI
from dataclasses import dataclass, field
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
@dataclass
class Step:
thought: str
action_tool: str
action_input: str
observation: str
@dataclass
class AgentTrace:
question: str
steps: list[Step] = field(default_factory=list)
final_answer: str = ""
error: str = ""
# Tools
def search(query: str) -> str:
"""Search for factual information."""
data = {
"python release year": "Python was first released in 1991.",
"python creator": "Python was created by Guido van Rossum.",
"python version": "The latest stable Python version is 3.13 (released 2024).",
"python popularity": "Python ranks #1 on the TIOBE index as of 2024.",
}
for key, val in data.items():
if any(word in query.lower() for word in key.split()):
return val
return f"No specific data found for: {query}"
def calculator(expression: str) -> str:
"""Compute a mathematical expression."""
try:
return str(eval(expression, {"__builtins__": {}}, {}))
except Exception as e:
return f"Error: {e}"
TOOLS = {"search": search, "calculator": calculator}
SYSTEM = """Solve problems step-by-step using:
Thought: [reasoning]
Action: tool_name[input]
Observation: [result]
... repeat ...
Final Answer: [answer]
Tools: search[query], calculator[expression]
Always start with Thought: Always end with Final Answer:"""
def react_agent_traced(question: str, max_steps: int = 8) -> AgentTrace:
trace = AgentTrace(question=question)
messages = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": question},
]
for _ in range(max_steps):
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=messages,
stop=["Observation:"],
temperature=0.0,
max_tokens=200,
)
output = response.choices[0].message.content
# Parse final answer
final_match = re.search(r"Final Answer:\s*(.+)", output, re.DOTALL)
if final_match:
trace.final_answer = final_match.group(1).strip()
return trace
# Parse thought and action
thought_match = re.search(r"Thought:\s*(.+?)(?=Action:|$)", output, re.DOTALL)
action_match = re.search(r"Action:\s*(\w+)\[(.+?)\]", output)
if not action_match:
trace.error = f"Failed to parse action from: {output}"
return trace
thought = thought_match.group(1).strip() if thought_match else ""
tool_name = action_match.group(1)
tool_input = action_match.group(2)
observation = TOOLS.get(tool_name, lambda x: f"Unknown tool: {tool_name}")(tool_input)
trace.steps.append(Step(
thought=thought,
action_tool=tool_name,
action_input=tool_input,
observation=observation,
))
messages.append({"role": "assistant", "content": output})
messages.append({"role": "user", "content": f"Observation: {observation}"})
trace.error = "Max steps reached"
return trace
def print_trace(trace: AgentTrace) -> None:
print(f"\nQ: {trace.question}")
print("=" * 60)
for i, step in enumerate(trace.steps, 1):
print(f"\nStep {i}:")
print(f" Thought: {step.thought}")
print(f" Action: {step.action_tool}[{step.action_input}]")
print(f" Obs: {step.observation}")
if trace.final_answer:
print(f"\nFinal Answer: {trace.final_answer}")
if trace.error:
print(f"\nError: {trace.error}")
print(f"\nTotal steps: {len(trace.steps)}")
# Run
for q in [
"Who created Python and in what year?",
"If Python was released in 1991, how many years ago is that from 2025?",
]:
trace = react_agent_traced(q)
print_trace(trace)
Exercise 2 — Customer support agent (Main)¶
Build a customer support agent that classifies tickets, retrieves account data, and resolves issues — with step-level logging.
import os
import json
from openai import OpenAI
from datetime import datetime
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# Simulated databases
ACCOUNTS = {
"ACC-1001": {"name": "Alice Chen", "plan": "Pro", "status": "active", "balance": 0.0},
"ACC-1002": {"name": "Bob Smith", "plan": "Basic", "status": "suspended", "balance": -29.99},
"ACC-1003": {"name": "Carol Jones", "plan": "Enterprise", "status": "active", "balance": 150.00},
}
ORDERS = {
"ORD-5001": {"account": "ACC-1001", "item": "Standing Desk", "status": "shipped", "tracking": "TRK-123"},
"ORD-5002": {"account": "ACC-1002", "item": "Chair", "status": "delivered", "date": "2025-05-10"},
}
# Tool implementations
def lookup_account(account_id: str) -> dict:
"""Look up customer account details by account ID."""
return ACCOUNTS.get(account_id, {"error": f"Account {account_id} not found"})
def lookup_order(order_id: str) -> dict:
"""Get order status and tracking information."""
return ORDERS.get(order_id, {"error": f"Order {order_id} not found"})
def update_account_status(account_id: str, new_status: str) -> dict:
"""Update an account's status (active, suspended, cancelled)."""
if account_id not in ACCOUNTS:
return {"error": f"Account {account_id} not found"}
if new_status not in ["active", "suspended", "cancelled"]:
return {"error": f"Invalid status: {new_status}"}
ACCOUNTS[account_id]["status"] = new_status
return {"success": True, "account_id": account_id, "new_status": new_status}
def apply_credit(account_id: str, amount: float, reason: str) -> dict:
"""Apply a credit to a customer's account balance."""
if account_id not in ACCOUNTS:
return {"error": f"Account {account_id} not found"}
ACCOUNTS[account_id]["balance"] += amount
return {"success": True, "account_id": account_id, "credit": amount,
"new_balance": ACCOUNTS[account_id]["balance"], "reason": reason}
SUPPORT_TOOLS = [
{"type": "function", "function": {"name": "lookup_account", "description": "Look up customer account details.",
"parameters": {"type": "object", "properties": {"account_id": {"type": "string"}}, "required": ["account_id"]}}},
{"type": "function", "function": {"name": "lookup_order", "description": "Get order status and tracking.",
"parameters": {"type": "object", "properties": {"order_id": {"type": "string"}}, "required": ["order_id"]}}},
{"type": "function", "function": {"name": "update_account_status", "description": "Update account status.",
"parameters": {"type": "object", "properties": {"account_id": {"type": "string"}, "new_status": {"type": "string", "enum": ["active", "suspended", "cancelled"]}}, "required": ["account_id", "new_status"]}}},
{"type": "function", "function": {"name": "apply_credit", "description": "Apply account credit.",
"parameters": {"type": "object", "properties": {"account_id": {"type": "string"}, "amount": {"type": "number"}, "reason": {"type": "string"}}, "required": ["account_id", "amount", "reason"]}}},
]
TOOL_MAP = {"lookup_account": lookup_account, "lookup_order": lookup_order,
"update_account_status": update_account_status, "apply_credit": apply_credit}
SUPPORT_SYSTEM = """You are a customer support agent with access to account and order data.
Resolve customer issues by looking up their information, then taking appropriate action.
Always verify account details before making changes. Be concise and helpful."""
def support_agent(ticket: str, max_steps: int = 8) -> str:
messages = [{"role": "system", "content": SUPPORT_SYSTEM}, {"role": "user", "content": ticket}]
steps = []
for _ in range(max_steps):
response = client.chat.completions.create(model="gpt-4o-mini", messages=messages, tools=SUPPORT_TOOLS)
choice = response.choices[0]
if choice.finish_reason == "stop":
print(f" Completed in {len(steps)} steps: {[s['tool'] for s in steps]}")
return choice.message.content
if choice.finish_reason == "tool_calls":
messages.append(choice.message)
for tc in choice.message.tool_calls:
args = json.loads(tc.function.arguments)
result = TOOL_MAP[tc.function.name](**args)
steps.append({"tool": tc.function.name, "args": args})
print(f" → {tc.function.name}({args}) = {str(result)[:80]}")
messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(result)})
return "Unable to resolve in the given steps."
# Test tickets
TICKETS = [
"My account ACC-1002 was suspended. Can you reactivate it and apply a $30 credit for the inconvenience?",
"I need the status of order ORD-5001.",
"Can you check account ACC-1001 and tell me my current plan?",
]
for t in TICKETS:
print(f"\nTicket: {t}")
response = support_agent(t)
print(f"Response: {response}")