Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion debug_ci_robust.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

1 change: 0 additions & 1 deletion debug_ci_timing.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@

16 changes: 8 additions & 8 deletions debug_rate_limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,43 +13,43 @@ def debug_rate_limiter():
"""Debug the rate limiter behavior."""
print("🔍 Debugging Rate Limiter Issue")
print("=" * 50)

# Create config with minimal settings (same as test)
config = RateLimitConfig(requests_per_minute=1, burst_size=1)
print(f"Config: requests_per_minute={config.requests_per_minute}, burst_size={config.burst_size}")

rate_limiter = TokenBucketRateLimiter(config)
print(f"Initial buckets: {rate_limiter.buckets}")
print(f"Initial last_refill: {rate_limiter.last_refill}")

# Test first request
print("\n🚀 Testing First Request...")
allowed1, reason1, meta1 = rate_limiter.allow_request("127.0.0.1")
print(f"First request - Allowed: {allowed1}, Reason: {reason1}")
print(f"Meta: {meta1}")
print(f"Buckets after first request: {rate_limiter.buckets}")
print(f"Last refill after first request: {rate_limiter.last_refill}")

# Test second request
print("\n🚀 Testing Second Request...")
allowed2, reason2, meta2 = rate_limiter.allow_request("127.0.0.1")
print(f"Second request - Allowed: {allowed2}, Reason: {reason2}")
print(f"Meta: {meta2}")
print(f"Buckets after second request: {rate_limiter.buckets}")

# Check what's in the bucket for this client
client_key = rate_limiter._get_client_key("127.0.0.1")
print(f"\n🔑 Client key: {client_key}")
print(f"Bucket value for client: {rate_limiter.buckets[client_key]}")
print(f"Last refill time for client: {rate_limiter.last_refill[client_key]}")

# Check if client is blocked
print(f"Client blocked: {rate_limiter._is_client_blocked(client_key)}")
print(f"Blocked clients: {rate_limiter.blocked_clients}")

# Check concurrent requests
print(f"Concurrent requests: {rate_limiter.concurrent_requests}")

# Check request history
print(f"Request history: {list(rate_limiter.request_history[client_key])}")

Expand Down
20 changes: 10 additions & 10 deletions deployment/api_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,17 +37,17 @@ def predict_emotion():
"""Predict emotion for given text"""
if detector is None:
return jsonify({'error': 'Model not loaded'}), 500

try:
data = request.get_json()
text = data.get('text', '')

if not text:
return jsonify({'error': 'No text provided'}), 400

result = detector.predict(text)
return jsonify(result)

except Exception as e:
logger.error(f"Prediction error: {e}")
return jsonify({'error': str(e)}), 500
Expand All @@ -57,17 +57,17 @@ def predict_batch():
"""Predict emotions for multiple texts"""
if detector is None:
return jsonify({'error': 'Model not loaded'}), 500

try:
data = request.get_json()
texts = data.get('texts', [])

if not texts:
return jsonify({'error': 'No texts provided'}), 400

results = detector.predict_batch(texts)
return jsonify({'results': results})

except Exception as e:
logger.error(f"Batch prediction error: {e}")
return jsonify({'error': str(e)}), 500
Expand All @@ -77,7 +77,7 @@ def get_emotions():
"""Get list of supported emotions"""
if detector is None:
return jsonify({'error': 'Model not loaded'}), 500

