RawiPostReview / app.py
walker11's picture
Update app.py
15a42d4 verified
import os
import json
import logging
from typing import Dict, Any, List
import requests
from datetime import datetime
import re
from flask import Flask, request, jsonify
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class ArabicContentModerator:
"""
Arabic Story Content Moderation Model using Deepseek API
Checks for cultural violations and inappropriate content
"""
def __init__(self, deepseek_api_key: str = None):
"""
Initialize the content moderator
Args:
deepseek_api_key: Deepseek API key
"""
self.api_key = deepseek_api_key or os.getenv('DEEPSEEK_API_KEY')
if not self.api_key:
raise ValueError("Deepseek API key is required")
self.api_url = "https://api.deepseek.com/chat/completions"
self.headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json"
}
# Enhanced Arabic Content Moderation with News Detection
self.moderation_prompt = """
أنت مراجع محتوى عربي محترف متخصص في التمييز بين القصص الأدبية والمحتوى الإخباري. مهمتك مراجعة النصوص العربية ورفض أي محتوى غير أدبي.
## معايير الرفض الصارمة:
### 1. المحتوى الإخباري والصحفي - رفض فوري:
**يجب رفض النصوص التي تحتوي على:**
**أ) التقارير الرياضية:**
- "بعد المباراة خرج وقال"
- "اللاعب تألق ومنع أهداف"
- "فاز بجائزة رجل المباراة"
- "المباراة انتهت بنتيجة"
- "في الشوط الأول"
- "المدرب صرح"
**ب) المؤتمرات الصحفية:**
- "في مؤتمر صحفي"
- "صرح الوزير"
- "أعلن المسؤول"
- "في تصريحات خاصة"
- "قال النائب"
- "أكد الخبير"
**ج) الاجتماعات والفعاليات:**
- "في اجتماع اليوم"
- "خلال الجلسة"
- "في المنتدى"
- "أثناء المؤتمر"
- "في الورشة"
- "خلال اللقاء"
**د) الأخبار السياسية:**
- "الرئيس التقى"
- "الوزير أعلن"
- "البرلمان ناقش"
- "الحكومة قررت"
- "السفير وصل"
- "الوزارة أصدرت"
**هـ) الأخبار الاقتصادية:**
- "البورصة ارتفعت"
- "أسعار النفط"
- "الدولار سجل"
- "الشركة حققت"
- "الاستثمارات بلغت"
- "التضخم وصل"
**و) التقارير التقنية:**
- "التطبيق الجديد"
- "الهاتف يتميز"
- "الخاصية الجديدة"
- "التحديث يتضمن"
- "النظام يدعم"
- "البرنامج أضاف"
**ز) الأخبار المحلية:**
- "في محافظة"
- "بلدية المدينة"
- "المحافظ افتتح"
- "المجلس المحلي"
- "الأهالي طالبوا"
- "الخدمات تحسنت"
### 2. العلامات المميزة للمحتوى الإخباري:
- استخدام أسماء حقيقية لأشخاص مشهورين
- ذكر مباريات وأحداث رياضية محددة
- استخدام مصطلحات إخبارية ("صرح"، "أعلن"، "أكد")
- التواريخ والأرقام الإحصائية
- ذكر مؤسسات وشركات حقيقية
- النبرة الرسمية والتقريرية
### 3. المحتوى الأدبي المقبول:
**يجب قبول النصوص التي تحتوي على:**
- شخصيات خيالية أو مجهولة الهوية
- أحداث متخيلة أو درامية
- حوار إبداعي وعاطفي
- وصف الشخصيات والأماكن
- صراع نفسي أو اجتماعي
- نهاية مفتوحة أو رسالة أدبية
- استخدام التشبيهات والمجازات
- الأسلوب السردي الإبداعي
### 4. الانتهاكات الدينية - فحص صارم:
**رفض فوري للمحتوى الذي يحتوي على:**
- أي استهزاء أو تهكم على الله أو الأنبياء
- انتقاد الآيات القرآنية أو الأحاديث
- السخرية من الشعائر الدينية
- التطاول على الصحابة
- التجديف أو الكفر الصريح
- السب بالدين
### 5. السب والشتم - فحص صارم:
**رفض فوري للمحتوى الذي يحتوي على:**
- الألفاظ الجنسية الصريحة
- السب بالأعضاء التناسلية
- الألفاظ الإخراجية
- إهانة الأم أو العرض
- السب العرقي بألفاظ قبيحة
- الكلمات المبتذلة الخادشة
## أمثلة للرفض:
**مثال إخباري رياضي (يجب رفضه):**
"لويس سواريز بعد المباراة خرج قال كنا نستطيع الفوز... الشناوي تألق ومنع 3 أهداف مؤكدة... فاز بجائزة رجل المباراة"
**مثال مؤتمر صحفي (يجب رفضه):**
"في مؤتمر صحفي اليوم، صرح الوزير بأن الحكومة ستتخذ إجراءات..."
**مثال اجتماع (يجب رفضه):**
"خلال اجتماع مجلس الإدارة أمس، تم الاتفاق على..."
## أمثلة للقبول:
**قصة أدبية (يجب قبولها):**
"كان يجلس في المقهى كل مساء، يراقب الناس ويحلم بحياة أخرى. في ذلك المساء، دخلت امرأة غريبة غيرت كل شيء..."
**حوار درامي (يجب قبوله):**
"قالت له بصوت مرتجف: لماذا تركتني؟ أجاب وهو يتجنب نظراتها: بعض الأشياء لا يمكن إصلاحها..."
## الاستجابة المطلوبة:
بعد المراجعة، أجب بكلمة واحدة فقط:
- "true" - إذا كان النص قصة أدبية إبداعية خالية من الانتهاكات
- "no" - إذا كان النص إخبارياً أو يحتوي على انتهاكات دينية أو سب فاحش
النص المطلوب مراجعته:
"""
def _call_deepseek_api(self, story_content: str) -> Dict[str, Any]:
"""
Call Deepseek API for content moderation
Args:
story_content: The Arabic story content to moderate
Returns:
API response dictionary
"""
try:
payload = {
"model": "deepseek-chat",
"messages": [
{
"role": "system",
"content": "أنت مراجع محتوى عربي محترف متخصص في التمييز بين القصص الأدبية والمحتوى الإخباري. يجب عليك رفض أي محتوى إخباري أو صحفي بصرامة."
},
{
"role": "user",
"content": f"{self.moderation_prompt}\n\n{story_content}"
}
],
"max_tokens": 10,
"temperature": 0.0,
"stream": False
}
response = requests.post(
self.api_url,
headers=self.headers,
json=payload,
timeout=30
)
if response.status_code == 200:
return response.json()
else:
logger.error(f"API Error: {response.status_code} - {response.text}")
return {"error": f"API Error: {response.status_code}"}
except Exception as e:
logger.error(f"Exception calling Deepseek API: {str(e)}")
return {"error": str(e)}
def _pre_check_news_content(self, story_content: str) -> bool:
"""
Pre-check for obvious news content patterns
Args:
story_content: Content to check
Returns:
True if appears to be news content, False otherwise
"""
# News indicators in Arabic
news_patterns = [
r'بعد المباراة.*قال',
r'في مؤتمر صحفي',
r'صرح.*الوزير',
r'أعلن.*المسؤول',
r'فاز.*بجائزة.*رجل المباراة',
r'تألق.*ومنع.*أهداف',
r'خلال.*الاجتماع',
r'في.*الجلسة',
r'الرئيس.*التقى',
r'البرلمان.*ناقش',
r'الحكومة.*قررت',
r'البورصة.*ارتفعت',
r'أسعار.*النفط',
r'الشركة.*حققت',
r'المحافظ.*افتتح',
r'بلدية.*المدينة',
r'التطبيق.*الجديد',
r'الهاتف.*يتميز',
r'في.*محافظة'
]
# Check for news patterns
for pattern in news_patterns:
if re.search(pattern, story_content, re.IGNORECASE):
return True
# Check for sports-specific terms
sports_terms = ['المباراة', 'اللاعب', 'المدرب', 'الفريق', 'الهدف', 'الشوط']
news_verbs = ['صرح', 'أعلن', 'أكد', 'قال', 'فاز', 'تألق']
has_sports = any(term in story_content for term in sports_terms)
has_news_verbs = any(verb in story_content for verb in news_verbs)
if has_sports and has_news_verbs:
return True
return False
def _validate_story_format(self, story_content: str) -> bool:
"""
Enhanced validation of story format and content
Args:
story_content: Story content to validate
Returns:
Boolean indicating if format is valid
"""
if not story_content or not isinstance(story_content, str):
return False
# Check minimum length (at least 50 characters for a meaningful story)
if len(story_content.strip()) < 50:
return False
# Check for Arabic characters (must have substantial Arabic content)
arabic_pattern = re.compile(r'[\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB50-\uFDFF\uFE70-\uFEFF]')
arabic_chars = len(arabic_pattern.findall(story_content))
# Arabic characters should be at least 30% of total characters
if arabic_chars < len(story_content.strip()) * 0.3:
return False
# Pre-check for obvious news content
if self._pre_check_news_content(story_content):
return False
return True
def moderate_story(self, story_content: str) -> Dict[str, Any]:
"""
Main method to moderate Arabic story content with enhanced validation
Args:
story_content: The Arabic story to moderate
Returns:
Dictionary with moderation result
"""
# Enhanced validation
if not self._validate_story_format(story_content):
return {
"approved": False,
"response": "no",
"reason": "المحتوى يبدو أنه تقرير إخباري أو صحفي وليس قصة أدبية، أو فشل في التحقق من صحة التنسيق",
"timestamp": datetime.now().isoformat()
}
# Clean and prepare content
cleaned_content = story_content.strip()
# Call Deepseek API
api_response = self._call_deepseek_api(cleaned_content)
if "error" in api_response:
logger.error(f"Moderation failed: {api_response['error']}")
return {
"approved": False,
"response": "no",
"reason": "خطأ في خدمة المراجعة",
"error": api_response["error"],
"timestamp": datetime.now().isoformat()
}
try:
# Extract the moderation decision
ai_response = api_response.get("choices", [{}])[0].get("message", {}).get("content", "").strip().lower()
# Clean the response (remove any extra whitespace or characters)
ai_response = re.sub(r'[^\w]', '', ai_response)
# Determine if content is approved (be more strict)
approved = ai_response == "true"
response_value = "true" if approved else "no"
result = {
"approved": approved,
"response": response_value,
"ai_decision": ai_response,
"timestamp": datetime.now().isoformat(),
"content_length": len(cleaned_content)
}
if not approved:
result["reason"] = "المحتوى ينتهك القواعد المجتمعية أو الثقافية أو الدينية، أو أنه ليس قصة أدبية حقيقية بل محتوى إخباري"
else:
result["reason"] = "المحتوى مقبول ويلتزم بالمعايير المطلوبة"
logger.info(f"Moderation completed: {response_value} for content of length {len(cleaned_content)}")
return result
except Exception as e:
logger.error(f"Error processing API response: {str(e)}")
return {
"approved": False,
"response": "no",
"reason": "خطأ في معالجة نتيجة المراجعة",
"error": str(e),
"timestamp": datetime.now().isoformat()
}
# Flask application
app = Flask(__name__)
# Initialize the moderator (API key will be set via environment variable)
try:
moderator = ArabicContentModerator()
logger.info("Arabic Content Moderator initialized successfully")
except ValueError as e:
logger.error(f"Failed to initialize moderator: {e}")
moderator = None
@app.route('/', methods=['GET'])
def home():
"""Home endpoint with API documentation"""
return jsonify({
"service": "مراجع المحتوى الأدبي العربي المحسن مع كشف الأخبار",
"service_en": "Enhanced Arabic Literary Content Moderator with News Detection",
"version": "3.0.0",
"description": "AI-powered professional literary critic for Arabic short stories with enhanced news content detection",
"description_ar": "ناقد أدبي محترف مدعوم بالذكاء الاصطناعي للقصص العربية القصيرة مع كشف محسن للمحتوى الإخباري",
"endpoints": {
"/health": "Health check",
"/moderate": "POST - Moderate single story",
"/moderate/batch": "POST - Moderate multiple stories"
},
"features": [
"Enhanced news content detection and rejection",
"Sports reporting detection",
"Press conference content filtering",
"Meeting and event content filtering",
"Religious and cultural compliance checking",
"Professional literary criticism standards",
"Comprehensive profanity detection"
],
"rejected_content_types": [
"Sports reports and match analysis",
"Press conferences and official statements",
"Meeting minutes and proceedings",
"Political news and announcements",
"Economic reports and market updates",
"Technical reviews and product launches",
"Local news and municipal updates"
],
"usage": {
"moderate": {
"method": "POST",
"payload": {"story_content": "Arabic story text"},
"response": {"approved": "boolean", "response": "true/no"}
}
},
"status": "healthy" if moderator else "service unavailable"
})
@app.route('/health', methods=['GET'])
def health_check():
"""Health check endpoint"""
return jsonify({
"status": "healthy" if moderator else "unhealthy",
"service": "Enhanced Arabic Content Moderator with News Detection",
"timestamp": datetime.now().isoformat(),
"api_available": moderator is not None
})
@app.route('/moderate', methods=['POST'])
def moderate_content():
"""
Enhanced moderation endpoint with news detection
Expected JSON payload:
{
"story_content": "Arabic story text here"
}
Returns:
{
"approved": true/false,
"response": "true"/"no",
"reason": "reason in Arabic",
"timestamp": "ISO timestamp"
}
"""
if not moderator:
return jsonify({
"error": "خدمة المراجعة غير متوفرة - لم يتم تكوين مفتاح API",
"error_en": "Moderation service not available - API key not configured",
"approved": False,
"response": "no"
}), 500
try:
data = request.get_json()
if not data or 'story_content' not in data:
return jsonify({
"error": "محتوى القصة مفقود في الطلب",
"error_en": "Missing story_content in request",
"approved": False,
"response": "no"
}), 400
story_content = data['story_content']
result = moderator.moderate_story(story_content)
return jsonify(result)
except Exception as e:
logger.error(f"Error in moderate_content: {str(e)}")
return jsonify({
"error": "خطأ داخلي في الخادم",
"error_en": "Internal server error",
"approved": False,
"response": "no",
"details": str(e)
}), 500
@app.route('/moderate/batch', methods=['POST'])
def moderate_batch():
"""
Enhanced batch moderation endpoint
Expected JSON payload:
{
"stories": ["story1", "story2", "story3"]
}
"""
if not moderator:
return jsonify({
"error": "خدمة المراجعة غير متوفرة - لم يتم تكوين مفتاح API",
"error_en": "Moderation service not available - API key not configured"
}), 500
try:
data = request.get_json()
if not data or 'stories' not in data:
return jsonify({
"error": "مصفوفة القصص مفقودة في الطلب",
"error_en": "Missing stories array in request"
}), 400
stories = data['stories']
if not isinstance(stories, list):
return jsonify({
"error": "القصص يجب أن تكون في شكل مصفوفة",
"error_en": "Stories must be an array"
}), 400
results = []
approved_count = 0
for i, story in enumerate(stories):
logger.info(f"Moderating story {i+1}/{len(stories)}")
result = moderator.moderate_story(story)
results.append({
"story_index": i,
"result": result
})
if result.get("approved", False):
approved_count += 1
return jsonify({
"results": results,
"summary": {
"total_processed": len(results),
"approved_count": approved_count,
"rejected_count": len(results) - approved_count,
"approval_rate": f"{(approved_count/len(results)*100):.1f}%" if results else "0%"
},
"timestamp": datetime.now().isoformat()
})
except Exception as e:
logger.error(f"Error in moderate_batch: {str(e)}")
return jsonify({
"error": "خطأ داخلي في الخادم",
"error_en": "Internal server error",
"details": str(e)
}), 500
if __name__ == '__main__':
# For local testing
port = int(os.environ.get('PORT', 7860))
app.run(host='0.0.0.0', port=port, debug=False)