Module 2: OpenAI API - Introduction
Mini-Project: Technical Support Chatbot
Project overview
This is the integrative project for Module 2. You'll apply EVERYTHING you've learned to build a production-ready technical support chatbot.
Features:
- ✅ Conversations with context (remembers history)
- ✅ Custom system message (specific behavior)
- ✅ Optimized parameters (temperature, max_tokens)
- ✅ Robust error handling (retries, logging)
- ✅ Cost tracking (monitors spending)
- ✅ Persistence (saves conversations to JSON)
Time: 60-90 minutes
Difficulty: Medium-High
🎯 Project goal
Build a CLI chatbot that:
- Answers technical support questions
- Maintains conversational context
- Handles errors gracefully (doesn't crash)
- Tracks cost per conversation
- Saves logs for analysis
📋 Specifications
Functionality:
Chatbot role:
- Technical support assistant for the "TechApp" app
- Answers FAQs about password reset, billing, features
- Escalates to a human if it can't resolve the issue
Special commands:
salir/exit→ Ends the conversationhistorial→ Shows the full historycosto→ Shows the accumulated cost
Persistence:
- Saves each conversation to
conversations/conversation_TIMESTAMP.json - Error logs in
logs/errors.log
💻 Implementation
Project structure:
chatbot-soporte/
├── .env # API key
├── .gitignore # Prevents leaks
├── chatbot.py # Main code
├── conversations/ # Saved conversations
│ └── conversation_20240215_103045.json
└── logs/
└── errors.log # Error logs
Full code (chatbot.py):
#!/usr/bin/env python3
"""
Technical Support Chatbot - TechApp
Module 2: OpenAI API - Final Project
"""
import os
import json
import time
import logging
from datetime import datetime
from typing import Optional, List, Dict
from pathlib import Path
from dotenv import load_dotenv
from openai import OpenAI, RateLimitError, APIError, APITimeoutError
# ============================================================================
# CONFIGURATION
# ============================================================================
load_dotenv()
# Create directories
Path("conversations").mkdir(exist_ok=True)
Path("logs").mkdir(exist_ok=True)
# Logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('logs/errors.log'),
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
# OpenAI client
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# ============================================================================
# CHATBOT CONFIGURATION
# ============================================================================
SYSTEM_MESSAGE = """
You are a technical support assistant for TechApp, a productivity application.
Your role:
1. Help users with frequently asked questions
2. Be friendly, clear, and concise
3. If you don't know something, say: "Let me escalate this to a human agent"
4. NEVER make up information (prices, dates, features)
Information about TechApp:
- Password reset: Settings > Security > Reset Password
- Billing: $10/month basic plan, $30/month premium plan
- Features: Task management, Calendar, Notes, Multi-device sync
- Support: support@techapp.com
Always respond in English, 4 sentences maximum.
""".strip()
# ============================================================================
# CHATBOT CLASS
# ============================================================================
class TechSupportBot:
"""Technical support chatbot with OpenAI."""
def __init__(self):
self.messages: List[Dict[str, str]] = [
{"role": "system", "content": SYSTEM_MESSAGE}
]
self.total_tokens = 0
self.total_cost = 0.0
self.conversation_id = datetime.now().strftime("%Y%m%d_%H%M%S")
def chat(self, user_message: str, max_retries: int = 3) -> Optional[str]:
"""
Send a message and return the response with retry.
Args:
user_message: The user's message
max_retries: Maximum attempts if there's an error
Returns:
The bot's response, or None if it fails
"""
# Add the user's message
self.messages.append({"role": "user", "content": user_message})
# Retry with exponential backoff
for attempt in range(max_retries):
try:
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=self.messages,
temperature=0.3, # Low (consistent support)
max_tokens=150, # Short answers
timeout=30.0
)
# Extract the response
assistant_message = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": assistant_message})
# Update metrics
self._update_metrics(response.usage)
logger.info(f"✅ Successful request | Tokens: {response.usage.total_tokens}")
return assistant_message
except RateLimitError:
logger.warning(f"⚠️ Rate limit | Attempt {attempt + 1}/{max_retries}")
if attempt == max_retries - 1:
logger.error("❌ Persistent rate limit")
return None
time.sleep(2 ** attempt)
except (APIError, APITimeoutError) as e:
logger.warning(f"⚠️ API error | Attempt {attempt + 1}/{max_retries}")
if attempt == max_retries - 1:
logger.error(f"❌ Persistent error: {e}")
return None
time.sleep(2 ** attempt)
except Exception as e:
logger.error(f"❌ Unexpected error: {type(e).__name__} | {e}")
return None
return None
def _update_metrics(self, usage):
"""Update cost and token metrics."""
self.total_tokens += usage.total_tokens
# GPT-3.5-turbo pricing (Feb 2026)
input_cost = (usage.prompt_tokens / 1_000_000) * 0.50
output_cost = (usage.completion_tokens / 1_000_000) * 1.50
self.total_cost += input_cost + output_cost
def show_history(self):
"""Show the conversation history."""
print("\n" + "="*60)
print("CONVERSATION HISTORY")
print("="*60)
for i, msg in enumerate(self.messages[1:], 1): # Skip system
role = "YOU" if msg["role"] == "user" else "BOT"
print(f"\n[{i}] {role}: {msg['content']}")
print("\n" + "="*60 + "\n")
def show_cost(self):
"""Show the accumulated cost."""
print(f"\n💰 Accumulated cost: ${self.total_cost:.6f}")
print(f"📊 Total tokens: {self.total_tokens}")
print(f"📝 Messages: {len(self.messages) - 1}\n") # -1 for system
def save_conversation(self):
"""Save the conversation to JSON."""
filename = f"conversations/conversation_{self.conversation_id}.json"
data = {
"conversation_id": self.conversation_id,
"timestamp": datetime.now().isoformat(),
"messages": self.messages[1:], # Skip system message
"total_tokens": self.total_tokens,
"total_cost": self.total_cost,
"message_count": len(self.messages) - 1
}
with open(filename, "w", encoding="utf-8") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
logger.info(f"💾 Conversation saved: {filename}")
print(f"💾 Conversation saved: {filename}")
# ============================================================================
# MAIN - CLI INTERFACE
# ============================================================================
def main():
"""The chatbot's main CLI."""
# Banner
print("\n" + "="*60)
print("🤖 TECHNICAL SUPPORT CHATBOT - TechApp")
print("="*60)
print("\nSpecial commands:")
print(" - 'salir' / 'exit' → End the conversation")
print(" - 'historial' → See the full history")
print(" - 'costo' → See the accumulated cost")
print("\n" + "="*60 + "\n")
# Create the bot
bot = TechSupportBot()
# Main loop
while True:
try:
# User input
user_input = input("You: ").strip()
# Special commands
if user_input.lower() in ["salir", "exit", "quit"]:
print("\n👋 Thanks for contacting TechApp! See you soon.\n")
bot.show_cost()
bot.save_conversation()
break
if user_input.lower() == "historial":
bot.show_history()
continue
if user_input.lower() == "costo":
bot.show_cost()
continue
# Validate input
if not user_input:
print("⚠️ Please type a message.\n")
continue
# Send to the bot
response = bot.chat(user_input)
if response:
print(f"\nBot: {response}\n")
else:
print("\n❌ Sorry, there was an error. Please try again.\n")
except KeyboardInterrupt:
print("\n\n👋 Conversation interrupted by the user.\n")
bot.show_cost()
bot.save_conversation()
break
except Exception as e:
logger.critical(f"❌ Critical error in the main loop: {e}")
print(f"\n❌ Unexpected error: {e}\n")
bot.save_conversation()
break
if __name__ == "__main__":
# Check the API key
if not os.getenv("OPENAI_API_KEY"):
print("❌ ERROR: OPENAI_API_KEY not found in .env")
exit(1)
main()
🚀 Usage
1. Run it:
python chatbot.py
2. Example conversation:
============================================================
🤖 TECHNICAL SUPPORT CHATBOT - TechApp
============================================================
Special commands:
- 'salir' / 'exit' → End the conversation
- 'historial' → See the full history
- 'costo' → See the accumulated cost
============================================================
You: Hi, how do I reset my password?
Bot: To reset your password in TechApp:
1. Go to Settings
2. Select Security
3. Click Reset Password
4. You'll receive an email with instructions
You: I'm not receiving the email
Bot: If you didn't receive the reset email:
1. Check your Spam/Junk folder
2. Confirm the registered email is correct
3. Wait 5 minutes (there's sometimes a delay)
If it still doesn't arrive, let me escalate this to a human agent.
You: historial
============================================================
CONVERSATION HISTORY
============================================================
[1] YOU: Hi, how do I reset my password?
[2] BOT: To reset your password in TechApp:
1. Go to Settings
2. Select Security
3. Click Reset Password
4. You'll receive an email with instructions
[3] YOU: I'm not receiving the email
[4] BOT: If you didn't receive the reset email:
1. Check your Spam/Junk folder
2. Confirm the registered email is correct
3. Wait 5 minutes (there's sometimes a delay)
If it still doesn't arrive, let me escalate this to a human agent.
============================================================
You: costo
💰 Accumulated cost: $0.000456
📊 Total tokens: 285
📝 Messages: 4
You: salir
👋 Thanks for contacting TechApp! See you soon.
💰 Accumulated cost: $0.000456
📊 Total tokens: 285
📝 Messages: 4
💾 Conversation saved: conversations/conversation_20240215_103045.json
📊 Conversation Analysis
Script to analyze costs:
import json
from pathlib import Path
def analyze_conversations():
"""Analyze all saved conversations."""
conversations_dir = Path("conversations")
json_files = list(conversations_dir.glob("*.json"))
if not json_files:
print("No conversations saved yet.")
return
total_cost = 0
total_tokens = 0
total_messages = 0
print(f"\n📊 ANALYSIS OF {len(json_files)} CONVERSATIONS\n")
print(f"{'ID':<20} {'Messages':<10} {'Tokens':<10} {'Cost':<12}")
print("="*52)
for file in json_files:
with open(file) as f:
data = json.load(f)
print(f"{data['conversation_id']:<20} {data['message_count']:<10} "
f"{data['total_tokens']:<10} ${data['total_cost']:<11.6f}")
total_cost += data['total_cost']
total_tokens += data['total_tokens']
total_messages += data['message_count']
print("="*52)
print(f"{'TOTAL':<20} {total_messages:<10} {total_tokens:<10} ${total_cost:<11.6f}")
print(f"\nAverage per conversation: ${total_cost/len(json_files):.6f}\n")
if __name__ == "__main__":
analyze_conversations()
Run it:
python analyze_conversations.py
✅ Self-Assessment Rubric
Functionality (40 points):
- (10 pts) The bot answers coherently
- (10 pts) Maintains context (references to previous messages)
- (10 pts) Special commands work (historial, costo, salir)
- (10 pts) Conversations are saved to JSON
Error Handling (30 points):
- (10 pts) Handles rate limit with retry
- (10 pts) Handles API errors with retry
- (10 pts) Error logging to a file
Optimization (20 points):
- (10 pts) Optimized temperature (0.3 for support)
- (10 pts) Limited max tokens (150)
Cost Tracking (10 points):
- (10 pts) Cost calculated correctly
Total: ___/100 points
Interpretation:
- 90-100: ✅ Excellent
- 70-89: ⚠️ Good
- <70: ❌ Review
🎯 Extensions (Optional)
1. Persistent memory across sessions:
# On startup, load the previous conversation
def load_last_conversation(self):
# Find the latest JSON
# Load the messages
pass
2. Streaming responses:
response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=self.messages,
stream=True
)
for chunk in response:
print(chunk.choices[0].delta.content, end="", flush=True)
3. Web UI with Streamlit:
pip install streamlit
import streamlit as st
st.title("🤖 TechApp Support")
user_input = st.text_input("You:")
if user_input:
response = bot.chat(user_input)
st.write(f"Bot: {response}")
📊 Summary
What you built:
-
Production-ready chatbot:
- Conversational context
- Custom system message
- Optimized parameters
-
Robust error handling:
- Retry with exponential backoff
- Logging to a file
- Graceful degradation
-
Cost tracking:
- Automatic calculation
- Conversation analysis
-
Persistence:
- JSON per conversation
- Later analysis
Skills mastered:
- ✅ Full OpenAI SDK
- ✅ Conversations with context
- ✅ Error handling in production
- ✅ Cost optimization
- ✅ Logging and debugging
Congratulations! You completed Module 2.
🔗 Additional resources
- OpenAI Best Practices
- Streamlit Docs - For a web UI
- Prompt Engineering - Improve system messages
➡️ Next step
Next module: Module 3 - LM Studio (Local GUI)
You'll learn to run LLMs locally on your machine:
- Zero operating cost
- 100% privacy
- OpenAI-compatible API (reusable code!)
Time: 2-3 hours
Estimated time: 60-90 minutes
Project completed! 🎉