Spaces:
Sleeping
Sleeping
| """ | |
| ناشناسساز پیشرفته متون مالی/خبری فارسی | |
| نسخه: 1.3.0 - با پشتیبانی از Qwen3 | |
| """ | |
| import requests | |
| import json | |
| import gradio as gr | |
| from typing import Dict, Any, Optional | |
| import os | |
| from dataclasses import dataclass | |
| import re | |
| class OpenRouterConfig: | |
| """تنظیمات OpenRouter API""" | |
| api_key: str | |
| base_url: str = "https://openrouter.ai/api/v1" | |
| max_tokens: int = 8192 | |
| temperature: float = 0.6 | |
| top_p: float = 0.85 | |
| class AdvancedOpenRouterAnonymizer: | |
| """سیستم پیشرفته ناشناسسازی""" | |
| # لیست مدلهای رایگان با اولویت (بروز شده) | |
| AVAILABLE_MODELS = [ | |
| # Qwen3 - بهترین برای فارسی | |
| "qwen/qwen3-30b-a3b:free", | |
| ] | |
| def __init__(self, api_key: str = None, preferred_model: str = None): | |
| if api_key is None: | |
| api_key = os.getenv("OPENROUTER_API_KEY") | |
| if not api_key: | |
| raise ValueError("کلید API یافت نشد") | |
| self.config = OpenRouterConfig(api_key=api_key) | |
| self.preferred_model = preferred_model | |
| self.working_model = None | |
| self.system_prompt = self._create_system_prompt() | |
| def _create_system_prompt(self) -> str: | |
| """دستورالعمل سیستمی بهینه شده""" | |
| return """شما یک سیستم ناشناسسازی متون مالی/خبری فارسی هستید. | |
| ## وظیفه: | |
| موجودیتهای حساس را با placeholder های اندیسدار جایگزین کنید. | |
| ## **قوانین اندیسگذاری - CRITICAL** | |
| ### **1. ترتیب شمارهگذاری الزامی:** | |
| - شرکتها: company-01, company-02, company-03, company-04, ... (پیوسته و بدون گپ) | |
| - اشخاص: person-01, person-02, person-03, ... (پیوسته و بدون گپ) | |
| - اعداد: amount-01, amount-02, amount-03, ... (پیوسته و بدون گپ) | |
| - درصدها: percent-01, percent-02, percent-03, ... (پیوسته و بدون گپ) | |
| ### **2. ثبات شناسهها در متن:** | |
| - اگر "همراه اول" اولبار company-01 شد، در تمام متن همان باشد | |
| - اگر "مهدی احمدی" اولبار person-01 شد، در تمام متن همان باشد | |
| ### **3. تشخیص صحیح انواع:** | |
| **شرکت/سازمان:** همراه اول، بانک ملی، ایرانخودرو، سایپا، بانک مرکزی، سامانه کدال، وزارت نفت، سازمان تنظیم مقررات رادیویی، سازمان تامین اجتماعی | |
| **⚠️ CRITICAL - گروهها:** "گروه همراه اول"، "گروه اقتصادی آزادگان"، "گروه مالی صبا" → همه company-XX هستند (نه group-XX) | |
| **⚠️ CRITICAL - کلمات عمومی:** "سه شرکت دارویی"، "چند بانک"، "یک شرکت" → کلمات عمومی هستند، موجودیت نیستند (حفظ شوند) | |
| **⚠️ CRITICAL - نامهای مستعار:** "فاما" همان "فولاد مبارکه اصفهان" است → هر دو company-01 | |
| **شخص:** مهدی اخوان بهابادی، محمدرضا فرزین، ابوالفضل نجارزاده | |
| **عدد:** 37، 70، 677، 73.7، 178 (هر عددی) | |
| **درصد:** 37 درصدی، 15 درصدی، 53 درصد، 43% | |
| ## **⚠️ CRITICAL - قوانین پیشرفته ناشناسسازی:** | |
| ### **1. حفظ هویت شرکت در کل متن (بسیار مهم):** | |
| - اگر "شرکت پتروشیمی بوعلی سینا" را company-01 کردی، در ادامه متن "این شرکت"، "بوعلی"، "شرکت مذکور" همه باید همان company-01 باشند | |
| - **اشتباه رایج:** "company-01 ... این شرکت company-02" ❌ | |
| - **صحیح:** "company-01 ... این شرکت company-01" ✅ | |
| ### **2. بازرس/حسابرس = شرکت است (نه شخص):** | |
| - "شرکت وانیا نیک تدبیر را بهعنوان بازرس قانونی انتخاب کردند" → company-XX | |
| - "حسابرس" و "بازرس" اسم شرکتهای حسابرسی است → company-XX (نه person-XX) | |
| ### **3. واحدها را حفظ کن (CRITICAL):** | |
| - "amount-01 دستگاه محصول" ✅ (واحد حفظ شود) | |
| - "amount-01 محصول" ❌ (واحد حذف شده) | |
| - "amount-03 همت" ✅ | |
| - "amount-03" ❌ | |
| - "amount-05 گیگابیت بر ثانیه" ✅ | |
| ### **4. اعداد خاص را موجودیت نگیر:** | |
| - شماره ثبت: "شماره 11385" → حفظ شود ❌ amount-XX نشود | |
| - شماره تماس، کد ملی، شماره حساب → حفظ شوند | |
| - فقط مبالغ پولی، تعداد، وزن → amount-XX | |
| ### **5. درصدهای دقیق را حفظ کن:** | |
| - "99.99 درصد" → حفظ شود (نه percent-XX) | |
| - درصدهای با اعشار بسیار دقیق مثل 99.99، 0.01 → حفظ شوند | |
| - درصدهای معمولی: "40 درصد" → percent-01 | |
| ### **6. نهادها و مراجع عمومی:** | |
| - "سازمان بورس و اوراق بهادار" → company-XX ✅ | |
| - "مرجع ثبت شرکتها" → حفظ شود ❌ (مرجع عمومی است) | |
| - "هیئت تحقیق و تفحص مجلس" → حفظ شود ❌ (نهاد عمومی) | |
| - "سامانه کدال" → company-XX ✅ (سامانه خاص) | |
| ### **7. کلمات توصیفی عمومی را موجودیت نگیر:** | |
| - "سه خودروساز بزرگ کشور" → حفظ شود ❌ | |
| - "یک شرکت سرمایهگذاری دولتی" → حفظ شود ❌ | |
| - "19 بانکی که اطلاعات" → "19 بانکی" حفظ شود ❌ | |
| - فقط اگر نام خاص داشت: "شرکت سرمایهگذاری ملی" → company-XX ✅ | |
| ### **8. صنعت/بخش/حوزه را موجودیت نگیر:** | |
| - "صنعت پالایش" → حفظ شود ❌ (نه industry-XX) | |
| - "بخش خصوصی" → حفظ شود ❌ | |
| - "حوزه بازار سرمایه" → حفظ شود ❌ | |
| ### **9. دورههای زمانی با "حدود":** | |
| - "حدود 18 تا 24 ماه" → حفظ شود ❌ (نه amount-XX) | |
| - "حدود 2 سال" → حفظ شود ❌ | |
| ### **10. بازههای عددی - یک entity:** | |
| - "یک تا 1.5 میلیون تن" → amount-01 ✅ (یک entity) | |
| - "40 الی 60 درصد" → percent-01 الی percent-02 ❌ | |
| - بازه = یک entity واحد | |
| ### **11. مقایسه با خود شرکت:** | |
| - "company-01 ... بیشتر از company-01" → اشتباه! | |
| - اگر شرکت با دوره قبل خود مقایسه میشود → amount متفاوت ولی company یکسان | |
| ## **تشخیص دقیق درصدها:** | |
| - "37 درصدی" → percent-01 (نه amount) | |
| - "15 درصد" → percent-02 (نه amount) | |
| - "53%" → percent-03 (نه amount) | |
| - "بازه 10 تا 20 درصد" → percent-04 تا percent-05 | |
| - «رنجها» با «تا/الی/بین … و …» باید یک entity واحد باشند: | |
| مثال: «یک تا 1.5 میلیون تن» → یک amount-# ، «50 الی 70 درصد» → یک percent-# . | |
| ## **⚠️ CRITICAL - دورههای زمانی را حفظ کن:** | |
| - "۹ ماهه" → حفظ شود (نه amount-XX) | |
| - "۵ ماهه سال" → حفظ شود (نه amount-XX) | |
| - "۳ ماهه اول" → حفظ شود (نه amount-XX) | |
| - "۶ ماهه منتهی به" → حفظ شود (نه amount-XX) | |
| - "سهماهه نخست" → حفظ شود (نه amount-XX) | |
| - "در ۹ ماه" → "در ۹ ماه" حفظ شود | |
| - "عملکرد ۵ ماهه" → "عملکرد ۵ ماهه" حفظ شود | |
| - "حدود 18 تا 24 ماه" → حفظ شود (بازه زمانی) | |
| - "حدود 2 سال" → حفظ شود | |
| اما: | |
| - "۹ ماه سپرده" → "amount-XX ماه سپرده" (چون مدت سپرده است) | |
| - "۹ میلیون تومان" → amount-XX (چون مبلغ است) | |
| **قانون:** اگر عدد + "ماهه" یا "ماهه سال" یا "ماهه اول" باشد → حفظ کن | |
| **قانون:** اگر عدد + "ماه" بدون "ه" باشد و منظور تعداد ماه است → amount-XX | |
| - تاریخ/ماه/سال و ساعت را فعلاً «اصلاً» انتیتی نگیر (هیچ date-* / time-* تولید نکن). | |
| ## **موارد حفظ شده:** | |
| - تاریخها: 1404/04/23، 30 آذر 1403، پاییز 1401 | |
| - فصلهای سال: پاییز، بهار، تابستان، زمستان (حفظ شوند، موجودیت نیستند) | |
| - عناوین شغلی: مدیرعامل، رئیس کل، مدیرکل | |
| - واحدها: میلیارد تومان، همت، ریال، ماه، سال | |
| - مکانها: تهران، اصفهان، ایران | |
| - کلمات عمومی: "سه شرکت دارویی"، "چند بانک"، "یک شرکت"، "مراکز درمانی" (بدون نام خاص) | |
| - ⚠️ **CRITICAL - دورههای زمانی:** "۵ ماهه سال"، "۹ ماهه"، "۳ ماهه اول"، "۶ ماهه منتهی به" → حفظ شوند (نه amount-XX) | |
| ## **ممنوع:** | |
| - کلمات انگلیسی اضافی | |
| - تغییر ساختار جمله | |
| - حذف یا اضافه کردن کلمات | |
| - ⚠️ **CRITICAL: استفاده از group-XX ممنوع است** - همه گروهها باید company-XX باشند | |
| **فقط متن ناشناسشده را برگردان - هیچ توضیح اضافی نیاز نیست.**""" | |
| def _try_model(self, model: str, text: str) -> Optional[Dict[str, Any]]: | |
| """تست یک مدل""" | |
| headers = { | |
| "Authorization": f"Bearer {self.config.api_key}", | |
| "Content-Type": "application/json" | |
| } | |
| payload = { | |
| "model": model, | |
| "messages": [ | |
| {"role": "system", "content": self.system_prompt}, | |
| {"role": "user", "content": f"متن فارسی را ناشناس کن:\n\n{text}"} | |
| ], | |
| "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 | |
| ) | |
| if response.status_code == 200: | |
| return response.json() | |
| elif response.status_code == 404: | |
| print(f"⚠️ {model}: مدل موجود نیست (404)") | |
| return None | |
| else: | |
| print(f"⚠️ {model}: خطا {response.status_code}") | |
| return None | |
| except Exception as e: | |
| print(f"⚠️ {model}: {str(e)}") | |
| return None | |
| def _make_api_request(self, text: str) -> Dict[str, Any]: | |
| """ارسال درخواست با fallback هوشمند""" | |
| # اگر قبلاً مدل کاری پیدا کردیم | |
| if self.working_model: | |
| print(f"🔄 استفاده از مدل قبلی: {self.working_model}") | |
| result = self._try_model(self.working_model, text) | |
| if result: | |
| return result | |
| else: | |
| print("⚠️ مدل قبلی کار نکرد، جستجوی مدل جدید...") | |
| self.working_model = None | |
| # مدل ترجیحی | |
| if self.preferred_model: | |
| print(f"🎯 تست مدل ترجیحی: {self.preferred_model}") | |
| result = self._try_model(self.preferred_model, text) | |
| if result: | |
| self.working_model = self.preferred_model | |
| return result | |
| # امتحان تمام مدلها | |
| print("🔍 جستجوی مدل کاری...") | |
| for i, model in enumerate(self.AVAILABLE_MODELS, 1): | |
| print(f"[{i}/{len(self.AVAILABLE_MODELS)}] در حال تست {model}...") | |
| result = self._try_model(model, text) | |
| if result: | |
| print(f"✅ موفق با {model}") | |
| self.working_model = model | |
| return result | |
| # اگر هیچ مدلی کار نکرد | |
| raise Exception( | |
| "❌ هیچ مدلی در دسترس نیست!\n\n" | |
| "🔍 بررسی کنید:\n" | |
| "1. کلید API معتبر است؟\n" | |
| "2. اتصال اینترنت فعال است؟\n" | |
| "3. OpenRouter سرویس میدهد؟\n\n" | |
| "💡 راهکار:\n" | |
| "- به https://openrouter.ai/keys بروید\n" | |
| "- کلید جدید بگیرید\n" | |
| "- مجدداً تلاش کنید" | |
| ) | |
| def anonymize_text(self, text: str) -> Dict[str, Any]: | |
| """ناشناسسازی متن""" | |
| 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": "پاسخ نامعتبر"} | |
| content = response["choices"][0]["message"]["content"] | |
| content = self._clean_output(content) | |
| if not content: | |
| return {"success": False, "error": "خروجی خالی است"} | |
| analysis = self._analyze_anonymized_text(content) | |
| return { | |
| "success": True, | |
| "anonymized_text": content, | |
| "entities": analysis["entities"], | |
| "statistics": analysis["statistics"], | |
| "usage": response.get("usage", {}), | |
| "model_used": self.working_model or "unknown" | |
| } | |
| except Exception as e: | |
| return {"success": False, "error": str(e)} | |
| def _clean_output(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) | |
| # حذف خطوط انگلیسی/توضیحات | |
| lines = content.split('\n') | |
| persian_lines = [] | |
| for line in lines: | |
| line = line.strip() | |
| if not line: | |
| continue | |
| # نگه داشتن خطوطی که فارسی یا entity دارند | |
| has_persian = any(c in 'ابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهیآأإئؤة' for c in line) | |
| has_entity = re.search(r'(company|person|amount|percent)-\d+', line) | |
| if has_persian or has_entity: | |
| persian_lines.append(line) | |
| return '\n'.join(persian_lines).strip() | |
| def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]: | |
| """تحلیل متن ناشناسسازی شده""" | |
| 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) | |
| return { | |
| "statistics": { | |
| "company": len(set(companies)), | |
| "person": len(set(persons)), | |
| "amount": len(set(amounts)), | |
| "percent": len(set(percents)), | |
| "total": len(companies) + len(persons) + len(amounts) + len(percents) | |
| }, | |
| "entities": { | |
| "companies": sorted(list(set(companies)), key=lambda x: int(x)) if companies else [], | |
| "persons": sorted(list(set(persons)), key=lambda x: int(x)) if persons else [], | |
| "amounts": sorted(list(set(amounts)), key=lambda x: int(x)) if amounts else [], | |
| "percents": sorted(list(set(percents)), key=lambda x: int(x)) if percents else [] | |
| } | |
| } | |
| def create_interface(): | |
| """رابط کاربری Gradio""" | |
| api_key_available = bool(os.getenv("OPENROUTER_API_KEY")) | |
| with gr.Blocks( | |
| title="ناشناسساز فارسی", | |
| theme=gr.themes.Soft(), | |
| css=".rtl { direction: rtl; text-align: right; }" | |
| ) as interface: | |
| gr.Markdown(""" | |
| # 🔒 ناشناسساز متون مالی/خبری فارسی | |
| ### 🚀 پشتیبانی از Qwen3 + Llama 3.2 (OpenRouter) | |
| """) | |
| if not api_key_available: | |
| gr.Markdown(""" | |
| ## ⚠️ نیاز به تنظیمات | |
| **مراحل:** | |
| 1. به [OpenRouter](https://openrouter.ai/keys) بروید | |
| 2. یک کلید API رایگان بگیرید | |
| 3. در **Settings → Secrets**: | |
| - Name: `OPENROUTER_API_KEY` | |
| - Value: کلید شما | |
| 4. **Restart** Space | |
| """) | |
| else: | |
| gr.Markdown("✅ **API Key تنظیم شده - سیستم آماده است**") | |
| with gr.Row(): | |
| with gr.Column(): | |
| input_text = gr.Textbox( | |
| label="📝 متن ورودی", | |
| placeholder="متن خبری یا مالی فارسی خود را اینجا بنویسید...", | |
| lines=12, | |
| elem_classes="rtl" | |
| ) | |
| with gr.Row(): | |
| clear_btn = gr.Button("🗑️ پاک کردن", size="sm") | |
| anonymize_btn = gr.Button( | |
| "🔒 ناشناسسازی", | |
| variant="primary", | |
| size="lg" | |
| ) | |
| with gr.Column(): | |
| output_text = gr.Textbox( | |
| label="🎯 متن ناشناسسازی شده", | |
| lines=12, | |
| elem_classes="rtl", | |
| interactive=True | |
| ) | |
| info_output = gr.Markdown(label="📊 اطلاعات") | |
| def process_text(text: str): | |
| """پردازش متن""" | |
| if not text or not text.strip(): | |
| return "", "⚠️ **لطفاً متنی وارد کنید**" | |
| try: | |
| anonymizer = AdvancedOpenRouterAnonymizer() | |
| result = anonymizer.anonymize_text(text) | |
| if not result["success"]: | |
| error_msg = result['error'] | |
| return "", f"❌ **خطا:**\n\n{error_msg}" | |
| stats = result.get("statistics", {}) | |
| usage = result.get("usage", {}) | |
| model = result.get("model_used", "unknown") | |
| entities = result.get("entities", {}) | |
| # ساخت لیست موجودیتها | |
| entity_list = [] | |
| if entities.get("companies"): | |
| entity_list.append(f"🏢 **شرکتها:** {', '.join([f'company-{i}' for i in entities['companies']])}") | |
| if entities.get("persons"): | |
| entity_list.append(f"👤 **اشخاص:** {', '.join([f'person-{i}' for i in entities['persons']])}") | |
| if entities.get("amounts"): | |
| entity_list.append(f"💰 **مبالغ:** {', '.join([f'amount-{i}' for i in entities['amounts']])}") | |
| if entities.get("percents"): | |
| entity_list.append(f"📊 **درصدها:** {', '.join([f'percent-{i}' for i in entities['percents']])}") | |
| entities_display = "\n".join(entity_list) if entity_list else "*هیچ موجودیتی شناسایی نشد*" | |
| info_md = f""" | |
| ## ✅ ناشناسسازی موفق! | |
| ### 📊 آمار کلی: | |
| - 🏢 **شرکتها:** {stats.get('company', 0)} | |
| - 👤 **اشخاص:** {stats.get('person', 0)} | |
| - 💰 **مبالغ:** {stats.get('amount', 0)} | |
| - 📈 **درصدها:** {stats.get('percent', 0)} | |
| - 🔢 **کل جایگزینیها:** {stats.get('total', 0)} | |
| ### 🔍 موجودیتهای شناسایی شده: | |
| {entities_display} | |
| ### ⚙️ اطلاعات فنی: | |
| - 🤖 **مدل:** `{model}` | |
| - 📊 **Tokens استفاده شده:** {usage.get('total_tokens', 0)} | |
| - ورودی: {usage.get('prompt_tokens', 0)} | |
| - خروجی: {usage.get('completion_tokens', 0)} | |
| - 💰 **هزینه:** رایگان 🆓 | |
| """ | |
| return result["anonymized_text"], info_md | |
| except Exception as e: | |
| return "", f"❌ **خطای غیرمنتظره:**\n\n```\n{str(e)}\n```" | |
| def clear_all(): | |
| return "", "", "" | |
| # Event handlers | |
| anonymize_btn.click( | |
| fn=process_text, | |
| inputs=[input_text], | |
| outputs=[output_text, info_output] | |
| ) | |
| clear_btn.click( | |
| fn=clear_all, | |
| outputs=[input_text, output_text, info_output] | |
| ) | |
| # مثالها | |
| with gr.Accordion("📚 مثالهای آماده (کلیک کنید)", open=False): | |
| gr.Examples( | |
| examples=[ | |
| ["همراه اول با سهمی ۵۳ درصدی بیشترین نقش را در بازار دارد."], | |
| ["ایران خودرو در اسفند 1402 حدود 23 هزار و 296 میلیارد تومان درآمد کسب کرد که 4.58 درصد افزایش نسبت به سال قبل داشت."], | |
| ["علی محمدی مدیرعامل بانک ملت اعلام کرد که این بانک سود 15 درصدی داشته و 500 میلیون تومان سرمایهگذاری جدید انجام داده است."], | |
| ["فولاد مبارکه اصفهان با نماد فاما در بورس، رشد 8.5 درصدی قیمت سهام را تجربه کرد و به ارزش 12 هزار میلیارد تومان رسید. مهدی رضایی تحلیلگر بازار سرمایه پیشبینی میکند که این روند ادامه یابد."] | |
| ], | |
| inputs=input_text, | |
| label="مثالها" | |
| ) | |
| # راهنما | |
| with gr.Accordion("📖 راهنمای کامل", open=False): | |
| gr.Markdown(""" | |
| ## 🎯 نحوه استفاده | |
| 1. **وارد کردن متن:** متن خبری یا مالی فارسی را در کادر ورودی بنویسید | |
| 2. **کلیک روی دکمه ناشناسسازی:** سیستم خودکار شروع به کار میکند | |
| 3. **دریافت نتیجه:** متن ناشناسسازی شده + آمار کامل | |
| --- | |
| ## 🔍 انواع موجودیتها | |
| | نوع | توضیح | مثال | | |
| |-----|-------|------| | |
| | **company-XX** | شرکتها، بانکها، سازمانها | همراه اول → company-01 | | |
| | **person-XX** | نام افراد | علی محمدی → person-01 | | |
| | **amount-XX** | مبالغ مالی (با واحد) | 500 میلیون → amount-01 | | |
| | **percent-XX** | درصدها (با %) | 15 درصد → percent-01 | | |
| --- | |
| ## ✅ موارد حفظ شده | |
| - ✅ تاریخها (اسفند 1402، 2023/12/01) | |
| - ✅ زمانها (10:30، 14 مارس) | |
| - ✅ واحدهای پولی (تومان، ریال، میلیارد) | |
| - ✅ کلمات عمومی (سه شرکت، دو نفر) | |
| - ✅ اعداد غیرحساس | |
| --- | |
| ## 🤖 مدلهای پشتیبانی شده | |
| سیستم به ترتیب اولویت این مدلها را امتحان میکند: | |
| 1. **Qwen3-30B** (بهترین برای فارسی) | |
| 2. **Qwen3-235B** (قدرتمندترین) | |
| 3. **Qwen3-Next-80B** (جدیدترین) | |
| 4. **Llama 3.2** (تست شده ✓) | |
| 5. سایر مدلهای رایگان | |
| --- | |
| ## 💡 نکات مهم | |
| - 🆓 **کاملاً رایگان** - بدون هزینه | |
| - 🔒 **امن** - بدون ذخیره داده | |
| - ⚡ **سریع** - پردازش آنی | |
| - 🎯 **دقیق** - شناسایی هوشمند | |
| - 🔄 **پایدار** - fallback خودکار | |
| --- | |
| ## ❓ سوالات متداول | |
| **Q: اگر خطا داد چه کنم؟** | |
| A: سیستم خودکار مدلهای دیگر را امتحان میکند. اگر باز هم خطا داد، کلید API را بررسی کنید. | |
| **Q: چه مدلی بهتر است؟** | |
| A: Qwen3-30B یا Qwen3-235B برای فارسی بهترین هستند. | |
| **Q: دادههای من ذخیره میشود؟** | |
| A: خیر، OpenRouter به طور پیشفرض دادهها را ذخیره نمیکند. | |
| """) | |
| return interface | |
| if __name__ == "__main__": | |
| interface = create_interface() | |
| interface.launch() |