Módulo 2: OpenAI API - Introducción
Mini-Proyecto: Chatbot de Soporte Técnico
Descripción del proyecto
Este es el proyecto integrador del Módulo 2. Aplicarás TODO lo aprendido para construir un chatbot de soporte técnico production-ready.
Características:
- ✅ Conversaciones con contexto (recuerda historial)
- ✅ System message personalizado (comportamiento específico)
- ✅ Parameters optimizados (temperature, max_tokens)
- ✅ Error handling robusto (retries, logging)
- ✅ Cost tracking (monitorea gasto)
- ✅ Persistence (guarda conversaciones en JSON)
Tiempo: 60-90 minutos
Dificultad: Media-Alta
🎯 Objetivo del proyecto
Crear chatbot CLI que:
- Responda preguntas de soporte técnico
- Mantenga contexto conversacional
- Maneje errores gracefully (no crashea)
- Trackee costo por conversación
- Guarde logs para análisis
📋 Especificaciones
Funcionalidad:
Rol del chatbot:
- Asistente de soporte técnico para app "TechApp"
- Responde FAQs sobre password reset, billing, features
- Escala a humano si no puede resolver
Comandos especiales:
salir/exit→ Termina conversaciónhistorial→ Muestra historial completocosto→ Muestra costo acumulado
Persistencia:
- Guarda cada conversación en
conversations/conversation_TIMESTAMP.json - Logs de errores en
logs/errors.log
💻 Implementación
Estructura del proyecto:
chatbot-soporte/
├── .env # API key
├── .gitignore # Previene leak
├── chatbot.py # Código principal
├── conversations/ # Conversaciones guardadas
│ └── conversation_20240215_103045.json
└── logs/
└── errors.log # Logs de errores
Código completo (chatbot.py):
#!/usr/bin/env python3
"""
Chatbot de Soporte Técnico - TechApp
Módulo 2: OpenAI API - Proyecto Final
"""
import os
import json
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
# ============================================================================
# CONFIGURACIÓN
# ============================================================================
load_dotenv()
# Crear directorios
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__)
# Cliente OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
# ============================================================================
# CONFIGURACIÓN DEL CHATBOT
# ============================================================================
SYSTEM_MESSAGE = """
Eres un asistente de soporte técnico para TechApp, una aplicación de productividad.
Tu rol:
1. Ayudar a usuarios con preguntas frecuentes
2. Ser amigable, claro y conciso
3. Si no sabes algo, di: "Déjame escalarlo a un agente humano"
4. NUNCA inventes información (precios, fechas, features)
Información sobre TechApp:
- Password reset: Settings > Security > Reset Password
- Billing: $10/mes plan básico, $30/mes plan premium
- Features: Task management, Calendar, Notas, Sync multi-device
- Soporte: support@techapp.com
Responde siempre en español, máximo 4 frases.
""".strip()
# ============================================================================
# CLASE CHATBOT
# ============================================================================
class TechSupportBot:
"""Chatbot de soporte técnico con 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]:
"""
Envía mensaje y retorna respuesta con retry.
Args:
user_message: Mensaje del usuario
max_retries: Intentos máximos si hay error
Returns:
Respuesta del bot o None si falla
"""
# Añadir mensaje del usuario
self.messages.append({"role": "user", "content": user_message})
# Retry con 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, # Bajo (soporte consistente)
max_tokens=150, # Respuestas cortas
timeout=30.0
)
# Extraer respuesta
assistant_message = response.choices[0].message.content
self.messages.append({"role": "assistant", "content": assistant_message})
# Actualizar métricas
self._update_metrics(response.usage)
logger.info(f"✅ Request exitoso | 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("❌ Rate limit persistente")
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"❌ Error persistente: {e}")
return None
time.sleep(2 ** attempt)
except Exception as e:
logger.error(f"❌ Error inesperado: {type(e).__name__} | {e}")
return None
return None
def _update_metrics(self, usage):
"""Actualiza métricas de costo y tokens."""
self.total_tokens += usage.total_tokens
# Pricing GPT-3.5-turbo (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):
"""Muestra historial de conversación."""
print("\n" + "="*60)
print("HISTORIAL DE CONVERSACIÓN")
print("="*60)
for i, msg in enumerate(self.messages[1:], 1): # Skip system
role = "TÚ" if msg["role"] == "user" else "BOT"
print(f"\n[{i}] {role}: {msg['content']}")
print("\n" + "="*60 + "\n")
def show_cost(self):
"""Muestra costo acumulado."""
print(f"\n💰 Costo acumulado: ${self.total_cost:.6f}")
print(f"📊 Tokens totales: {self.total_tokens}")
print(f"📝 Mensajes: {len(self.messages) - 1}\n") # -1 por system
def save_conversation(self):
"""Guarda conversación en 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"💾 Conversación guardada: {filename}")
print(f"💾 Conversación guardada: {filename}")
# ============================================================================
# MAIN - CLI INTERFACE
# ============================================================================
def main():
"""CLI principal del chatbot."""
# Banner
print("\n" + "="*60)
print("🤖 CHATBOT DE SOPORTE TÉCNICO - TechApp")
print("="*60)
print("\nComandos especiales:")
print(" - 'salir' / 'exit' → Terminar conversación")
print(" - 'historial' → Ver historial completo")
print(" - 'costo' → Ver costo acumulado")
print("\n" + "="*60 + "\n")
# Crear bot
bot = TechSupportBot()
# Loop principal
while True:
try:
# Input usuario
user_input = input("Tú: ").strip()
# Comandos especiales
if user_input.lower() in ["salir", "exit", "quit"]:
print("\n👋 ¡Gracias por contactar a TechApp! Hasta pronto.\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
# Validar input
if not user_input:
print("⚠️ Por favor escribe un mensaje.\n")
continue
# Enviar a bot
response = bot.chat(user_input)
if response:
print(f"\nBot: {response}\n")
else:
print("\n❌ Lo siento, hubo un error. Por favor intenta de nuevo.\n")
except KeyboardInterrupt:
print("\n\n👋 Conversación interrumpida por usuario.\n")
bot.show_cost()
bot.save_conversation()
break
except Exception as e:
logger.critical(f"❌ Error crítico en main loop: {e}")
print(f"\n❌ Error inesperado: {e}\n")
bot.save_conversation()
break
if __name__ == "__main__":
# Verificar API key
if not os.getenv("OPENAI_API_KEY"):
print("❌ ERROR: OPENAI_API_KEY no encontrada en .env")
exit(1)
main()
🚀 Uso
1. Ejecutar:
python chatbot.py
2. Conversación ejemplo:
============================================================
🤖 CHATBOT DE SOPORTE TÉCNICO - TechApp
============================================================
Comandos especiales:
- 'salir' / 'exit' → Terminar conversación
- 'historial' → Ver historial completo
- 'costo' → Ver costo acumulado
============================================================
Tú: Hola, ¿cómo reseteo mi contraseña?
Bot: Para resetear tu contraseña en TechApp:
1. Ve a Settings
2. Selecciona Security
3. Click en Reset Password
4. Recibirás un email con instrucciones
Tú: No recibo el email
Bot: Si no recibiste el email de reset:
1. Verifica tu carpeta de Spam/Junk
2. Confirma que el email registrado es correcto
3. Espera 5 minutos (a veces hay delay)
Si sigue sin llegar, déjame escalarlo a un agente humano.
Tú: historial
============================================================
HISTORIAL DE CONVERSACIÓN
============================================================
[1] TÚ: Hola, ¿cómo reseteo mi contraseña?
[2] BOT: Para resetear tu contraseña en TechApp:
1. Ve a Settings
2. Selecciona Security
3. Click en Reset Password
4. Recibirás un email con instrucciones
[3] TÚ: No recibo el email
[4] BOT: Si no recibiste el email de reset:
1. Verifica tu carpeta de Spam/Junk
2. Confirma que el email registrado es correcto
3. Espera 5 minutos (a veces hay delay)
Si sigue sin llegar, déjame escalarlo a un agente humano.
============================================================
Tú: costo
💰 Costo acumulado: $0.000456
📊 Tokens totales: 285
📝 Mensajes: 4
Tú: salir
👋 ¡Gracias por contactar a TechApp! Hasta pronto.
💰 Costo acumulado: $0.000456
📊 Tokens totales: 285
📝 Mensajes: 4
💾 Conversación guardada: conversations/conversation_20240215_103045.json
📊 Análisis de Conversaciones
Script para analizar costos:
import json
from pathlib import Path
def analyze_conversations():
"""Analiza todas las conversaciones guardadas."""
conversations_dir = Path("conversations")
json_files = list(conversations_dir.glob("*.json"))
if not json_files:
print("No hay conversaciones guardadas aún.")
return
total_cost = 0
total_tokens = 0
total_messages = 0
print(f"\n📊 ANÁLISIS DE {len(json_files)} CONVERSACIONES\n")
print(f"{'ID':<20} {'Mensajes':<10} {'Tokens':<10} {'Costo':<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"\nPromedio por conversación: ${total_cost/len(json_files):.6f}\n")
if __name__ == "__main__":
analyze_conversations()
Ejecuta:
python analyze_conversations.py
✅ Rúbrica de Auto-Evaluación
Funcionalidad (40 puntos):
- (10 pts) Bot responde coherentemente
- (10 pts) Mantiene contexto (referencias a mensajes previos)
- (10 pts) Comandos especiales funcionan (historial, costo, salir)
- (10 pts) Conversaciones se guardan en JSON
Error Handling (30 puntos):
- (10 pts) Maneja rate limit con retry
- (10 pts) Maneja API errors con retry
- (10 pts) Logging de errores a archivo
Optimización (20 puntos):
- (10 pts) Temperature optimizado (0.3 para soporte)
- (10 pts) Max tokens limitado (150)
Cost Tracking (10 puntos):
- (10 pts) Costo calculado correctamente
Total: ___/100 puntos
Interpretación:
- 90-100: ✅ Excelente
- 70-89: ⚠️ Bueno
- <70: ❌ Revisar
🎯 Extensiones (Opcional)
1. Memoria persistente entre sesiones:
# Al iniciar, cargar conversación previa
def load_last_conversation(self):
# Buscar último JSON
# Cargar 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 con Streamlit:
pip install streamlit
import streamlit as st
st.title("🤖 TechApp Support")
user_input = st.text_input("Tú:")
if user_input:
response = bot.chat(user_input)
st.write(f"Bot: {response}")
📊 Resumen
Lo que construiste:
-
Chatbot production-ready:
- Contexto conversacional
- System message personalizado
- Parameters optimizados
-
Error handling robusto:
- Retry con exponential backoff
- Logging a archivo
- Graceful degradation
-
Cost tracking:
- Cálculo automático
- Análisis de conversaciones
-
Persistence:
- JSON por conversación
- Análisis posterior
Skills dominadas:
- ✅ OpenAI SDK completo
- ✅ Conversaciones con contexto
- ✅ Error handling en producción
- ✅ Cost optimization
- ✅ Logging y debugging
¡Felicitaciones! Completaste el Módulo 2.
🔗 Recursos adicionales
- OpenAI Best Practices
- Streamlit Docs - Para web UI
- Prompt Engineering - Mejorar system messages
➡️ Próximo paso
Siguiente módulo: Módulo 3 - LM Studio (Local GUI)
Aprenderás a ejecutar LLMs localmente en tu máquina:
- Zero costo operativo
- 100% privacidad
- API compatible con OpenAI (código reutilizable!)
Tiempo: 2-3 horas
Tiempo estimado: 60-90 minutos
¡Proyecto completado! 🎉