Spaces:
Runtime error
Runtime error
| from flask import Flask, request, jsonify | |
| from flask_cors import CORS | |
| import os | |
| import logging | |
| import torch | |
| import threading | |
| from transformers import ( | |
| AutoTokenizer, | |
| AutoModelForSeq2SeqLM, | |
| AutoModelForCausalLM, | |
| AutoConfig, | |
| ) | |
| # Configure logging | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| app = Flask(__name__) | |
| CORS(app) | |
| # Set environment variables | |
| os.environ['HF_HUB_DISABLE_TELEMETRY'] = '1' | |
| # Explainer model globals | |
| explanation_tokenizer = None | |
| explanation_model = None | |
| explanation_is_encoder_decoder = None | |
| model_loaded = False # Tracks readiness | |
| # Configuration via environment | |
| EXPLAINER_MODEL_NAME = os.environ.get('EXPLAINER_MODEL_NAME', 'HuggingFaceH4/zephyr-7b-beta').strip() | |
| EXPLAINER_TRUST_REMOTE_CODE = os.environ.get('EXPLAINER_TRUST_REMOTE_CODE', '0').strip() in ('1', 'true', 'True') | |
| def load_explainer(): | |
| """Load local explanation model in background.""" | |
| global explanation_tokenizer, explanation_model, explanation_is_encoder_decoder, model_loaded | |
| try: | |
| logger.info(f"Loading explainer model: {EXPLAINER_MODEL_NAME} ...") | |
| config = AutoConfig.from_pretrained(EXPLAINER_MODEL_NAME, trust_remote_code=EXPLAINER_TRUST_REMOTE_CODE) | |
| explanation_is_encoder_decoder = getattr(config, 'is_encoder_decoder', False) | |
| explanation_tokenizer = AutoTokenizer.from_pretrained(EXPLAINER_MODEL_NAME, trust_remote_code=EXPLAINER_TRUST_REMOTE_CODE) | |
| if explanation_is_encoder_decoder: | |
| explanation_model = AutoModelForSeq2SeqLM.from_pretrained(EXPLAINER_MODEL_NAME, trust_remote_code=EXPLAINER_TRUST_REMOTE_CODE) | |
| else: | |
| explanation_model = AutoModelForCausalLM.from_pretrained(EXPLAINER_MODEL_NAME, trust_remote_code=EXPLAINER_TRUST_REMOTE_CODE) | |
| explanation_model.eval() | |
| model_loaded = True | |
| arch = 'seq2seq' if explanation_is_encoder_decoder else 'causal' | |
| logger.info(f"Explainer model loaded successfully! Detected architecture: {arch}") | |
| except Exception as e: | |
| logger.error(f"Error loading explainer model: {e}") | |
| explanation_tokenizer = None | |
| explanation_model = None | |
| explanation_is_encoder_decoder = None | |
| def generate_explanation_local(message: str, label: str) -> str | None: | |
| """Generate explanation using local model if available.""" | |
| if not model_loaded: | |
| return None | |
| try: | |
| system_instruction = ( | |
| f"Explain in a short and concise why the the text was classified as '{label}'. " | |
| "If phishing Highlight signals such as urgency, credential requests, suspicious links, sender authenticity, grammar, tone, or context." | |
| ) | |
| prompt = f"{system_instruction}\n\nMessage:\n{message}\n\nExplanation:" | |
| if explanation_is_encoder_decoder: | |
| inputs = explanation_tokenizer(prompt, return_tensors="pt", truncation=True, padding=True, max_length=512) | |
| with torch.no_grad(): | |
| output_ids = explanation_model.generate( | |
| **inputs, | |
| max_new_tokens=180, | |
| temperature=0.7, | |
| top_p=0.95, | |
| do_sample=True, | |
| num_return_sequences=1, | |
| ) | |
| return explanation_tokenizer.decode(output_ids[0], skip_special_tokens=True).strip() | |
| else: | |
| inputs = explanation_tokenizer(prompt, return_tensors="pt", truncation=True, padding=True, max_length=1024) | |
| with torch.no_grad(): | |
| output_ids = explanation_model.generate( | |
| **inputs, | |
| max_new_tokens=200, | |
| temperature=0.7, | |
| top_p=0.95, | |
| do_sample=True, | |
| pad_token_id=explanation_tokenizer.eos_token_id or explanation_tokenizer.pad_token_id, | |
| ) | |
| generated = explanation_tokenizer.decode(output_ids[0], skip_special_tokens=True) | |
| if generated.startswith(prompt): | |
| generated = generated[len(prompt):] | |
| return generated.strip() | |
| except Exception as e: | |
| logger.error(f"Error generating local explanation: {e}") | |
| return None | |
| def home(): | |
| return jsonify({ | |
| "status": "healthy", | |
| "message": "Anti-Phishing Explanation API", | |
| "endpoints": { | |
| "/explain": "POST - Generate explanation for a classification", | |
| "/health": "GET - Health check" | |
| } | |
| }) | |
| def health(): | |
| return jsonify({ | |
| "status": "healthy", | |
| "model_ready": model_loaded, | |
| "architecture": 'seq2seq' if explanation_is_encoder_decoder else ('causal' if model_loaded else None) | |
| }) | |
| def explain(): | |
| if not model_loaded: | |
| return jsonify({"error": "Model not yet loaded"}), 503 | |
| try: | |
| data = request.get_json() | |
| if not data or "message" not in data or "label" not in data: | |
| return jsonify({"error": "Missing 'message' or 'label' field"}), 400 | |
| message = data["message"] | |
| label = data["label"] | |
| if not message.strip() or label not in ("Safe", "Phishing"): | |
| return jsonify({"error": "Invalid 'message' or 'label'"}), 400 | |
| explanation = generate_explanation_local(message.strip(), label) | |
| return jsonify({ | |
| "label": label, | |
| "message": message, | |
| "explanation": explanation or "" | |
| }) | |
| except Exception as e: | |
| logger.error(f"Error in explain endpoint: {e}") | |
| return jsonify({"error": "Internal server error"}), 500 | |
| if __name__ == "__main__": | |
| threading.Thread(target=load_explainer, daemon=True).start() # Load model after server starts | |
| app.run(debug=False, host="0.0.0.0", port=7860) |