Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import re | |
| import os | |
| import requests | |
| import logging | |
| from typing import Dict, List, Tuple | |
| import json | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| class AnonymizerCerebrasFixed: | |
| def __init__(self, api_key: str = None): | |
| self.api_key = api_key or os.getenv("CEREBRAS_API_KEY") | |
| self.mapping_table = {} | |
| self.counters = { | |
| 'company': 0, 'person': 0, 'amount': 0, 'phone': 0, | |
| 'email': 0, 'id_number': 0, 'date': 0, 'location': 0, | |
| 'percent': 0 | |
| } | |
| if not self.api_key: | |
| raise ValueError("❌ کلید API Cerebras یافت نشد!") | |
| logger.info("✅ Anonymizer Fixed مقداردهی شد") | |
| def get_system_prompt(self) -> str: | |
| """System Prompt برای ناشناسسازی مستقیم""" | |
| return """شما یک «ناشناسساز متون مالی فارسی پیشرفته» هستید. | |
| ## وظایف: | |
| 1. تمام اسامی خاص را ناشناس کنید | |
| 2. تمام مقادیر عددی را ناشناس کنید | |
| 3. Entity Linking دقیق اعمال کنید | |
| ## قوانین CRITICAL: | |
| ### برای اشخاص: | |
| - "سروش خسروی" و "خسروی" و "سروش" و "وی" = person-01 | |
| - نام خانوادگی تنهایی را رها نکنید | |
| - ضمیرهای اشاره را ناشناس کنید | |
| ### برای شرکتها: | |
| - "پتروشیمی غدیر" و "غدیر" و "این شرکت" = company-01 | |
| - نام کوتاه را ناشناس کنید | |
| ### برای مقادیر: | |
| - واحد یکسان = ID یکسان | |
| - "142 میلیارد" و "۱۴۲ میلیارد" = amount-01 | |
| ### برای درصدها: | |
| - "21 درصد" و "21%" و "۲۱ درصدی" = percent-01 | |
| ## ترتیب شمارهگذاری: | |
| - company-01, company-02, ... | |
| - person-01, person-02, ... | |
| - amount-01, amount-02, ... | |
| - percent-01, percent-02, ... | |
| ## خروجی: | |
| فقط متن ناشناسشده را برگردان - هیچ توضیح اضافی نیاز نیست.""" | |
| def get_user_prompt(self, text: str) -> str: | |
| """User Prompt برای متن ورودی""" | |
| return f"""متن مالی فارسی زیر را ناشناس کنید: | |
| {text} | |
| دستورات: | |
| 1. نام کامل + نام تنهایی + ضمیرها = یک موجودیت | |
| 2. واحد یکسان = موجودیت یکسان | |
| 3. Entity Linking دقیق اعمال کنید | |
| 4. جدول نگاشت بسازید | |
| خروجی: متن ناشناسشده""" | |
| def anonymize_with_api(self, text: str) -> Tuple[str, Dict]: | |
| """ناشناسسازی با استفاده از API""" | |
| logger.info("🚀 شروع ناشناسسازی...") | |
| system_prompt = self.get_system_prompt() | |
| user_prompt = self.get_user_prompt(text) | |
| try: | |
| logger.info("📡 ارسال به Cerebras API...") | |
| response = requests.post( | |
| "https://api.cerebras.ai/v1/chat/completions", | |
| headers={ | |
| "Authorization": f"Bearer {self.api_key}", | |
| "Content-Type": "application/json" | |
| }, | |
| json={ | |
| "model": "llama-3.3-70b", | |
| "messages": [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt} | |
| ], | |
| "max_tokens": 4096, | |
| "temperature": 0.1 | |
| }, | |
| timeout=60 | |
| ) | |
| if response.status_code != 200: | |
| error_msg = response.text | |
| logger.error(f"❌ خطای API: {error_msg}") | |
| return text, {"error": error_msg} | |
| result = response.json() | |
| anonymized_text = result['choices'][0]['message']['content'].strip() | |
| logger.info(f"✅ ناشناسسازی کامل شد") | |
| logger.info(f"متن ورودی: {len(text)} کاراکتر") | |
| logger.info(f"متن خروجی: {len(anonymized_text)} کاراکتر") | |
| return anonymized_text, {"status": "success"} | |
| except Exception as e: | |
| logger.error(f"❌ خطا: {str(e)}") | |
| return text, {"error": str(e)} | |
| def anonymize_with_regex(self, text: str) -> Tuple[str, Dict]: | |
| """ناشناسسازی ساده با Regex (بدون API)""" | |
| logger.info("🔍 شروع ناشناسسازی Regex...") | |
| anonymized = text | |
| mapping = {} | |
| entity_count = {'person': 0, 'company': 0, 'amount': 0, 'percent': 0} | |
| # الگوهای Regex | |
| patterns = { | |
| 'person': r'\b[ء-ي]+\s+[ء-ي]+(?:\s+[ء-ي]+)*\b', # نامهای فارسی | |
| 'amount': r'\d+\s*(?:میلیارد|میلیون|هزار|تومان|ریال|دلار|تن|دستگاه)', | |
| 'percent': r'\d+\s*(?:درصد|%|درصدی)', | |
| 'company': r'(?:شرکت|بانک|سازمان|گروه|هلدینگ)\s+[ء-ي]+(?:\s+[ء-ي]+)*', | |
| } | |
| # ناشناسسازی | |
| for entity_type, pattern in patterns.items(): | |
| matches = re.finditer(pattern, anonymized) | |
| for match in matches: | |
| text_match = match.group() | |
| key = text_match.lower() | |
| if key not in mapping: | |
| entity_count[entity_type] += 1 | |
| placeholder = f"{entity_type}-{entity_count[entity_type]:02d}" | |
| mapping[key] = placeholder | |
| anonymized = anonymized.replace(text_match, placeholder, 1) | |
| logger.info(f"✅ {sum(entity_count.values())} موجودیت ناشناس شد") | |
| return anonymized, mapping | |
| def get_mapping_table_str(self) -> str: | |
| """تبدیل جدول نگاشت به string""" | |
| if not self.mapping_table: | |
| return "### جدول نگاشت خالی است" | |
| table = "### 📋 جدول نگاشت\n\n" | |
| table += "| ID | نوع | متن اصلی |\n" | |
| table += "|----|----|----------|\n" | |
| for token, info in self.mapping_table.items(): | |
| entity_type = info.get('type', 'نامشخص') | |
| original = info.get('original', '') | |
| table += f"| {token} | {entity_type} | {original} |\n" | |
| return table | |
| # متغیر سراسری | |
| anonymizer = None | |
| def process(input_text: str, api_choice: str = "cerebras"): | |
| """پردازش متن""" | |
| global anonymizer | |
| if not input_text.strip(): | |
| return "", "", "" | |
| # مقداردهی | |
| api_key = os.getenv("CEREBRAS_API_KEY") | |
| if not api_key and api_choice == "cerebras": | |
| return "", "", "❌ API Key تنظیم نشده است" | |
| if not anonymizer: | |
| if api_key: | |
| anonymizer = AnonymizerCerebrasFixed(api_key) | |
| else: | |
| anonymizer = AnonymizerCerebrasFixed("dummy") # Regex mode | |
| try: | |
| if api_choice == "cerebras" and api_key: | |
| logger.info("استفاده از Cerebras API") | |
| anonymized_text, result = anonymizer.anonymize_with_api(input_text) | |
| else: | |
| logger.info("استفاده از Regex") | |
| anonymized_text, mapping = anonymizer.anonymize_with_regex(input_text) | |
| anonymizer.mapping_table = {v: {'original': k, 'type': 'unknown'} for k, v in mapping.items()} | |
| mapping_str = anonymizer.get_mapping_table_str() | |
| return input_text, anonymized_text, mapping_str | |
| except Exception as e: | |
| logger.error(f"❌ خطا: {str(e)}") | |
| return "", "", f"❌ خطا: {str(e)}" | |
| def clear(): | |
| """پاک کردن""" | |
| return "", "", "" | |
| # Gradio Interface | |
| css_rtl = """ | |
| #input_text textarea { direction: rtl; text-align: right; } | |
| #anonymized_text textarea { direction: rtl; text-align: right; } | |
| #mapping textarea { direction: rtl; text-align: right; } | |
| """ | |
| with gr.Blocks(title="سیستم ناشناسسازی متون", theme=gr.themes.Soft(), css=css_rtl) as app: | |
| gr.Markdown("# 🔐 سیستم ناشناسسازی متون مالی فارسی (اصلاح شده)") | |
| gr.Markdown("#### استخراج موجودیتهای حساس و ناشناسسازی آنها") | |
| with gr.Row(): | |
| with gr.Column(scale=2): | |
| input_text = gr.Textbox( | |
| lines=12, | |
| placeholder="متن مالی/خبری را وارد کنید...", | |
| label="📝 متن ورودی", | |
| elem_id="input_text" | |
| ) | |
| with gr.Column(scale=1): | |
| gr.HTML("<div style='text-align: center; margin-bottom: 10px;'></div>") | |
| api_choice = gr.Radio( | |
| ["cerebras", "regex"], | |
| value="regex", | |
| label="انتخاب روش" | |
| ) | |
| process_btn = gr.Button("🔄 پردازش", variant="primary", size="lg") | |
| clear_btn = gr.Button("🗑️ پاک کردن", variant="stop", size="lg") | |
| with gr.Row(): | |
| anonymized_text = gr.Textbox( | |
| lines=12, | |
| label="🔒 متن ناشناسشده", | |
| interactive=False, | |
| elem_id="anonymized_text" | |
| ) | |
| with gr.Row(): | |
| mapping = gr.Textbox( | |
| lines=8, | |
| label="📋 جدول نگاشت", | |
| interactive=False, | |
| elem_id="mapping" | |
| ) | |
| # Event handlers | |
| process_btn.click( | |
| fn=process, | |
| inputs=[input_text, api_choice], | |
| outputs=[input_text, anonymized_text, mapping] | |
| ) | |
| clear_btn.click( | |
| fn=clear, | |
| outputs=[input_text, anonymized_text, mapping] | |
| ) | |
| if __name__ == "__main__": | |
| print("🚀 سیستم ناشناسسازی متون در حال راهاندازی...") | |
| app.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True | |
| ) | |