import requests import json import gradio as gr from typing import Dict, Any import os from dataclasses import dataclass import re @dataclass class CerebrasConfig: """تنظیمات Cerebras API برای Qwen 3-32B""" api_key: str base_url: str = "https://api.cerebras.ai/v1" model: str = "qwen/qwen-3-14b:free" max_tokens: int = 16384 temperature: float = 0.3 # ⬇️ کمتر = دقیق‌تر top_p: float = 0.8 # ⬇️ کمتر = محافظه‌کارانه‌تر presence_penalty: float = 0.2 # ⬆️ بیشتر = کمتر تکرار frequency_penalty: float = 0.2 # ⬆️ بیشتر = تنوع بیشتر #model: str = "qwen-3-32b" #max_tokens: int = 3000 # افزایش برای thinking tokens #temperature: float = 0.3 # کاهش برای دقت بیشتر #top_p: float = 0.9 class AdvancedCerebrasAnonymizer: """سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی با Qwen 3-32B""" def __init__(self, api_key: str = None): if api_key is None: api_key = os.getenv("CEREBRAS_API_KEY") if not api_key: raise ValueError("کلید API یافت نشد") self.config = CerebrasConfig(api_key=api_key) self.system_prompt = self._create_advanced_system_prompt() def _create_advanced_system_prompt(self) -> str: """ایجاد دستورالعمل سیستمی بهینه شده""" return """شما یک سیستم ناشناس‌سازی متون مالی فارسی هستید. ⚠️ CRITICAL: در پاسخ نهایی خود، فقط و فقط متن ناشناس‌سازی شده را برگردانید، بدون هیچ توضیح، تحلیل، یا تگ اضافی. ## قوانین اندیس‌گذاری: 1. **ترتیب پیوسته**: company-01, company-02, ... | person-01, person-02, ... | amount-01, amount-02, ... | percent-01, percent-02, ... 2. **ثبات**: اگر "همراه اول" → company-01 شد، در تمام متن همان باشد 3. **نام مستعار**: "فاما" = "فولاد مبارکه" → هر دو company-01 4. **اشاره ضمنی**: "این شرکت" اگر به company-01 اشاره دارد → company-01 (نه company-02) ## انواع موجودیت: - **company-XX**: شرکت‌ها، بانک‌ها، سازمان‌ها، گروه‌ها - **person-XX**: نام و نام خانوادگی اشخاص - **amount-XX**: مبالغ - واحد را حفظ کن - **percent-XX**: درصدها ## قوانین کلیدی: 1. بازرس = شرکت است → company-XX 2. واحدها: "amount-01 میلیارد تومان" ✅ 3. گروه‌ها: "گروه X" → company-XX 4. کلمات عمومی حفظ: "سه شرکت" → حفظ 5. دوره زمانی حفظ: "۵ ماهه" → حفظ 6. بازه = یک entity: "یک تا 1.5 میلیون" → amount-01 7.شناسایی و دسته‌بندی درصدها بین 50 تا 70 درصد به عنوان یک موجودیت درصد در متن 8.شناسایی تمام ارقام موجود در متن به عنوان موجودیت amount-XX مانند "سود خالص 50 میلیارد تومان" را به مقدار amount-01 تبدیل کن. 9.شناسایی مقدار درصد در بازه 40–60٪ به عنوان یک موجودیت درصد (percent-03مثلا). 10.بزرگ‌ترین هلدینگ شستا در حوزه بازار سرمایه عمل می‌کند" را به صورت "بزرگ‌ترین هلدینگ company-03 در حوزه بازار سرمایه عمل می‌کند" 11.هر جا که یک شرکت و گروه با نام یکسان ذکر شود، آن را به یک موجودیت یکسان (company-XX) تبدیل کن. 12.سود حاصل از منابع عملیاتی ۱۰،۸۸۷،۸۶۴ میلیون ریال" را به عنوان amount-01 شناسایی کن. 13."بانک ملی ایران" را به عنوان company-01 شناسایی کن. 14.شرکت ارتباطات سیار ایران همراه اول" را به عنوان company-01 شناسایی کن. 15."سپرده‌گذاری مرکزی اوراق بهادار و تسویه وجوه" را به عنوان company-01 شناسایی کن. ## مثال: ورودی: ایران خودرو در اسفند 1402 حدود 23 هزار و 296 میلیارد درآمد کسب کرد که 4.58 درصد افزایش داشت. خروجی: company-01 در اسفند 1402 حدود amount-01 درآمد کسب کرد که percent-01 افزایش داشت. "این اپراتور در منطقه توانسته به پوششی 100 میلیونی دست پیدا کند" را به صورت "این اپراتور در منطقه توانسته به پوششی amount-04 دست پیدا کند". ⚠️ یادآوری: فقط متن ناشناس‌شده، بدون هیچ توضیح اضافی.""" def _make_api_request(self, text: str) -> Dict[str, Any]: """ارسال درخواست به Cerebras API""" headers = { "Authorization": f"Bearer {self.config.api_key}", "Content-Type": "application/json" } # اضافه کردن /no_think به متن برای غیرفعال کردن reasoning user_content = f"{text}\n\n/no_think" payload = { "messages": [ { "role": "system", "content": self.system_prompt }, { "role": "user", "content": user_content } ], "model": self.config.model, "temperature": self.config.temperature, "top_p": self.config.top_p, "max_tokens": self.config.max_tokens } try: response = requests.post( f"{self.config.base_url}/chat/completions", headers=headers, json=payload, timeout=60 ) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: raise Exception(f"خطا در ارتباط با Cerebras API: {str(e)}") def anonymize_text(self, text: str) -> Dict[str, Any]: """ناشناس‌سازی متن با استفاده از Qwen 3-32B""" if not text.strip(): return { "success": False, "error": "متن ورودی خالی است" } try: response = self._make_api_request(text) if "choices" not in response or not response["choices"]: return { "success": False, "error": "پاسخ نامعتبر از API" } content = response["choices"][0]["message"]["content"] # پاک کردن thinking tags اگر وجود دارد content = self._remove_thinking_tags(content) # پاک کردن markdown اگر وجود دارد content = self._clean_markdown(content) # حذف خطوط اضافی و فضاهای خالی content = content.strip() # حذف توضیحات اضافی در ابتدا یا انتها content = self._clean_explanations(content) # تحلیل نتایج analysis = self._analyze_anonymized_text(content) return { "success": True, "anonymized_text": content, "entities": analysis["entities"], "statistics": analysis["statistics"], "detailed_analysis": analysis["detailed_analysis"], "usage": response.get("usage", {}), "quality_check": self._validate_anonymized_text(content) } except Exception as e: return { "success": False, "error": f"خطا در پردازش: {str(e)}" } def _remove_thinking_tags(self, content: str) -> str: """حذف تگ‌های thinking از خروجی""" # حذف محتوای داخل ... content = re.sub(r'.*?', '', content, flags=re.DOTALL) # حذف تگ‌های خالی content = re.sub(r'', '', content) return content.strip() def _clean_markdown(self, content: str) -> str: """پاک کردن markdown از پاسخ""" if "```" in content: lines = content.split('\n') clean_lines = [] skip = False for line in lines: if line.strip().startswith('```'): skip = not skip continue if not skip: clean_lines.append(line) content = '\n'.join(clean_lines) return content def _clean_explanations(self, content: str) -> str: """حذف توضیحات اضافی در ابتدا یا انتها""" lines = content.split('\n') clean_lines = [] for line in lines: # حذف خطوطی که شامل توضیحات متا هستند if any(word in line.lower() for word in ['okay', 'let me', 'here is', 'خروجی', 'نتیجه', 'پاسخ']): continue clean_lines.append(line) return '\n'.join(clean_lines).strip() def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]: """تحلیل متن ناشناس‌سازی شده""" import re # شمارش موجودیت‌ها companies = re.findall(r'company-(\d+)', text) persons = re.findall(r'person-(\d+)', text) amounts = re.findall(r'amount-(\d+)', text) percents = re.findall(r'percent-(\d+)', text) # آمار کلی statistics = { "company": len(set(companies)), "person": len(set(persons)), "amount": len(set(amounts)), "percent": len(set(percents)), "total_replacements": len(companies) + len(persons) + len(amounts) + len(percents) } # جزئیات موجودیت‌ها entities = { "companies": sorted(list(set(companies)), key=lambda x: int(x)), "persons": sorted(list(set(persons)), key=lambda x: int(x)), "amounts": sorted(list(set(amounts)), key=lambda x: int(x)), "percents": sorted(list(set(percents)), key=lambda x: int(x)) } # تحلیل دقیق‌تر detailed_analysis = { "preserved_dates": len(re.findall(r'\d{4}/\d{1,2}/\d{1,2}|\d{1,2}\s+\w+\s+\d{4}', text)), "preserved_times": len(re.findall(r'\d{1,2}:\d{2}', text)), "financial_indicators": len(re.findall(r'\b(EPS|P/E|ARPU|NPL|ROE|ROA)\b', text)), "units_preserved": len(re.findall(r'(میلیارد|میلیون|هزار|تومان|ریال|درهم|دلار|یورو|تن|کیلوگرم)', text)) } return { "statistics": statistics, "entities": entities, "detailed_analysis": detailed_analysis } def _validate_anonymized_text(self, text: str) -> Dict[str, Any]: """اعتبارسنجی پیشرفته متن ناشناس‌شده""" import re # استخراج همه موجودیت‌ها companies = re.findall(r'company-(\d+)', text) persons = re.findall(r'person-(\d+)', text) amounts = re.findall(r'amount-(\d+)', text) percents = re.findall(r'percent-(\d+)', text) validation_issues = [] # بررسی هر نوع موجودیت for entity_type, indices in [ ("company", companies), ("person", persons), ("amount", amounts), ("percent", percents) ]: if indices: unique_indices = sorted(list(set([int(x) for x in indices]))) # بررسی شروع از 1 if unique_indices[0] != 1: validation_issues.append(f"اندیس {entity_type} از 01 شروع نشده (شروع: {unique_indices[0]:02d})") # بررسی پیوستگی expected = list(range(1, len(unique_indices) + 1)) if unique_indices != expected: validation_issues.append(f"اندیس‌های {entity_type} پیوسته نیستند: {[f'{x:02d}' for x in unique_indices]}") # بررسی کلمات انگلیسی غیرضروری english_words = re.findall(r'\b[a-zA-Z]+\b', text) unwanted_english = [word for word in english_words if word.lower() not in ['eps', 'p/e', 'arpu', 'npl', 'roe', 'roa']] if unwanted_english: validation_issues.append(f"کلمات انگلیسی غیرضروری: {unwanted_english}") return { "is_valid": len(validation_issues) == 0, "issues": validation_issues, "entity_counts": { "company": len(set(companies)), "person": len(set(persons)), "amount": len(set(amounts)), "percent": len(set(percents)) } } # باقی کد رابط کاربری همان قبل است... # (تابع create_advanced_interface و بقیه کد تغییری ندارد) def create_advanced_interface(): """ایجاد رابط کاربری پیشرفته""" api_key_available = bool(os.getenv("CEREBRAS_API_KEY")) custom_css = """ .gradio-container { font-family: 'Tahoma', 'Arial', sans-serif !important; direction: rtl; max-width: 1400px; margin: 0 auto; } .result-box { background-color: #f8f9fa; border: 2px solid #e9ecef; border-radius: 12px; padding: 20px; margin: 10px 0; } .warning-box { background-color: #fff3cd; border: 2px solid #ffeaa7; border-radius: 12px; padding: 15px; color: #856404; margin: 10px 0; } .success-box { background-color: #d4edda; border: 2px solid #c3e6cb; border-radius: 12px; padding: 15px; color: #155724; margin: 10px 0; } .qwen-box { background-color: #e7f3ff; border: 2px solid #2196F3; border-radius: 12px; padding: 15px; color: #0d47a1; margin: 10px 0; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 15px; margin: 15px 0; } .stat-card { background-color: #ffffff; border: 1px solid #dee2e6; border-radius: 8px; padding: 15px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .quality-badge { display: inline-block; padding: 5px 10px; border-radius: 20px; font-weight: bold; margin: 5px; } .quality-pass { background-color: #28a745; color: white; } .quality-fail { background-color: #dc3545; color: white; } """ with gr.Blocks(css=custom_css, title="ناشناس‌ساز پیشرفته با Qwen 3-32B", theme=gr.themes.Soft()) as interface: gr.Markdown(""" # 🔒 سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی ### ⚡ قدرت‌گرفته از Cerebras AI + Alibaba Qwen 3-32B """) gr.Markdown("""
🚀 مدل: Alibaba Qwen 3-32B (اصلاح شده)
⚡ سرعت: 2,400 توکن/ثانیه | 🧠 32B پارامتر | 💰 $0.40/$0.80
بهینه‌سازی شده: Thinking Mode غیرفعال برای خروجی مستقیم
""") if api_key_available: gr.Markdown("""
سیستم آماده است - کلید API تنظیم شده
""") api_key_input = gr.Textbox(visible=False, value="") else: gr.Markdown("""
⚠️ کلید API تنظیم نشده
لطفاً کلید Cerebras API خود را وارد کنید
""") api_key_input = gr.Textbox( label="🔑 کلید Cerebras API", placeholder="csk-...", type="password" ) with gr.Row(): with gr.Column(scale=1): input_text = gr.Textbox( label="📝 متن ورودی", placeholder="متن مالی یا خبری خود را اینجا وارد کنید...", lines=12, max_lines=25 ) with gr.Row(): anonymize_btn = gr.Button( "🔒 ناشناس‌سازی", variant="primary", size="lg" ) clear_btn = gr.Button( "🗑️ پاک کردن", variant="secondary" ) with gr.Column(scale=1): output_text = gr.Textbox( label="🎯 متن ناشناس‌سازی شده", lines=12, max_lines=25, elem_classes=["result-box"] ) copy_btn = gr.Button( "📋 کپی متن", variant="secondary", size="sm" ) copy_output = gr.Textbox( label="📋 متن برای کپی", lines=3, visible=False, interactive=True ) with gr.Row(): with gr.Column(): statistics_output = gr.Markdown(label="📊 آمار کلی") with gr.Column(): quality_output = gr.Markdown(label="✅ کنترل کیفیت") with gr.Row(): with gr.Column(): entities_output = gr.Markdown(label="🏷️ موجودیت‌ها") with gr.Column(): detailed_analysis_output = gr.Markdown(label="🔍 تحلیل دقیق") usage_output = gr.Markdown(label="⚡ اطلاعات پردازش") def process_advanced_text(text: str, api_key_manual: str = ""): """پردازش متن""" if api_key_manual is None: api_key_manual = "" final_api_key = "" if api_key_manual and api_key_manual.strip(): final_api_key = api_key_manual.strip() elif os.getenv("CEREBRAS_API_KEY"): final_api_key = os.getenv("CEREBRAS_API_KEY") if not final_api_key: return ("", "❌ کلید API وارد نشده است", "", "", "", "") if not text or not text.strip(): return ("", "❌ لطفاً متن ورودی را وارد کنید", "", "", "", "") try: anonymizer = AdvancedCerebrasAnonymizer(api_key=final_api_key) result = anonymizer.anonymize_text(text) if not result["success"]: return ("", f"❌ خطا: {result['error']}", "", "", "", "") stats = result.get("statistics", {}) stats_md = f"""📊 **آمار کلی:**