return jsonify({
'emotions': list(detector.label_encoder.classes_),
'count': len(detector.label_encoder.classes_)
Expand All @@ -94,5 +94,5 @@ def get_emotions():
print(" - POST /predict_batch - Batch prediction")
print(" - GET /emotions - List emotions")
print("=" * 50)

app.run(host='0.0.0.0', port=5000, debug=False)
72 changes: 36 additions & 36 deletions deployment/cloud-run/robust_predict.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,42 +41,42 @@
def load_model():
"""Load the emotion detection model"""
global model, tokenizer, emotion_mapping, model_loading, model_loaded, model_lock

with model_lock:
if model_loading or model_loaded:
return

model_loading = True
logger.info("🔄 Starting model loading...")

try:
# Get model path
model_path = Path("/app/model")
logger.info(f"📁 Loading model from: {model_path}")

# Check if model files exist
if not model_path.exists():
raise FileNotFoundError(f"Model directory not found: {model_path}")

# Load tokenizer and model
logger.info("📥 Loading tokenizer...")
tokenizer = AutoTokenizer.from_pretrained("roberta-base")

logger.info("📥 Loading model...")
model = AutoModelForSequenceClassification.from_pretrained(str(model_path))

# Set device (CPU for Cloud Run)
device = torch.device('cpu')
model.to(device)
model.eval()

emotion_mapping = EMOTION_MAPPING
model_loaded = True
model_loading = False

logger.info(f"✅ Model loaded successfully on {device}")
logger.info(f"🎯 Supported emotions: {emotion_mapping}")

except Exception:
model_loading = False
logger.exception("❌ Failed to load model")
Expand All @@ -87,7 +87,7 @@ def load_model():
def predict_emotion(text):
"""Predict emotion for given text"""
global model, tokenizer, emotion_mapping

if not model_loaded:
raise RuntimeError("Model not loaded")

Expand All @@ -99,17 +99,17 @@ def predict_emotion(text):

# Tokenize
inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=MAX_INPUT_LENGTH, padding=True)

# Predict
with torch.no_grad():
outputs = model(**inputs)
probabilities = torch.softmax(outputs.logits, dim=1)
predicted_class = torch.argmax(probabilities, dim=1).item()
confidence = probabilities[0][predicted_class].item()

# Map to emotion name
emotion = emotion_mapping[predicted_class]

return {
"emotion": emotion,
"confidence": confidence,
Expand All @@ -120,7 +120,7 @@ def ensure_model_loaded():
"""Ensure model is loaded before processing requests"""
if not model_loaded and not model_loading:
load_model()

if not model_loaded:
raise RuntimeError("Model not loaded")

Expand Down Expand Up @@ -159,27 +159,27 @@ def predict():
try:
# Ensure model is loaded
ensure_model_loaded()

# Content-type validation
if not request.is_json:
return jsonify({'error': 'Content-Type must be application/json'}), 400

try:
data = request.get_json()
except Exception:
return jsonify({'error': 'Invalid JSON data'}), 400

if not data:
return jsonify({'error': 'No JSON data provided'}), 400

text = data.get('text', '')
if not text:
return jsonify({'error': 'No text provided'}), 400

# Make prediction
result = predict_emotion(text)
return jsonify(result)

except Exception:
return create_error_response('Prediction processing failed. Please try again later.')

Expand All @@ -189,31 +189,31 @@ def predict_batch():
try:
# Ensure model is loaded
ensure_model_loaded()

# Content-type validation
if not request.is_json:
return jsonify({'error': 'Content-Type must be application/json'}), 400

try:
data = request.get_json()
except Exception:
return jsonify({'error': 'Invalid JSON data'}), 400

if not data:
return jsonify({'error': 'No JSON data provided'}), 400

texts = data.get('texts', [])
if not texts:
return jsonify({'error': 'No texts provided'}), 400

# Make predictions
results = []
for text in texts:
result = predict_emotion(text)
results.append(result)

return jsonify({'results': results})

except Exception:
return create_error_response('Batch prediction processing failed. Please try again later.')

Expand Down Expand Up @@ -260,34 +260,34 @@ def initialize_model():
logger.info(" - GET /emotions - List emotions")
logger.info(" - GET /model_status - Model status")
logger.info("=" * 50)

# Load model immediately
try:
load_model()
except Exception:
logger.exception("Failed to load model on startup")

# Get port from environment (Cloud Run requirement)
port = int(os.environ.get('PORT', 8080))

# Use production WSGI server for better performance and reliability
import gunicorn.app.base

class StandaloneApplication(gunicorn.app.base.BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()

def load_config(self):
config = {key: value for key, value in self.options.items()
if key in self.cfg.settings and value is not None}
for key, value in config.items():
self.cfg.set(key.lower(), value)

def load(self):
return self.application

options = {
'bind': f'0.0.0.0:{port}',
'workers': 1, # Single worker for Cloud Run
Expand All @@ -300,5 +300,5 @@ def load(self):
'error_logfile': '-',
'loglevel': 'info'
}

StandaloneApplication(app, options).run()
Loading