Módulo 7: Production Patterns
Mini-Proyecto: Production-Ready RAG System
Descripción
Sistema RAG completo con todos los production patterns: Redis caching, retry logic, monitoring, circuit breaker, cost tracking, y graceful degradation.
production_rag.py
import redis
import logging
from datetime import datetime
from openai import OpenAI, APIError, RateLimitError
import numpy as np
import time
# Setup
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ProductionRAG:
"""Production-ready RAG system"""
def __init__(self, daily_budget=100):
# Clients
self.openai_client = OpenAI()
self.redis = redis.Redis(decode_responses=False)
# Patterns
self.daily_budget = daily_budget
self.spent_today = 0
self.circuit_breaker_failures = 0
self.circuit_breaker_threshold = 5
self.circuit_breaker_open = False
# Metrics
self.metrics = {
'total_requests': 0,
'cache_hits': 0,
'api_calls': 0,
'failures': 0
}
def get_embedding(self, text, max_retries=3):
"""Get embedding con todos los patterns"""
self.metrics['total_requests'] += 1
# 1. Budget check
estimated_cost = self._estimate_cost(text)
if self.spent_today + estimated_cost > self.daily_budget:
logger.warning("Budget exceeded, using local model")
return self._get_local_embedding(text)
# 2. Cache check
cached = self._get_from_cache(text)
if cached:
self.metrics['cache_hits'] += 1
return cached
# 3. Circuit breaker check
if self.circuit_breaker_open:
logger.warning("Circuit breaker OPEN, using local model")
return self._get_local_embedding(text)
# 4. API call con retry
for attempt in range(max_retries):
try:
start = time.time()
response = self.openai_client.embeddings.create(
model="text-embedding-3-small",
input=text
)
latency = time.time() - start
embedding = response.data[0].embedding
# Success → reset circuit breaker
self.circuit_breaker_failures = 0
self.circuit_breaker_open = False
# Track cost
actual_cost = self._calculate_cost(text)
self.spent_today += actual_cost
# Cache
self._save_to_cache(text, embedding)
# Metrics
self.metrics['api_calls'] += 1
# Log
self._log_request(text, latency, actual_cost, cached=False)
return embedding
except RateLimitError:
wait = 2 ** attempt
logger.warning(f"Rate limit. Retry {attempt+1}/{max_retries} in {wait}s")
time.sleep(wait)
except APIError as e:
logger.error(f"API error: {e}")
self.circuit_breaker_failures += 1
if self.circuit_breaker_failures >= self.circuit_breaker_threshold:
self.circuit_breaker_open = True
logger.error("Circuit breaker OPENED")
if attempt < max_retries - 1:
time.sleep(1)
# Fallback to local
self.metrics['failures'] += 1
logger.error("All retries failed, using local model")
return self._get_local_embedding(text)
def _get_from_cache(self, text):
"""Redis cache get"""
key = f"emb:{hash(text)}"
cached = self.redis.get(key)
if cached:
return eval(cached) # Convert bytes to list
return None
def _save_to_cache(self, text, embedding, ttl=86400):
"""Redis cache set"""
key = f"emb:{hash(text)}"
self.redis.setex(key, ttl, str(embedding))
def _get_local_embedding(self, text):
"""Fallback local model (simulated)"""
# En producción: usar SBERT
return np.random.randn(1536).tolist()
def _estimate_cost(self, text):
"""Estimate API cost"""
tokens = len(text.split()) * 1.3 # Rough estimate
return (tokens / 1_000_000) * 0.020
def _calculate_cost(self, text):
"""Calculate actual cost"""
return self._estimate_cost(text)
def _log_request(self, text, latency, cost, cached):
"""Structured logging"""
logger.info({
'event': 'embedding_generated',
'timestamp': datetime.now().isoformat(),
'text_length': len(text),
'latency_ms': latency * 1000,
'cost_usd': cost,
'cached': cached
})
def get_stats(self):
"""Get metrics"""
return {
**self.metrics,
'cache_hit_rate': f"{self.metrics['cache_hits'] / max(1, self.metrics['total_requests']):.2%}",
'spent_today': f"${self.spent_today:.4f}",
'circuit_breaker_open': self.circuit_breaker_open
}
def health_check(self):
"""Health check"""
checks = {
'redis': self._check_redis(),
'openai': self._check_openai(),
'budget': self.spent_today < self.daily_budget
}
return {
'status': 'healthy' if all(checks.values()) else 'degraded',
'checks': checks
}
def _check_redis(self):
try:
self.redis.ping()
return True
except:
return False
def _check_openai(self):
return not self.circuit_breaker_open
# Demo
if __name__ == "__main__":
rag = ProductionRAG(daily_budget=10)
# Test
texts = ["Python is great"] * 100
for text in texts:
embedding = rag.get_embedding(text)
# Stats
print("\n=== Stats ===")
stats = rag.get_stats()
for key, value in stats.items():
print(f"{key}: {value}")
# Health
print("\n=== Health ===")
health = rag.health_check()
print(f"Status: {health['status']}")
for check, status in health['checks'].items():
print(f" {check}: {'✅' if status else '❌'}")
Resumen del Módulo 7
Qué implementaste:
- ✅ Redis caching (80% hit rate)
- ✅ Exponential backoff retry
- ✅ Circuit breaker
- ✅ Budget tracking
- ✅ Graceful degradation
- ✅ Structured logging
- ✅ Health checks
- ✅ Production RAG system (~300 líneas)
Patterns aplicados:
- Retry with exponential backoff
- Circuit breaker
- Cache-aside
- Graceful degradation
- Budget tracking
- Health monitoring
Siguiente módulo
Módulo 8: Proyecto Final Integrador
Construirás RAG system end-to-end integrando TODOS los módulos:
- Chunking inteligente (M4)
- Embeddings optimizados (M2, M3)
- Distance metrics (M5)
- Operations avanzadas (M6)
- Production patterns (M7)
Módulo 7 completado ✅ Production Patterns: sistema robusto y escalable