🏢 شرکت‌ها

{stats.get('company', 0)}

👤 اشخاص

{stats.get('person', 0)}

💰 مبالغ

{stats.get('amount', 0)}

📊 درصدها

{stats.get('percent', 0)}

🔢 کل

{stats.get('total_replacements', 0)}

""" quality = result.get("quality_check", {}) quality_md = "✅ **کنترل کیفیت:**\n\n" if quality.get("is_valid", False): quality_md += '✅ موفق\n\n' else: quality_md += '❌ مشکل\n\n' issues = quality.get("issues", []) if issues: quality_md += "**مشکلات:**\n" for issue in issues: quality_md += f"• {issue}\n" entities = result.get("entities", {}) entities_md = "🏷️ **موجودیت‌ها:**\n\n" if entities.get("companies"): entities_md += f"🏢 company-{', company-'.join(entities['companies'])}\n\n" if entities.get("persons"): entities_md += f"👤 person-{', person-'.join(entities['persons'])}\n\n" if entities.get("amounts"): entities_md += f"💰 amount-{', amount-'.join(entities['amounts'])}\n\n" if entities.get("percents"): entities_md += f"📊 percent-{', percent-'.join(entities['percents'])}\n\n" detailed = result.get("detailed_analysis", {}) detailed_md = f"""🔍 **تحلیل:** 📅 تاریخ: {detailed.get('preserved_dates', 0)} 📈 شاخص‌ها: {detailed.get('financial_indicators', 0)} 📏 واحدها: {detailed.get('units_preserved', 0)} """ usage = result.get("usage", {}) usage_md = f"""⚡ **Qwen 3-32B:** 🤖 مدل: {anonymizer.config.model} 🌡️ Temperature: {anonymizer.config.temperature} 📥 Input: {usage.get('prompt_tokens', '?')} 📤 Output: {usage.get('completion_tokens', '?')} 📊 Total: {usage.get('total_tokens', '?')} """ return ( result["anonymized_text"], stats_md, quality_md, entities_md, detailed_md, usage_md ) except Exception as e: return ("", f"❌ خطا: {str(e)}", "", "", "", "") def copy_text(text_to_copy): if not text_to_copy or not text_to_copy.strip(): return gr.Textbox(visible=False), "⚠️ متن خالی" return gr.Textbox(value=text_to_copy, visible=True), "✅ آماده کپی" def clear_all(): return "", "", "", "", "", "", "", gr.Textbox(visible=False) anonymize_btn.click( fn=process_advanced_text, inputs=[input_text, api_key_input], outputs=[output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output] ) copy_btn.click( fn=copy_text, inputs=[output_text], outputs=[copy_output, statistics_output] ) clear_btn.click( fn=clear_all, outputs=[input_text, output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output, copy_output] ) gr.Examples( examples=[ ["ایران خودرو در اسفندماه سال 1402 حدود 23 هزار و 296 میلیارد تومان درآمد کسب کرد که در مقایسه با بهمن 4.58 درصد افزایش داشت."], ["مجمع پتروشیمی بوعلی سینا برگزار شد. وانیا نیک تدبیر را بازرس قانونی انتخاب کردند."], ["شرکت فولاد مبارکه اصفهان با ملی نفت قرارداد امضا کرد. فاما سرمایه را از 8700 به 12500 میلیارد افزایش می‌دهد."] ], inputs=input_text, label="📚 مثال‌ها" ) with gr.Accordion("📖 راهنما", open=False): gr.Markdown(""" ## 🔧 اصلاحات انجام شده: ### ✅ مشکل حل شده: - غیرفعال کردن Thinking Mode با `/no_think` - حذف خودکار تگ‌های `` - پاک‌سازی توضیحات اضافی - کاهش Temperature به 0.3 برای دقت بیشتر ### 🎯 چگونه کار می‌کند: 1. پرامپت بهینه شده برای خروجی مستقیم 2. اضافه کردن `/no_think` به انتهای درخواست 3. پردازش و پاک‌سازی خودکار خروجی 4. حذف تمام توضیحات و تگ‌های اضافی """) return interface if __name__ == "__main__": interface = create_advanced_interface() interface.launch( show_error=True, ssr_mode=False # برای پایداری بیشتر )