Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -11,10 +11,10 @@ class CerebrasConfig:
|
|
| 11 |
"""تنظیمات Cerebras API برای Qwen 3-32B"""
|
| 12 |
api_key: str
|
| 13 |
base_url: str = "https://api.cerebras.ai/v1"
|
| 14 |
-
model: str = "qwen-3-32b"
|
| 15 |
-
max_tokens: int =
|
| 16 |
-
temperature: float = 0.
|
| 17 |
-
top_p: float = 0.
|
| 18 |
|
| 19 |
class AdvancedCerebrasAnonymizer:
|
| 20 |
"""سیستم پیشرفته ناشناسسازی متون مالی/خبری فارسی با Qwen 3-32B"""
|
|
@@ -29,8 +29,10 @@ class AdvancedCerebrasAnonymizer:
|
|
| 29 |
self.system_prompt = self._create_advanced_system_prompt()
|
| 30 |
|
| 31 |
def _create_advanced_system_prompt(self) -> str:
|
| 32 |
-
"""ایجاد دستورالعمل سیستمی
|
| 33 |
-
return """شما
|
|
|
|
|
|
|
| 34 |
|
| 35 |
## قوانین اندیسگذاری:
|
| 36 |
1. **ترتیب پیوسته**: company-01, company-02, ... | person-01, person-02, ... | amount-01, amount-02, ... | percent-01, percent-02, ...
|
|
@@ -39,60 +41,35 @@ class AdvancedCerebrasAnonymizer:
|
|
| 39 |
4. **اشاره ضمنی**: "این شرکت" اگر به company-01 اشاره دارد → company-01 (نه company-02)
|
| 40 |
|
| 41 |
## انواع موجودیت:
|
| 42 |
-
- **company-XX**: شرکتها، بانکها، سازمانها، گروهها
|
| 43 |
-
- **person-XX**: نام و نام خانوادگی اشخاص
|
| 44 |
-
- **amount-XX**: مبالغ
|
| 45 |
-
- **percent-XX**: درصدها
|
| 46 |
-
|
| 47 |
-
## ⚠️ قوانین کلیدی:
|
| 48 |
-
1. **بازرس = شرکت است**: "بازرس قانونی" → company-XX (نه person)
|
| 49 |
-
2. **واحدها**: "amount-01 میلیارد تومان" ✅ | "amount-01" ❌
|
| 50 |
-
3. **گروهها**: "گروه مالی صبا" → company-XX (نه group-XX)
|
| 51 |
-
4. **کلمات عمومی حفظ**: "سه شرکت"، "چند بانک"، "مراکز درمانی" → حفظ (موجودیت نیستند)
|
| 52 |
-
5. **دوره زمانی حفظ**: "۵ ماهه"، "۹ ماهه اول" → حفظ (نه amount)
|
| 53 |
-
6. **بازه = یک entity**: "یک تا 1.5 میلیون" → amount-01
|
| 54 |
-
7. **شماره ثبت حفظ**: "شماره 11385" → حفظ (نه amount)
|
| 55 |
-
8. **نهادهای عمومی حفظ**: "مرجع ثبت شرکتها"، "هیئت مجلس" → حفظ
|
| 56 |
-
|
| 57 |
-
## موارد حفظ شده:
|
| 58 |
-
تاریخ، فصل (پاییز)، عناوین شغلی، مکانها، کلمات عمومی بدون نام خاص، دورههای زمانی (۹ ماهه)
|
| 59 |
-
|
| 60 |
-
## مثالهای کلیدی:
|
| 61 |
-
|
| 62 |
-
**مثال 1:**
|
| 63 |
-
ورودی: مهدی اخوان، مدیرعامل همراه اول، اعلام کرد درآمد با رشد 37 درصدی به 677 میلیارد تومان رسید. سود تلفیگی گروه همراه اول 8003 میلیارد شد.
|
| 64 |
-
خروجی: person-01، مدیرعامل company-01، اعلام کرد درآمد با رشد percent-01 به amount-01 رسید. سود تلفیگی company-01 amount-02 شد.
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
|
| 70 |
-
|
| 71 |
-
ورودی:
|
| 72 |
-
خروجی:
|
| 73 |
|
| 74 |
-
|
| 75 |
-
ورودی: همراه اول در ۹ ماه سال 49 هزار میلیارد درآمد کسب کرد. عملکرد ۵ ماهه رشد 37 درصدی داشت.
|
| 76 |
-
خروجی: company-01 در ۹ ماه سال amount-01 درآمد کسب کرد. عملکرد ۵ ماهه رشد percent-01 داشت.
|
| 77 |
-
|
| 78 |
-
**مثال 5:**
|
| 79 |
-
ورودی: پالایش نفت اصفهان EPS آن به 2500 ریال میرسد. این شرکت یکی از بزرگترین پالایشگاهها است.
|
| 80 |
-
خروجی: company-01 EPS آن به amount-01 میرسد. این شرکت یکی از بزرگترین پالایشگاهها است.
|
| 81 |
-
⚠️ "این شرکت" = company-01 (نه company-02)
|
| 82 |
-
|
| 83 |
-
**مثال 6:**
|
| 84 |
-
ورودی: سازمان تامین اجتماعی دارای سه شرکت دارویی است که از مراکز درمانی وزارت بهداشت مطالبات دارند.
|
| 85 |
-
خروجی: company-01 دارای سه شرکت دارویی است که از مراکز درمانی company-02 مطالبات دارند.
|
| 86 |
-
|
| 87 |
-
فقط متن ناشناسشده را برگردان، بدون توضیح."""
|
| 88 |
|
| 89 |
def _make_api_request(self, text: str) -> Dict[str, Any]:
|
| 90 |
-
"""ارسال درخواست به Cerebras API
|
| 91 |
headers = {
|
| 92 |
"Authorization": f"Bearer {self.config.api_key}",
|
| 93 |
"Content-Type": "application/json"
|
| 94 |
}
|
| 95 |
|
|
|
|
|
|
|
|
|
|
| 96 |
payload = {
|
| 97 |
"messages": [
|
| 98 |
{
|
|
@@ -101,7 +78,7 @@ class AdvancedCerebrasAnonymizer:
|
|
| 101 |
},
|
| 102 |
{
|
| 103 |
"role": "user",
|
| 104 |
-
"content":
|
| 105 |
}
|
| 106 |
],
|
| 107 |
"model": self.config.model,
|
|
@@ -115,7 +92,7 @@ class AdvancedCerebrasAnonymizer:
|
|
| 115 |
f"{self.config.base_url}/chat/completions",
|
| 116 |
headers=headers,
|
| 117 |
json=payload,
|
| 118 |
-
timeout=60
|
| 119 |
)
|
| 120 |
response.raise_for_status()
|
| 121 |
return response.json()
|
|
@@ -142,12 +119,18 @@ class AdvancedCerebrasAnonymizer:
|
|
| 142 |
|
| 143 |
content = response["choices"][0]["message"]["content"]
|
| 144 |
|
|
|
|
|
|
|
|
|
|
| 145 |
# پاک کردن markdown اگر وجود دارد
|
| 146 |
content = self._clean_markdown(content)
|
| 147 |
|
| 148 |
# حذف خطوط اضافی و فضاهای خالی
|
| 149 |
content = content.strip()
|
| 150 |
|
|
|
|
|
|
|
|
|
|
| 151 |
# تحلیل نتایج
|
| 152 |
analysis = self._analyze_anonymized_text(content)
|
| 153 |
|
|
@@ -167,6 +150,16 @@ class AdvancedCerebrasAnonymizer:
|
|
| 167 |
"error": f"خطا در پردازش: {str(e)}"
|
| 168 |
}
|
| 169 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
def _clean_markdown(self, content: str) -> str:
|
| 171 |
"""پاک کردن markdown از پاسخ"""
|
| 172 |
if "```" in content:
|
|
@@ -182,6 +175,19 @@ class AdvancedCerebrasAnonymizer:
|
|
| 182 |
content = '\n'.join(clean_lines)
|
| 183 |
return content
|
| 184 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
|
| 186 |
"""تحلیل متن ناشناسسازی شده"""
|
| 187 |
import re
|
|
@@ -270,13 +276,14 @@ class AdvancedCerebrasAnonymizer:
|
|
| 270 |
}
|
| 271 |
}
|
| 272 |
|
|
|
|
|
|
|
|
|
|
| 273 |
def create_advanced_interface():
|
| 274 |
"""ایجاد رابط کاربری پیشرفته"""
|
| 275 |
|
| 276 |
-
# بررسی وجود کلید API
|
| 277 |
api_key_available = bool(os.getenv("CEREBRAS_API_KEY"))
|
| 278 |
|
| 279 |
-
# CSS سفارشی پیشرفته
|
| 280 |
custom_css = """
|
| 281 |
.gradio-container {
|
| 282 |
font-family: 'Tahoma', 'Arial', sans-serif !important;
|
|
@@ -348,23 +355,19 @@ def create_advanced_interface():
|
|
| 348 |
|
| 349 |
with gr.Blocks(css=custom_css, title="ناشناسساز پیشرفته با Qwen 3-32B", theme=gr.themes.Soft()) as interface:
|
| 350 |
|
| 351 |
-
# عنوان
|
| 352 |
gr.Markdown("""
|
| 353 |
# 🔒 سیستم پیشرفته ناشناسسازی متون مالی/خبری فارسی
|
| 354 |
-
### ⚡ قدرتگرفته از Cerebras AI
|
| 355 |
""")
|
| 356 |
|
| 357 |
-
# نمایش اطلاعات مدل Qwen
|
| 358 |
gr.Markdown("""
|
| 359 |
<div class="qwen-box">
|
| 360 |
-
🚀 <strong>مدل: Alibaba Qwen 3-32B</strong><br>
|
| 361 |
-
⚡ سرعت: 2,400
|
| 362 |
-
|
| 363 |
-
🎯 مدل Reasoning هیبریدی با دقت بالا
|
| 364 |
</div>
|
| 365 |
""")
|
| 366 |
|
| 367 |
-
# نمایش وضعیت API
|
| 368 |
if api_key_available:
|
| 369 |
gr.Markdown("""
|
| 370 |
<div class="success-box">
|
|
@@ -376,7 +379,7 @@ def create_advanced_interface():
|
|
| 376 |
gr.Markdown("""
|
| 377 |
<div class="warning-box">
|
| 378 |
⚠️ <strong>کلید API تنظیم نشده</strong><br>
|
| 379 |
-
لطفاً کلید Cerebras API خود را
|
| 380 |
</div>
|
| 381 |
""")
|
| 382 |
api_key_input = gr.Textbox(
|
|
@@ -396,7 +399,7 @@ def create_advanced_interface():
|
|
| 396 |
|
| 397 |
with gr.Row():
|
| 398 |
anonymize_btn = gr.Button(
|
| 399 |
-
"🔒 ناشناسسازی
|
| 400 |
variant="primary",
|
| 401 |
size="lg"
|
| 402 |
)
|
|
@@ -413,23 +416,19 @@ def create_advanced_interface():
|
|
| 413 |
elem_classes=["result-box"]
|
| 414 |
)
|
| 415 |
|
| 416 |
-
# دکمه کپی
|
| 417 |
copy_btn = gr.Button(
|
| 418 |
"📋 کپی متن",
|
| 419 |
variant="secondary",
|
| 420 |
size="sm"
|
| 421 |
)
|
| 422 |
|
| 423 |
-
# متن برای کپی
|
| 424 |
copy_output = gr.Textbox(
|
| 425 |
-
label="📋 متن برای کپی
|
| 426 |
lines=3,
|
| 427 |
-
max_lines=10,
|
| 428 |
visible=False,
|
| 429 |
interactive=True
|
| 430 |
)
|
| 431 |
|
| 432 |
-
# نمایش آمار پیشرفته
|
| 433 |
with gr.Row():
|
| 434 |
with gr.Column():
|
| 435 |
statistics_output = gr.Markdown(label="📊 آمار کلی")
|
|
@@ -438,19 +437,17 @@ def create_advanced_interface():
|
|
| 438 |
|
| 439 |
with gr.Row():
|
| 440 |
with gr.Column():
|
| 441 |
-
entities_output = gr.Markdown(label="🏷️
|
| 442 |
with gr.Column():
|
| 443 |
detailed_analysis_output = gr.Markdown(label="🔍 تحلیل دقیق")
|
| 444 |
|
| 445 |
usage_output = gr.Markdown(label="⚡ اطلاعات پردازش")
|
| 446 |
|
| 447 |
def process_advanced_text(text: str, api_key_manual: str = ""):
|
| 448 |
-
"""پردازش
|
| 449 |
-
# حل مشکل NoneType
|
| 450 |
if api_key_manual is None:
|
| 451 |
api_key_manual = ""
|
| 452 |
|
| 453 |
-
# تعیین کلید API
|
| 454 |
final_api_key = ""
|
| 455 |
if api_key_manual and api_key_manual.strip():
|
| 456 |
final_api_key = api_key_manual.strip()
|
|
@@ -458,124 +455,72 @@ def create_advanced_interface():
|
|
| 458 |
final_api_key = os.getenv("CEREBRAS_API_KEY")
|
| 459 |
|
| 460 |
if not final_api_key:
|
| 461 |
-
return (
|
| 462 |
-
"",
|
| 463 |
-
"❌ کلید API وارد نشده است",
|
| 464 |
-
"",
|
| 465 |
-
"",
|
| 466 |
-
"",
|
| 467 |
-
""
|
| 468 |
-
)
|
| 469 |
|
| 470 |
if not text or not text.strip():
|
| 471 |
-
return (
|
| 472 |
-
"",
|
| 473 |
-
"❌ لطفاً متن ورودی را وارد کنید",
|
| 474 |
-
"",
|
| 475 |
-
"",
|
| 476 |
-
"",
|
| 477 |
-
""
|
| 478 |
-
)
|
| 479 |
|
| 480 |
try:
|
| 481 |
anonymizer = AdvancedCerebrasAnonymizer(api_key=final_api_key)
|
| 482 |
result = anonymizer.anonymize_text(text)
|
| 483 |
|
| 484 |
if not result["success"]:
|
| 485 |
-
return (
|
| 486 |
-
"",
|
| 487 |
-
f"❌ خطا: {result['error']}",
|
| 488 |
-
"",
|
| 489 |
-
"",
|
| 490 |
-
"",
|
| 491 |
-
""
|
| 492 |
-
)
|
| 493 |
|
| 494 |
-
# آمار کلی
|
| 495 |
stats = result.get("statistics", {})
|
| 496 |
-
stats_md = "📊 **آمار
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
<h3>👤 اشخاص</h3>
|
| 507 |
-
<h2>{stats.get('person', 0)}</h2>
|
| 508 |
-
</div>
|
| 509 |
-
<div class="stat-card">
|
| 510 |
-
<h3>💰 مبالغ</h3>
|
| 511 |
-
<h2>{stats.get('amount', 0)}</h2>
|
| 512 |
-
</div>
|
| 513 |
-
<div class="stat-card">
|
| 514 |
-
<h3>📊 درصدها</h3>
|
| 515 |
-
<h2>{stats.get('percent', 0)}</h2>
|
| 516 |
-
</div>
|
| 517 |
-
<div class="stat-card">
|
| 518 |
-
<h3>🔢 کل تغییرات</h3>
|
| 519 |
-
<h2>{stats.get('total_replacements', 0)}</h2>
|
| 520 |
-
</div>
|
| 521 |
-
</div>
|
| 522 |
-
"""
|
| 523 |
|
| 524 |
-
# کنترل کیفیت
|
| 525 |
quality = result.get("quality_check", {})
|
| 526 |
quality_md = "✅ **کنترل کیفیت:**\n\n"
|
| 527 |
|
| 528 |
if quality.get("is_valid", False):
|
| 529 |
-
quality_md += '<span class="quality-badge quality-pass">✅
|
| 530 |
else:
|
| 531 |
-
quality_md += '<span class="quality-badge quality-fail">❌
|
| 532 |
issues = quality.get("issues", [])
|
| 533 |
if issues:
|
| 534 |
quality_md += "**مشکلات:**\n"
|
| 535 |
for issue in issues:
|
| 536 |
quality_md += f"• {issue}\n"
|
| 537 |
|
| 538 |
-
entity_counts = quality.get("entity_counts", {})
|
| 539 |
-
if entity_counts:
|
| 540 |
-
quality_md += f"\n**تعداد موجودیتهای منحصربهفرد:**\n"
|
| 541 |
-
for entity_type, count in entity_counts.items():
|
| 542 |
-
if count > 0:
|
| 543 |
-
quality_md += f"• {entity_type}: {count}\n"
|
| 544 |
-
|
| 545 |
-
# موجودیتهای شناسایی شده
|
| 546 |
entities = result.get("entities", {})
|
| 547 |
-
entities_md = "🏷️
|
| 548 |
|
| 549 |
if entities.get("companies"):
|
| 550 |
-
entities_md += f"🏢
|
| 551 |
if entities.get("persons"):
|
| 552 |
-
entities_md += f"👤
|
| 553 |
if entities.get("amounts"):
|
| 554 |
-
entities_md += f"💰
|
| 555 |
if entities.get("percents"):
|
| 556 |
-
entities_md += f"📊
|
| 557 |
|
| 558 |
-
# تحلیل دقیق
|
| 559 |
detailed = result.get("detailed_analysis", {})
|
| 560 |
-
detailed_md = "🔍
|
| 561 |
-
|
| 562 |
-
|
| 563 |
-
|
| 564 |
-
|
|
|
|
| 565 |
|
| 566 |
-
# اطلاعات پردازش
|
| 567 |
usage = result.get("usage", {})
|
| 568 |
-
usage_md = "⚡
|
| 569 |
-
|
| 570 |
-
|
| 571 |
-
|
| 572 |
-
|
| 573 |
-
|
| 574 |
-
|
| 575 |
-
|
| 576 |
-
usage_md += f"\n⚡ **سرعت Qwen 3-32B: 2,400 tokens/second - فوقالعاده سریع!**"
|
| 577 |
-
else:
|
| 578 |
-
usage_md += "✅ پردازش با موفقیت انجام شد"
|
| 579 |
|
| 580 |
return (
|
| 581 |
result["anonymized_text"],
|
|
@@ -587,27 +532,16 @@ def create_advanced_interface():
|
|
| 587 |
)
|
| 588 |
|
| 589 |
except Exception as e:
|
| 590 |
-
return (
|
| 591 |
-
"",
|
| 592 |
-
f"❌ خطایی غیرمنتظره: {str(e)}",
|
| 593 |
-
"",
|
| 594 |
-
"",
|
| 595 |
-
"",
|
| 596 |
-
""
|
| 597 |
-
)
|
| 598 |
|
| 599 |
def copy_text(text_to_copy):
|
| 600 |
-
"""تابع کپی متن"""
|
| 601 |
if not text_to_copy or not text_to_copy.strip():
|
| 602 |
-
return gr.Textbox(visible=False), "⚠️
|
| 603 |
-
|
| 604 |
-
return gr.Textbox(value=text_to_copy, visible=True), "✅ متن در کادر زیر آماده کپی است"
|
| 605 |
|
| 606 |
def clear_all():
|
| 607 |
-
"""پاک کردن تمام فیلدها"""
|
| 608 |
return "", "", "", "", "", "", "", gr.Textbox(visible=False)
|
| 609 |
|
| 610 |
-
# اتصال رویدادها
|
| 611 |
anonymize_btn.click(
|
| 612 |
fn=process_advanced_text,
|
| 613 |
inputs=[input_text, api_key_input],
|
|
@@ -625,69 +559,35 @@ def create_advanced_interface():
|
|
| 625 |
outputs=[input_text, output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output, copy_output]
|
| 626 |
)
|
| 627 |
|
| 628 |
-
# مثالهای پیشرفته
|
| 629 |
gr.Examples(
|
| 630 |
examples=[
|
| 631 |
-
["
|
| 632 |
-
["
|
| 633 |
-
["
|
| 634 |
-
["براساس آخرین گزارش سازمان تنظیم مقررات رادیویی در پاییز ۱۴۰۱ تعداد مشترکین تلفن همراه در ایران به بالای ۱۴۵ میلیون نفر رسیده که نسبت به سال گذشته حدود ۷.۲ درصد رشد داشته است."],
|
| 635 |
-
["شرکت فولاد مبارکه اصفهان با همکاری شرکت ملی نفت ایران، قرارداد توسعه میدان گازی مدار را امضا کرد. شرکت فاما قصد دارد سرمایه خود را از ۸،۷۰۰ میلیارد ریال به ۱۲،۵۰۰ میلیارد ریال افزایش دهد."],
|
| 636 |
-
["صورتهای مالی سه خودروساز بزرگ کشور نشان میدهد که زیان انباشته تلفیقی خودروسازان از مرز 500 همت عبور کرده و به 620 همت رسیده است."]
|
| 637 |
],
|
| 638 |
inputs=input_text,
|
| 639 |
-
label="📚
|
| 640 |
)
|
| 641 |
|
| 642 |
-
|
| 643 |
-
with gr.Accordion("📖 راهنمای کامل استفاده", open=False):
|
| 644 |
gr.Markdown("""
|
| 645 |
-
##
|
| 646 |
-
|
| 647 |
-
### 🚀 مزایای Qwen 3-32B:
|
| 648 |
-
- **سرعت فوقالعاده:** 2,400 tokens/second
|
| 649 |
-
- **دقت بالا:** 32 میلیارد پارامتر
|
| 650 |
-
- **Context بزرگ:** 128K tokens
|
| 651 |
-
- **مدل Reasoning:** قابلیت استدلال پیشرفته
|
| 652 |
-
- **قیمت مناسب:** $0.40/M input, $0.80/M output
|
| 653 |
-
- **پشتیبانی عالی از فارسی:** به عنوان یکی از مدلهای قدرتمند چندزبانه
|
| 654 |
-
|
| 655 |
-
### ⚙️ پارامترهای بهینه شده:
|
| 656 |
-
- **Temperature:** 0.6 (توصیه شده برای Qwen)
|
| 657 |
-
- **Top-P:** 0.95 (برای تنوع و دقت بهتر)
|
| 658 |
-
- **Max Tokens:** 2000
|
| 659 |
|
| 660 |
-
###
|
| 661 |
-
-
|
| 662 |
-
|
| 663 |
-
|
| 664 |
-
-
|
| 665 |
-
- **amount-XX:** مبالغ (با حفظ واحد)
|
| 666 |
-
- **percent-XX:** درصدها
|
| 667 |
|
| 668 |
-
###
|
| 669 |
-
|
| 670 |
-
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
| 674 |
-
|
| 675 |
-
### 💡 نکات مهم:
|
| 676 |
-
- Qwen 3-32B دقت بالاتری نسبت به Llama 3.1-8B دارد
|
| 677 |
-
- مدل Reasoning برای تحلیل پیچیده بهتر است
|
| 678 |
-
- سرعت 2,400 t/s یعنی پاسخ تقریباً لحظهای
|
| 679 |
-
- برای متون طولانیتر (تا 128K tokens) مناسب است
|
| 680 |
-
|
| 681 |
-
### 🔧 مدلهای جایگزین Cerebras:
|
| 682 |
-
qwen-3-32b # فعلی ⭐ (توصیه میشود)
|
| 683 |
-
qwen-3-235b-a22b-instruct-2507 # قدرتمندتر اما گرانتر
|
| 684 |
-
llama3.1-8b # سریعتر اما ضعیفتر
|
| 685 |
-
llama3.3-70b # تعادل خوب
|
| 686 |
-
""")
|
| 687 |
|
| 688 |
return interface
|
| 689 |
|
| 690 |
-
# اجرای برنامه
|
| 691 |
if __name__ == "__main__":
|
| 692 |
interface = create_advanced_interface()
|
| 693 |
interface.launch(
|
|
|
|
| 11 |
"""تنظیمات Cerebras API برای Qwen 3-32B"""
|
| 12 |
api_key: str
|
| 13 |
base_url: str = "https://api.cerebras.ai/v1"
|
| 14 |
+
model: str = "qwen-3-32b"
|
| 15 |
+
max_tokens: int = 3000 # افزایش برای thinking tokens
|
| 16 |
+
temperature: float = 0.3 # کاهش برای دقت بیشتر
|
| 17 |
+
top_p: float = 0.9
|
| 18 |
|
| 19 |
class AdvancedCerebrasAnonymizer:
|
| 20 |
"""سیستم پیشرفته ناشناسسازی متون مالی/خبری فارسی با Qwen 3-32B"""
|
|
|
|
| 29 |
self.system_prompt = self._create_advanced_system_prompt()
|
| 30 |
|
| 31 |
def _create_advanced_system_prompt(self) -> str:
|
| 32 |
+
"""ایجاد دستورالعمل سیستمی بهینه شده"""
|
| 33 |
+
return """شما یک سیستم ناشناسسازی متون مالی فارسی هستید.
|
| 34 |
+
|
| 35 |
+
⚠️ CRITICAL: در پاسخ نهایی خود، فقط و فقط متن ناشناسسازی شده را برگردانید، بدون هیچ توضیح، تحلیل، یا تگ اضافی.
|
| 36 |
|
| 37 |
## قوانین اندیسگذاری:
|
| 38 |
1. **ترتیب پیوسته**: company-01, company-02, ... | person-01, person-02, ... | amount-01, amount-02, ... | percent-01, percent-02, ...
|
|
|
|
| 41 |
4. **اشاره ضمنی**: "این شرکت" اگر به company-01 اشاره دارد → company-01 (نه company-02)
|
| 42 |
|
| 43 |
## انواع موجودیت:
|
| 44 |
+
- **company-XX**: شرکتها، بانکها، سازمانها، گروهها
|
| 45 |
+
- **person-XX**: نام و نام خانوادگی اشخاص
|
| 46 |
+
- **amount-XX**: مبالغ - واحد را حفظ کن
|
| 47 |
+
- **percent-XX**: درصدها
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
+
## قوانین کلیدی:
|
| 50 |
+
1. بازرس = شرکت است → company-XX
|
| 51 |
+
2. واحدها: "amount-01 میلیارد تومان" ✅
|
| 52 |
+
3. گروهها: "گروه X" → company-XX
|
| 53 |
+
4. کلمات عمومی حفظ: "سه شرکت" → حفظ
|
| 54 |
+
5. دوره زمانی حفظ: "۵ ماهه" → حفظ
|
| 55 |
+
6. بازه = یک entity: "یک تا 1.5 میلیون" → amount-01
|
| 56 |
|
| 57 |
+
## مثال:
|
| 58 |
+
ورودی: ایران خودرو در اسفند 1402 حدود 23 هزار و 296 میلیارد درآمد کسب کرد که 4.58 درصد افزایش داشت.
|
| 59 |
+
خروجی: company-01 در اسفند 1402 حدود amount-01 درآمد کسب کرد که percent-01 افزایش داشت.
|
| 60 |
|
| 61 |
+
⚠️ یادآوری: فقط متن ناشناسشده، بدون هیچ توضیح اضافی."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
|
| 63 |
def _make_api_request(self, text: str) -> Dict[str, Any]:
|
| 64 |
+
"""ارسال درخواست به Cerebras API"""
|
| 65 |
headers = {
|
| 66 |
"Authorization": f"Bearer {self.config.api_key}",
|
| 67 |
"Content-Type": "application/json"
|
| 68 |
}
|
| 69 |
|
| 70 |
+
# اضافه کردن /no_think به متن برای غیرفعال کردن reasoning
|
| 71 |
+
user_content = f"{text}\n\n/no_think"
|
| 72 |
+
|
| 73 |
payload = {
|
| 74 |
"messages": [
|
| 75 |
{
|
|
|
|
| 78 |
},
|
| 79 |
{
|
| 80 |
"role": "user",
|
| 81 |
+
"content": user_content
|
| 82 |
}
|
| 83 |
],
|
| 84 |
"model": self.config.model,
|
|
|
|
| 92 |
f"{self.config.base_url}/chat/completions",
|
| 93 |
headers=headers,
|
| 94 |
json=payload,
|
| 95 |
+
timeout=60
|
| 96 |
)
|
| 97 |
response.raise_for_status()
|
| 98 |
return response.json()
|
|
|
|
| 119 |
|
| 120 |
content = response["choices"][0]["message"]["content"]
|
| 121 |
|
| 122 |
+
# پاک کردن thinking tags اگر وجود دارد
|
| 123 |
+
content = self._remove_thinking_tags(content)
|
| 124 |
+
|
| 125 |
# پاک کردن markdown اگر وجود دارد
|
| 126 |
content = self._clean_markdown(content)
|
| 127 |
|
| 128 |
# حذف خطوط اضافی و فضاهای خالی
|
| 129 |
content = content.strip()
|
| 130 |
|
| 131 |
+
# حذف توضیحات اضافی در ابتدا یا انتها
|
| 132 |
+
content = self._clean_explanations(content)
|
| 133 |
+
|
| 134 |
# تحلیل نتایج
|
| 135 |
analysis = self._analyze_anonymized_text(content)
|
| 136 |
|
|
|
|
| 150 |
"error": f"خطا در پردازش: {str(e)}"
|
| 151 |
}
|
| 152 |
|
| 153 |
+
def _remove_thinking_tags(self, content: str) -> str:
|
| 154 |
+
"""حذف تگهای thinking از خروجی"""
|
| 155 |
+
# حذف محتوای داخل <think>...</think>
|
| 156 |
+
content = re.sub(r'<think>.*?</think>', '', content, flags=re.DOTALL)
|
| 157 |
+
|
| 158 |
+
# حذف تگهای خالی
|
| 159 |
+
content = re.sub(r'</?think>', '', content)
|
| 160 |
+
|
| 161 |
+
return content.strip()
|
| 162 |
+
|
| 163 |
def _clean_markdown(self, content: str) -> str:
|
| 164 |
"""پاک کردن markdown از پاسخ"""
|
| 165 |
if "```" in content:
|
|
|
|
| 175 |
content = '\n'.join(clean_lines)
|
| 176 |
return content
|
| 177 |
|
| 178 |
+
def _clean_explanations(self, content: str) -> str:
|
| 179 |
+
"""حذف توضیحات اضافی در ابتدا یا انتها"""
|
| 180 |
+
lines = content.split('\n')
|
| 181 |
+
clean_lines = []
|
| 182 |
+
|
| 183 |
+
for line in lines:
|
| 184 |
+
# حذف خطوطی که شامل توضیحات متا هستند
|
| 185 |
+
if any(word in line.lower() for word in ['okay', 'let me', 'here is', 'خروجی', 'نتیجه', 'پاسخ']):
|
| 186 |
+
continue
|
| 187 |
+
clean_lines.append(line)
|
| 188 |
+
|
| 189 |
+
return '\n'.join(clean_lines).strip()
|
| 190 |
+
|
| 191 |
def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
|
| 192 |
"""تحلیل متن ناشناسسازی شده"""
|
| 193 |
import re
|
|
|
|
| 276 |
}
|
| 277 |
}
|
| 278 |
|
| 279 |
+
# باقی کد رابط کاربری همان قبل است...
|
| 280 |
+
# (تابع create_advanced_interface و بقیه کد تغییری ندارد)
|
| 281 |
+
|
| 282 |
def create_advanced_interface():
|
| 283 |
"""ایجاد رابط کاربری پیشرفته"""
|
| 284 |
|
|
|
|
| 285 |
api_key_available = bool(os.getenv("CEREBRAS_API_KEY"))
|
| 286 |
|
|
|
|
| 287 |
custom_css = """
|
| 288 |
.gradio-container {
|
| 289 |
font-family: 'Tahoma', 'Arial', sans-serif !important;
|
|
|
|
| 355 |
|
| 356 |
with gr.Blocks(css=custom_css, title="ناشناسساز پیشرفته با Qwen 3-32B", theme=gr.themes.Soft()) as interface:
|
| 357 |
|
|
|
|
| 358 |
gr.Markdown("""
|
| 359 |
# 🔒 سیستم پیشرفته ناشناسسازی متون مالی/خبری فارسی
|
| 360 |
+
### ⚡ قدرتگرفته از Cerebras AI + Alibaba Qwen 3-32B
|
| 361 |
""")
|
| 362 |
|
|
|
|
| 363 |
gr.Markdown("""
|
| 364 |
<div class="qwen-box">
|
| 365 |
+
🚀 <strong>مدل: Alibaba Qwen 3-32B (اصلاح شده)</strong><br>
|
| 366 |
+
⚡ سرعت: 2,400 توکن/ثانیه | 🧠 32B پارامتر | 💰 $0.40/$0.80<br>
|
| 367 |
+
✅ <strong>بهینهسازی شده:</strong> Thinking Mode غیرفعال برای خروجی مستقیم
|
|
|
|
| 368 |
</div>
|
| 369 |
""")
|
| 370 |
|
|
|
|
| 371 |
if api_key_available:
|
| 372 |
gr.Markdown("""
|
| 373 |
<div class="success-box">
|
|
|
|
| 379 |
gr.Markdown("""
|
| 380 |
<div class="warning-box">
|
| 381 |
⚠️ <strong>کلید API تنظیم نشده</strong><br>
|
| 382 |
+
لطفاً کلید Cerebras API خود را وارد کنید
|
| 383 |
</div>
|
| 384 |
""")
|
| 385 |
api_key_input = gr.Textbox(
|
|
|
|
| 399 |
|
| 400 |
with gr.Row():
|
| 401 |
anonymize_btn = gr.Button(
|
| 402 |
+
"🔒 ناشناسسازی",
|
| 403 |
variant="primary",
|
| 404 |
size="lg"
|
| 405 |
)
|
|
|
|
| 416 |
elem_classes=["result-box"]
|
| 417 |
)
|
| 418 |
|
|
|
|
| 419 |
copy_btn = gr.Button(
|
| 420 |
"📋 کپی متن",
|
| 421 |
variant="secondary",
|
| 422 |
size="sm"
|
| 423 |
)
|
| 424 |
|
|
|
|
| 425 |
copy_output = gr.Textbox(
|
| 426 |
+
label="📋 متن برای کپی",
|
| 427 |
lines=3,
|
|
|
|
| 428 |
visible=False,
|
| 429 |
interactive=True
|
| 430 |
)
|
| 431 |
|
|
|
|
| 432 |
with gr.Row():
|
| 433 |
with gr.Column():
|
| 434 |
statistics_output = gr.Markdown(label="📊 آمار کلی")
|
|
|
|
| 437 |
|
| 438 |
with gr.Row():
|
| 439 |
with gr.Column():
|
| 440 |
+
entities_output = gr.Markdown(label="🏷️ موجودیتها")
|
| 441 |
with gr.Column():
|
| 442 |
detailed_analysis_output = gr.Markdown(label="🔍 تحلیل دقیق")
|
| 443 |
|
| 444 |
usage_output = gr.Markdown(label="⚡ اطلاعات پردازش")
|
| 445 |
|
| 446 |
def process_advanced_text(text: str, api_key_manual: str = ""):
|
| 447 |
+
"""پردازش متن"""
|
|
|
|
| 448 |
if api_key_manual is None:
|
| 449 |
api_key_manual = ""
|
| 450 |
|
|
|
|
| 451 |
final_api_key = ""
|
| 452 |
if api_key_manual and api_key_manual.strip():
|
| 453 |
final_api_key = api_key_manual.strip()
|
|
|
|
| 455 |
final_api_key = os.getenv("CEREBRAS_API_KEY")
|
| 456 |
|
| 457 |
if not final_api_key:
|
| 458 |
+
return ("", "❌ کلید API وارد نشده است", "", "", "", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 459 |
|
| 460 |
if not text or not text.strip():
|
| 461 |
+
return ("", "❌ لطفاً متن ورودی را وارد کنید", "", "", "", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 462 |
|
| 463 |
try:
|
| 464 |
anonymizer = AdvancedCerebrasAnonymizer(api_key=final_api_key)
|
| 465 |
result = anonymizer.anonymize_text(text)
|
| 466 |
|
| 467 |
if not result["success"]:
|
| 468 |
+
return ("", f"❌ خطا: {result['error']}", "", "", "", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 469 |
|
|
|
|
| 470 |
stats = result.get("statistics", {})
|
| 471 |
+
stats_md = f"""📊 **آمار کلی:**
|
| 472 |
+
|
| 473 |
+
<div class="stats-grid">
|
| 474 |
+
<div class="stat-card"><h3>🏢 شرکتها</h3><h2>{stats.get('company', 0)}</h2></div>
|
| 475 |
+
<div class="stat-card"><h3>👤 اشخاص</h3><h2>{stats.get('person', 0)}</h2></div>
|
| 476 |
+
<div class="stat-card"><h3>💰 مبالغ</h3><h2>{stats.get('amount', 0)}</h2></div>
|
| 477 |
+
<div class="stat-card"><h3>📊 درصدها</h3><h2>{stats.get('percent', 0)}</h2></div>
|
| 478 |
+
<div class="stat-card"><h3>🔢 کل</h3><h2>{stats.get('total_replacements', 0)}</h2></div>
|
| 479 |
+
</div>
|
| 480 |
+
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
|
|
|
|
| 482 |
quality = result.get("quality_check", {})
|
| 483 |
quality_md = "✅ **کنترل کیفیت:**\n\n"
|
| 484 |
|
| 485 |
if quality.get("is_valid", False):
|
| 486 |
+
quality_md += '<span class="quality-badge quality-pass">✅ موفق</span>\n\n'
|
| 487 |
else:
|
| 488 |
+
quality_md += '<span class="quality-badge quality-fail">❌ مشکل</span>\n\n'
|
| 489 |
issues = quality.get("issues", [])
|
| 490 |
if issues:
|
| 491 |
quality_md += "**مشکلات:**\n"
|
| 492 |
for issue in issues:
|
| 493 |
quality_md += f"• {issue}\n"
|
| 494 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 495 |
entities = result.get("entities", {})
|
| 496 |
+
entities_md = "🏷️ **موجودیتها:**\n\n"
|
| 497 |
|
| 498 |
if entities.get("companies"):
|
| 499 |
+
entities_md += f"🏢 company-{', company-'.join(entities['companies'])}\n\n"
|
| 500 |
if entities.get("persons"):
|
| 501 |
+
entities_md += f"👤 person-{', person-'.join(entities['persons'])}\n\n"
|
| 502 |
if entities.get("amounts"):
|
| 503 |
+
entities_md += f"💰 amount-{', amount-'.join(entities['amounts'])}\n\n"
|
| 504 |
if entities.get("percents"):
|
| 505 |
+
entities_md += f"📊 percent-{', percent-'.join(entities['percents'])}\n\n"
|
| 506 |
|
|
|
|
| 507 |
detailed = result.get("detailed_analysis", {})
|
| 508 |
+
detailed_md = f"""🔍 **تحلیل:**
|
| 509 |
+
|
| 510 |
+
📅 تاریخ: {detailed.get('preserved_dates', 0)}
|
| 511 |
+
📈 شاخصها: {detailed.get('financial_indicators', 0)}
|
| 512 |
+
📏 واحدها: {detailed.get('units_preserved', 0)}
|
| 513 |
+
"""
|
| 514 |
|
|
|
|
| 515 |
usage = result.get("usage", {})
|
| 516 |
+
usage_md = f"""⚡ **Qwen 3-32B:**
|
| 517 |
+
|
| 518 |
+
🤖 مدل: {anonymizer.config.model}
|
| 519 |
+
🌡️ Temperature: {anonymizer.config.temperature}
|
| 520 |
+
📥 Input: {usage.get('prompt_tokens', '?')}
|
| 521 |
+
📤 Output: {usage.get('completion_tokens', '?')}
|
| 522 |
+
📊 Total: {usage.get('total_tokens', '?')}
|
| 523 |
+
"""
|
|
|
|
|
|
|
|
|
|
| 524 |
|
| 525 |
return (
|
| 526 |
result["anonymized_text"],
|
|
|
|
| 532 |
)
|
| 533 |
|
| 534 |
except Exception as e:
|
| 535 |
+
return ("", f"❌ خطا: {str(e)}", "", "", "", "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 536 |
|
| 537 |
def copy_text(text_to_copy):
|
|
|
|
| 538 |
if not text_to_copy or not text_to_copy.strip():
|
| 539 |
+
return gr.Textbox(visible=False), "⚠️ متن خالی"
|
| 540 |
+
return gr.Textbox(value=text_to_copy, visible=True), "✅ آماده کپی"
|
|
|
|
| 541 |
|
| 542 |
def clear_all():
|
|
|
|
| 543 |
return "", "", "", "", "", "", "", gr.Textbox(visible=False)
|
| 544 |
|
|
|
|
| 545 |
anonymize_btn.click(
|
| 546 |
fn=process_advanced_text,
|
| 547 |
inputs=[input_text, api_key_input],
|
|
|
|
| 559 |
outputs=[input_text, output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output, copy_output]
|
| 560 |
)
|
| 561 |
|
|
|
|
| 562 |
gr.Examples(
|
| 563 |
examples=[
|
| 564 |
+
["ایران خودرو در اسفندماه سال 1402 حدود 23 هزار و 296 میلیارد تومان درآمد کسب کرد که در مقایسه با بهمن 4.58 درصد افزایش داشت."],
|
| 565 |
+
["مجمع پتروشیمی بوعلی سینا برگزار شد. وانیا نیک تدبیر را بازرس قانونی انتخاب کردند."],
|
| 566 |
+
["شرکت فولاد مبارکه اصفهان با ملی نفت قرارداد امضا کرد. فاما سرمایه را از 8700 به 12500 میلیارد افزایش میدهد."]
|
|
|
|
|
|
|
|
|
|
| 567 |
],
|
| 568 |
inputs=input_text,
|
| 569 |
+
label="📚 مثالها"
|
| 570 |
)
|
| 571 |
|
| 572 |
+
with gr.Accordion("📖 راهنما", open=False):
|
|
|
|
| 573 |
gr.Markdown("""
|
| 574 |
+
## 🔧 اصلاحات انجام شده:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 575 |
|
| 576 |
+
### ✅ مشکل حل شده:
|
| 577 |
+
- غیرفعال کردن Thinking Mode با `/no_think`
|
| 578 |
+
- حذف خودکار تگهای `<think>`
|
| 579 |
+
- پاکسازی توضیحات اضافی
|
| 580 |
+
- کاهش Temperature به 0.3 برای دقت بیشتر
|
|
|
|
|
|
|
| 581 |
|
| 582 |
+
### 🎯 چگونه کار میکند:
|
| 583 |
+
1. پرامپت بهینه شده برای خروجی مستقیم
|
| 584 |
+
2. اضافه کردن `/no_think` به انتهای درخواست
|
| 585 |
+
3. پردازش و پاکسازی خودکار خروجی
|
| 586 |
+
4. حذف تمام توضیحات و تگهای اضافی
|
| 587 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 588 |
|
| 589 |
return interface
|
| 590 |
|
|
|
|
| 591 |
if __name__ == "__main__":
|
| 592 |
interface = create_advanced_interface()
|
| 593 |
interface.launch(
|