| import torch |
| import asyncio |
| import re |
| import uvicorn |
| import ahocorasick |
| from fastapi import FastAPI, HTTPException |
| from pydantic import BaseModel, Field |
| from typing import List, Dict |
| from transformers import pipeline |
| from functools import lru_cache |
| from concurrent.futures import ThreadPoolExecutor |
| from fastapi.responses import RedirectResponse |
|
|
| |
| device = -1 |
| app = FastAPI(title="ML Comment Service", version="2.0.1") |
|
|
| |
| executor = ThreadPoolExecutor(max_workers=4) |
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| |
| |
|
|
| |
| intent_pipe = pipeline("text-classification" , model="lalit-narayan/youtube-comment-intent-classifier" , device=device) |
|
|
| |
| toxicity_model = pipeline("text-classification", model="martin-ha/toxic-comment-model" , device=device) |
|
|
| |
| sent_pipe = pipeline("sentiment-analysis",model="AmaanP314/youtube-xlm-roberta-base-sentiment-multilingual",device=device) |
|
|
| |
| spam_pipe = pipeline("text-classification", model="valurank/distilroberta-spam-comments-detection", device=device) |
|
|
| class CommentInput(BaseModel): |
| text: str = Field(..., min_length=1, max_length=1000) |
|
|
| class AnalysisResponse(BaseModel): |
| intent: str |
| intent_confidence: float |
| sentiment: str |
| sentiment_confidence: float |
| toxicity: bool |
| is_spam: bool |
|
|
| def preprocess_text(text: str) -> str: |
| return " ".join(text.strip().lower().split()) |
|
|
| |
|
|
| |
| def rule_signals(text: str): |
| text_lower = re.sub(r"(.)\1{2,}", r"\1", text.lower()) |
|
|
| |
| link_pattern = r"(https?://|www\.|[a-z0-9]+\s*(\.|dot)\s*(com|in|net)|bit\s*\.?\s*ly|t\s*\.?\s*me|youtu\s*\.?\s*be)" |
| has_link = bool(re.search(link_pattern, text_lower)) |
|
|
| |
| handle_pattern = r"(@[A-Za-z0-9_]{3,})" |
| has_handle = bool(re.search(handle_pattern, text_lower)) |
|
|
| |
| cta_keywords = [ |
| "join", "contact", "dm", "dm me", "message", "msg", |
| "whatsapp", "click", "come to", "check bio", |
| "reach me", "telegram" |
| ] |
| has_cta = any(word in text_lower for word in cta_keywords) |
|
|
| |
| promo_pattern = r"(s\s?[uv]\s?b|channel|video|subscribe|follow|mast|maza|op content|best teacher)" |
| has_promo = bool(re.search(promo_pattern, text_lower)) |
|
|
| |
| scam_pattern = r"(earn|money|paisa|kamao|invest|profit|winner|giveaway|[\$₹£]|crypto|free|cash|sub\s*4\s*sub|leak|iphone|win|offer|limited|bonus)" |
| has_scam = bool(re.search(scam_pattern, text_lower)) |
|
|
| |
| complaint_pattern = r"(too\s(small|loud|blurry|fast|slow)|broken|not working|error|404|issue|waste|outdated)" |
| has_complaint_signal = bool(re.search(complaint_pattern, text_lower)) |
|
|
| |
| has_adult = any(word in text_lower for word in ["18+", "xxx", "sex", "nude", "hot girl"]) |
|
|
| |
| phone_pattern = r"(\+?\d[\d\-\s]{8,}\d)" |
| has_phone = bool(re.search(phone_pattern, text)) |
|
|
| |
| repetition_pattern = r"\b(\w+)(\s+\1\b){2,}" |
| has_repetition = bool(re.search(repetition_pattern, text_lower)) |
|
|
| return { |
| "has_link": has_link, |
| "has_promo": has_promo, |
| "has_phone": has_phone, |
| "has_scam": has_scam, |
| "has_adult": has_adult, |
| "has_handle": has_handle, |
| "has_cta": has_cta, |
| "has_complaint": has_complaint_signal, |
| "has_repetition": has_repetition |
| } |
|
|
|
|
| def is_comment_spam(text: str) -> dict: |
| signals = rule_signals(text) |
|
|
| res = spam_pipe(text)[0] |
| spam_ml_score = res["score"] if res["label"] == "spam" else 1 - res["score"] |
|
|
| |
| if signals["has_phone"] or signals["has_adult"]: |
| return {"is_spam": True, "force_intent": "spam"} |
|
|
| if signals["has_scam"]: |
| return {"is_spam": True, "force_intent": "spam"} |
|
|
| if signals.get("has_repetition"): |
| return {"is_spam": True, "force_intent": "spam"} |
|
|
| |
| if spam_ml_score > 0.75: |
| return {"is_spam": True, "force_intent": "spam"} |
|
|
| |
| score = 0 |
|
|
| if signals["has_link"]: score += 2 |
| if signals["has_handle"]: score += 2 |
| if signals["has_cta"]: score += 2 |
| if signals["has_promo"]: score += 1 |
| if signals["has_scam"]: score += 2 |
|
|
| score += int(spam_ml_score * 5) |
|
|
| if score >= 3: |
| return {"is_spam": True, "force_intent": "spam"} |
|
|
| return {"is_spam": False} |
| |
| |
|
|
| |
|
|
|
|
| |
|
|
| |
| def init_moderator(filepath): |
| A = ahocorasick.Automaton() |
| with open(filepath, "r", encoding="utf-8") as f: |
| for idx, line in enumerate(f): |
| word = line.strip().lower() |
| if word and not word.startswith("#"): |
| A.add_word(word, (idx, word)) |
| A.make_automaton() |
| return A |
|
|
| |
| blocklist_engine = init_moderator("blocklist.txt") |
|
|
| def is_toxic(comment: str) -> bool: |
| """ |
| Returns True if comment is toxic (via Blocklist or AI), False otherwise. |
| """ |
| if not comment or len(comment.strip()) == 0: |
| return False |
|
|
| clean_comment = comment.lower().strip() |
|
|
| |
| |
| for end_index, (idx, matched_word) in blocklist_engine.iter(clean_comment): |
| |
| return True |
|
|
| |
| |
| try: |
| result = toxicity_model(clean_comment)[0] |
| |
| if result['label'] == 'toxic' and result['score'] > 0.7: |
| return True |
| except Exception as e: |
| print(f"Model Error: {e}") |
| return False |
|
|
| return False |
|
|
| |
|
|
|
|
| |
| @lru_cache(maxsize=2048) |
| def get_heavy_predictions(text: str) -> Dict: |
| |
| |
| id_to_label = { |
| "LABEL_0": "appreciation", |
| "LABEL_1": "question", |
| "LABEL_2": "complaint" |
| } |
|
|
| |
| intent_output = intent_pipe(text)[0] |
| |
| |
| |
| |
| |
| raw_label = str(intent_output["label"]) |
| |
| |
| tox_res = is_toxic(text) |
| |
|
|
| |
| sent_res = sent_pipe(text)[0] |
|
|
| |
| return { |
| "intent": id_to_label.get(raw_label, "Unknown"), |
| "intent_confidence": round(intent_output["score"], 3), |
| "sentiment": sent_res["label"].lower(), |
| "sentiment_confidence": round(sent_res["score"], 3), |
| "toxicity": tox_res |
| } |
|
|
| |
| @app.get("/", include_in_schema=False) |
| def root(): |
| return RedirectResponse(url="/docs") |
|
|
| @app.get("/health") |
| def health(): |
| return {"status": "healthy", "device": "CPU"} |
|
|
| @app.post("/analyze", response_model=AnalysisResponse) |
| async def analyze_comment(data: CommentInput): |
| cleaned = preprocess_text(data.text) |
| spam_result = is_comment_spam(cleaned) |
|
|
| if spam_result["is_spam"]: |
| return { |
| "intent": "complaint", |
| "intent_confidence": 1.0, |
| "sentiment": "neutral", "sentiment_confidence": 1.0, |
| "toxicity": False, "is_spam": True |
| } |
|
|
| |
| loop = asyncio.get_event_loop() |
| heavy_results = await loop.run_in_executor(executor, get_heavy_predictions, cleaned) |
| |
| |
|
|
| return {**heavy_results, "is_spam": False} |
|
|
| @app.post("/analyze-batch", response_model=List[AnalysisResponse]) |
| async def analyze_batch(data: List[CommentInput]): |
| responses = [] |
| for item in data: |
| res = await analyze_comment(item) |
| responses.append(res) |
| return responses |
|
|
| if __name__ == "__main__": |
| uvicorn.run(app, host="0.0.0.0", port=7860) |