Spaces:
Sleeping
Sleeping
File size: 4,692 Bytes
c8b1fd7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | import os
from typing import Dict, Any
BASE_MODEL_NAME = "distilbert-base-uncased"
# Path to the LoRA adapter produced by training/train_clickbait_classifier.py
_MODEL_DIR = os.path.join(
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))),
"models",
"clickbait-lora",
)
_DEFAULT_RESULT: Dict[str, Any] = {
"clickbait": False,
"clickbait_score": 0.0,
"model_status": "not_loaded",
"explanation": "Finetuned clickbait model not found. Run "
"backend/training/train_clickbait_classifier.py and copy "
"the output into backend/models/clickbait-lora/ to enable "
"this feature.",
}
# Lazy-loaded globals - we only import torch/transformers and load
# weights the first time analyze_clickbait() is actually called, so a
# missing/broken model never slows down or crashes app startup.
_model = None
_tokenizer = None
_load_attempted = False
_load_error = None
def _try_load_model():
global _model, _tokenizer, _load_attempted, _load_error
if _load_attempted:
return
_load_attempted = True
if not os.path.isdir(_MODEL_DIR):
_load_error = f"Model directory not found at {_MODEL_DIR}"
return
try:
import torch # noqa: F401
from transformers import AutoTokenizer, AutoModelForSequenceClassification
from peft import PeftModel
tokenizer = AutoTokenizer.from_pretrained(_MODEL_DIR)
base_model = AutoModelForSequenceClassification.from_pretrained(
BASE_MODEL_NAME,
num_labels=2,
id2label={0: "not_clickbait", 1: "clickbait"},
label2id={"not_clickbait": 0, "clickbait": 1},
)
model = PeftModel.from_pretrained(base_model, _MODEL_DIR)
model.eval()
_model = model
_tokenizer = tokenizer
except Exception as e: # noqa: BLE001 - intentionally broad, this must never crash the app
_load_error = str(e)
_model = None
_tokenizer = None
def _get_headline(text: str) -> str:
"""Use the first sentence as a headline proxy if no explicit title exists."""
if not text:
return ""
first_sentence = text.strip().split(".")[0]
# Keep it short - headlines/titles are short by nature
return first_sentence[:200]
def analyze_clickbait(text: str) -> Dict[str, Any]:
"""
Returns a dict:
{
"clickbait": bool,
"clickbait_score": float (0.0 - 1.0),
"model_status": "loaded" | "not_loaded" | "error",
"explanation": str
}
This function NEVER raises. Any failure path returns a safe default
so callers (routes/analyze.py) don't need special-case error handling
beyond what they already do for the other analyzers.
"""
if not text or not text.strip():
return {
**_DEFAULT_RESULT,
"model_status": "not_loaded" if _model is None else "loaded",
"explanation": "Empty input text.",
}
_try_load_model()
if _model is None or _tokenizer is None:
result = dict(_DEFAULT_RESULT)
if _load_error:
result["explanation"] = (
"Clickbait model unavailable, using neutral default. "
f"(Reason: {_load_error})"
)
return result
try:
import torch
headline = _get_headline(text)
inputs = _tokenizer(
headline,
return_tensors="pt",
truncation=True,
max_length=64,
)
with torch.no_grad():
outputs = _model(**inputs)
probs = torch.softmax(outputs.logits, dim=-1)[0]
clickbait_score = float(probs[1].item())
return {
"clickbait": clickbait_score >= 0.5,
"clickbait_score": round(clickbait_score, 4),
"model_status": "loaded",
"explanation": (
f"Finetuned model classified this as "
f"{'likely' if clickbait_score >= 0.5 else 'unlikely'} clickbait "
f"(confidence {round(clickbait_score, 2)})."
),
}
except Exception as e: # noqa: BLE001 - never let inference errors reach the caller
return {
**_DEFAULT_RESULT,
"model_status": "error",
"explanation": f"Clickbait model inference failed, using neutral default. ({e})",
}
if __name__ == "__main__":
samples = [
"You Won't Believe What Happened Next!",
"Central Bank Raises Interest Rates by 0.25 Percent",
]
for s in samples:
print(s, "->", analyze_clickbait(s))
|