leilaghomashchi commited on
Commit
2a90001
·
verified ·
1 Parent(s): 8e9375d

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -487
app.py DELETED
@@ -1,487 +0,0 @@
1
- import gradio as gr
2
- import re
3
- import os
4
- import requests
5
- import logging
6
- from typing import Dict, List, Tuple, Set
7
- import json
8
-
9
- logging.basicConfig(level=logging.INFO)
10
- logger = logging.getLogger(__name__)
11
-
12
- class AnonymizerCerebrasEnhanced:
13
- def __init__(self, api_key: str = None):
14
- self.api_key = api_key or os.getenv("CEREBRAS_API_KEY")
15
- self.mapping_table = {}
16
- self.counters = {
17
- 'company': 0, 'person': 0, 'amount': 0, 'phone': 0,
18
- 'email': 0, 'id_number': 0, 'date': 0, 'location': 0,
19
- 'percent': 0
20
- }
21
- self.seen_entities = {} # برای ثبات نگاشت
22
-
23
- if not self.api_key:
24
- raise ValueError("❌ کلید API Cerebras یافت نشد!")
25
-
26
- logger.info("✅ Anonymizer Enhanced مقداردهی شد")
27
-
28
- def get_system_prompt(self) -> str:
29
- """ایجاد دستورالعمل سیستمی پیشرفته برای Groq"""
30
- return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
31
-
32
- ## **قوانین اندیس‌گذاری - CRITICAL**
33
- ### **1. ترتیب شماره‌گذاری الزامی:**
34
- - شرکت‌ها: company-01, company-02, company-03, company-04, ... (پیوسته و بدون گپ)
35
- - اشخاص: person-01, person-02, person-03, ... (پیوسته و بدون گپ)
36
- - اعداد: amount-01, amount-02, amount-03, ... (پیوسته و بدون گپ)
37
- - درصدها: percent-01, percent-02, percent-03, ... (پیوسته و بدون گپ)
38
-
39
- ### **2. ثبات شناسه‌ها در متن:**
40
- - اگر "همراه اول" اول‌بار company-01 شد، در تمام متن همان باشد
41
- - اگر "مهدی احمدی" اول‌بار person-01 شد، در تمام متن همان باشد
42
-
43
- ### **3. تشخیص صحیح انواع:**
44
- **شرکت/سازمان:** همراه اول، بانک ملی، ایران‌خودرو، سایپا، بانک مرکزی، سامانه کدال، وزارت نفت
45
- **شخص:** مهدی اخوان بهابادی، محمدرضا فرزین، ابوالفضل نجارزاده
46
- **عدد:** 37، 70، 677، 73.7، 178 (هر عددی)
47
- **درصد:** 37 درصدی، 15 درصدی، 53 درصد، 43%
48
-
49
- ## **انواع موجودیت‌ها:**
50
-
51
- **company-XX:** نام شرکت‌ها، سازمان‌ها، بانک‌ها، هلدینگ‌ها، گروه‌های مالی (مثال: ایران خودرو، بانک ملی، گروه مالی صبا، سازمان حسابرسی)
52
-
53
- **person-XX:** نام و نام خانوادگی اشخاص (مثال: محمد رضایی، مهدی اخوان بهابادی، فرج‌اله قدمی)
54
-
55
- **amount-XX:** مبالغ مالی شامل ریال، تومان، همت، دلار، تن، دستگاه و واحدهای اندازه‌گیری (مثال: ۲۳ هزار و ۲۹۶ میلیارد تومان، ۵۰۰ میلیون دلار، ۷۳.۷ میلیون نفر، 636 ریال)
56
-
57
- **percent-XX:** درصدها و نسبت‌ها (مثال: ۴.۵۸ درصد، ۷۵ درصد، ۱۴٪، منفی 345 درصد)
58
-
59
- ## **قوانین کلیدی:**
60
-
61
- 1. **ترتیب شماره‌گذاری:** اولین باری که موجودیت ظاهر می‌شود، شماره می‌گیرد (01، 02، 03، ...)
62
-
63
- 2. **حفظ هویت یکسان:** اگر همان موجودیت دوباره آمد، از همان شماره استفاده کن. مثلا "ایران خودرو" در جمله اول و "این شرکت" در جمله دوم هر دو company-01 هستند.
64
-
65
- 3. **تشخیص نام‌های مختلف:** "فولاد مبارکه اصفهان" و "فولاد مبارکه" و "این شرکت" همه company-01 هستند. "همراه اول" و "گروه همراه اول" و "این اپراتور" همه company-01 هستند. اما "بانک پاسارگاد" و "سرزمین هوشمند پاد" دو company مختلف هستند.
66
-
67
- 4. **مبالغ و درصدهای مختلف:** هر عدد جدید، شماره جدید می‌گیرد
68
-
69
- 5. **حفظ ساختار:** ساختار جمله را حفظ کن، تاریخ‌ها را تغییر نده، کلمات توصیفی مثل "شرکت"، "بانک"، "گروه" را قبل از برچسب حفظ کن
70
-
71
- 6. **هیچ توضیح اضافه‌ای نده:** فقط متن ناشناس‌شده را برگردان، بدون توضیح یا تفسیر
72
-
73
- ## **تشخیص دقیق درصدها:**
74
- - "37 درصدی" → percent-01 (نه amount)
75
- - "15 درصد" → percent-02 (نه amount)
76
- - "53%" → percent-03 (نه amount)
77
- - "بازه 10 تا 20 درصد" → percent-04 تا percent-05
78
-
79
- ## **موارد حفظ شده:**
80
- - تاریخ‌ها: 1404/04/23، 30 آذر 1403، پاییز 1401
81
- - عناوین شغلی: مدیرعامل، رئیس کل، مدیرکل
82
- - واحدها: میلیارد تومان، همت، ریال، ماه، سال
83
- - مکان‌ها: تهران، اصفهان، ایران
84
-
85
- ## **ممنوع:**
86
- - کلمات انگلیسی اضافی
87
- - تغییر ساختار جمله
88
- - حذف یا اضافه کردن کلمات
89
-
90
- ## **نمونه‌های آموزشی:**
91
-
92
- **نمونه ۱:**
93
- ورودی: مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است. سود خالص 7101 میلیارد تومان و تلفیقی گروه همراه اول 8003 میلیارد تومان شد.
94
- خروجی: person-01، مدیرعامل company-01، اعلام کرد درآمد عملیاتی شرکت با رشد percent-01 به amount-01 رسیده است. سود خالص amount-02 و تلفیقی گروه company-01 amount-03 شد.
95
-
96
- **نمونه ۲:**
97
- ورودی: بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند.
98
- خروجی: company-01 و company-02 با همکاری person-01، percent-01 سپرده‌ها را مدیریت کردند.
99
-
100
- **نمونه ۳:**
101
- ورودی: سایپا و ایران‌خودرو مجموع زیان 620 همت داشتند و سایپا 269 هزار میلیارد زیان اعلام کرد.
102
- خروجی: company-01 و company-02 مجموع زیان amount-01 داشتند و company-01 amount-02 زیان اعلام کرد.
103
-
104
- **نمونه ۴:**
105
- ورودی: ایران خودرو در اسفندماه سال 1402 حدود 23 هزار و 296 میلیارد تومان درآمد کسب کرد که در مقایسه با بهمن 4.58 درصد افزایش داشت. زیان خالص ایران خودرو در این سال به بیش از 37 همت رساند.
106
- خروجی: company-01 در اسفندماه سال 1402 حدود amount-01 درآمد کسب کرد که در مقایسه با بهمن percent-01 افزایش داشت. زیان خالص company-01 در این سال به بیش از amount-02 رساند.
107
-
108
- **نمونه ۵:**
109
- ورودی: بانک پاسارگاد با شناسایی سود خالص 155 هزار میلیارد ریالی در رده دوم سودآورترین بانک‌های کشور قرار گرفت. در مقابل، بانک سرمایه با مدیرعاملی فرج‌اله قدمی وضعیت بحرانی دارد.
110
- خروجی: company-01 با شناسایی سود خالص amount-01 در رده دوم سودآورترین بانک‌های کشور قرار گرفت. در مقابل، company-02 با مدیرعاملی person-01 وضعیت بحرانی دارد.
111
-
112
- **نمونه ۶:**
113
- ورودی: مجمع عمومی عادی سالیانه شرکت پتروشیمی پارس با نماد «پارس» راس ساعت 10:00 روز سه شنبه مورخ 1403/04/12 برگزار شد و سازمان حسابرسی به‌ عنوان بازرس قانونی انتخاب شد.
114
- خروجی: مجمع عمومی عادی سالیانه company-01 با نماد «پارس» راس ساعت 10:00 روز سه شنبه مورخ 1403/04/12 برگزار شد و company-02 به‌ عنوان بازرس قانونی انتخاب شد.
115
-
116
- **نمونه ۷:**
117
- ورودی: شرکت فولاد مبارکه اصفهان در مجمع عمومی عادی سالیانه حاشیه سود خالص شرکت 38 درصد بوده و EPS این شرکت از 636 ریال به 614 ریال خواهد رسید.
118
- خروجی: company-01 در مجمع عمومی عادی سالیانه حاشیه سود خالص شرکت percent-01 بوده و EPS این شرکت از amount-01 به amount-02 خواهد رسید.
119
-
120
- **نمونه ۸:**
121
- ورودی: بانک سرمایه با مدیرعاملی فرج‌اله قدمی زیان خالص 2700 میلیارد تومانی در سه‌ماهه نخست 1404 گزارش کرد. نسبت کفایت سرمایه به منفی 345 درصد رسیده و زیان انباشته نزدیک به 67 هزار میلیارد تومان است.
122
- خروجی: company-01 با مدیرعاملی person-01 زیان خالص amount-01 در سه‌ماهه نخست 1404 گزارش کرد. نسبت کفایت سرمایه به percent-01 رسیده و زیان انباشته نزدیک به amount-02 است.
123
-
124
- **نمونه ۹:**
125
- ورودی: دو بانک ملت و پاسارگاد به ترتیب با شناسایی سود خالص 157 و 155 هزار میلیارد ریالی رقابت تنگاتنگی داشته و در رده‌های اول و دوم جای دارند.
126
- خروجی: دو بانک company-01 و company-02 به ترتیب با شناسایی سود خالص amount-01 و amount-02 رقابت تنگاتنگی داشته و در رده‌های اول و دوم جای دارند.
127
-
128
- **نمونه ۱۰:**
129
- ورودی: مرور صورت‌های مالی بانک‌ها نشان می‌دهد سهم سودهای ارزی به‌راحتی به 40–60٪ رسیده است و این مسئله نشان‌دهنده وضعیت غیرعادی بازار است.
130
- خروجی: مرور صورت‌های مالی بانک‌ها نشان می‌دهد سهم سودهای ارزی به‌راحتی به percent-01 رسیده است و این مسئله نشان‌دهنده وضعیت غیرعادی بازار است.
131
-
132
- **فقط متن ناشناس‌شده را برگردان - هیچ توضیح اضافی نیاز نیست."""
133
-
134
- def get_user_prompt(self, text: str) -> str:
135
- """تشکیل پرامپت کاربر"""
136
- return f"""متن مالی فارسی زیر را تجزیه و تحلیل کنید. تمام موجودیت‌های حساس را شناسایی کنید و یک JSON Array برگردانید.
137
-
138
- متن:
139
- {text}
140
-
141
- **مهم**:
142
- - اگر چند بار یک نام تکرار شود، یک id بدهید
143
- - کلمات عمومی را حفظ کنید
144
- - واحدها را حفظ کنید
145
- - فقط JSON برگردانید!
146
-
147
- یک JSON Array برگردانید. هر عنصر دارای:
148
- - "text": متن دقیق استخراج شده
149
- - "type": نوع (company, person, amount, percent, phone, email, date, location, id_number)
150
- - "original": توضیح اضافی اگر نام مستعار باشد"""
151
-
152
- def call_cerebras(self, text: str) -> List[Dict]:
153
- """فراخوانی Cerebras API با پرامپت بهبود شده"""
154
- logger.info("🔄 فراخوانی Cerebras API با دستورالعمل قوی...")
155
-
156
- system_prompt = self.get_system_prompt()
157
- user_prompt = self.get_user_prompt(text)
158
-
159
- try:
160
- response = requests.post(
161
- "https://api.cerebras.ai/v1/chat/completions",
162
- headers={
163
- "Authorization": f"Bearer {self.api_key}",
164
- "Content-Type": "application/json"
165
- },
166
- json={
167
- "model": "llama-3.3-70b",
168
- "messages": [
169
- {"role": "system", "content": system_prompt},
170
- {"role": "user", "content": user_prompt}
171
- ],
172
- "max_tokens": 4000,
173
- "temperature": 0.1
174
- },
175
- timeout=30
176
- )
177
-
178
- if response.status_code != 200:
179
- logger.error(f"❌ خطای API Cerebras: {response.text}")
180
- return []
181
-
182
- result = response.json()
183
- content = result['choices'][0]['message']['content']
184
-
185
- try:
186
- # تمیز کردن محتوا از markdown اگر وجود داشته باشد
187
- content = content.replace("```json", "").replace("```", "").strip()
188
- entities = json.loads(content)
189
- if not isinstance(entities, list):
190
- entities = []
191
- logger.info(f"✅ {len(entities)} موجودیت استخراج شد")
192
- return entities
193
- except json.JSONDecodeError:
194
- logger.error(f"❌ خطا در JSON parsing: {content[:200]}")
195
- return []
196
-
197
- except Exception as e:
198
- logger.error(f"❌ خطا Cerebras: {e}")
199
- return []
200
-
201
- def get_placeholder(self, entity_type: str) -> str:
202
- """تولید placeholder با format جدید"""
203
- type_lower = entity_type.lower()
204
- if type_lower not in self.counters:
205
- type_lower = 'amount'
206
-
207
- self.counters[type_lower] += 1
208
- return f"{type_lower}-{self.counters[type_lower]:02d}"
209
-
210
- def anonymize(self, text: str) -> Tuple[str, List]:
211
- """ناشناس‌سازی متن با قوانین ثبات"""
212
- logger.info("🚀 شروع ناشناس‌سازی متن...")
213
-
214
- # تنظیف
215
- self.mapping_table = {}
216
- self.seen_entities = {}
217
- for key in self.counters:
218
- self.counters[key] = 0
219
-
220
- # دریافت موجودیت‌ها
221
- entities = self.call_cerebras(text)
222
-
223
- if not entities:
224
- logger.warning("⚠️ موجودیتی شناسایی نشد")
225
- return text, []
226
-
227
- logger.info("🔄 Processing entities...")
228
-
229
- # جایگزینی با قانون ثبات
230
- anonymized = text
231
- replacements = []
232
-
233
- for entity in entities:
234
- entity_type = entity.get('type', 'amount').lower()
235
- entity_text = entity.get('text', '').strip()
236
- original_info = entity.get('original', '')
237
-
238
- if not entity_text:
239
- continue
240
-
241
- # بررسی اگر این موجودیت قبلاً دیده شده است
242
- entity_key = (entity_type, entity_text.lower())
243
-
244
- if entity_key in self.seen_entities:
245
- token = self.seen_entities[entity_key]
246
- logger.info(f"🔄 موجودیت تکراری: {entity_text} → {token}")
247
- else:
248
- token = self.get_placeholder(entity_type)
249
- self.seen_entities[entity_key] = token
250
- self.mapping_table[token] = {
251
- 'original': entity_text,
252
- 'type': entity_type,
253
- 'note': original_info
254
- }
255
- logger.info(f"✅ جایگزینی: {entity_text} → {token}")
256
-
257
- # جایگزینی دقیق (case-sensitive اول، سپس case-insensitive)
258
- idx = anonymized.find(entity_text)
259
- if idx != -1:
260
- anonymized = anonymized[:idx] + token + anonymized[idx + len(entity_text):]
261
- replacements.append({
262
- 'original': entity_text,
263
- 'placeholder': token,
264
- 'type': entity_type
265
- })
266
-
267
- logger.info(f"✅ ناشناس‌سازی کامل - {len(self.mapping_table)} نگاشت")
268
- return anonymized, entities
269
-
270
- def get_mapping_table_str(self) -> str:
271
- """جدول نگاشت جزئی"""
272
- if not self.mapping_table:
273
- return "❌ موجودیتی شناسایی نشد"
274
-
275
- result = "## 📊 جدول نگاشت\n\n"
276
- result += "| توکن | اطلاعات اصلی | نوع |\n"
277
- result += "|------|--------|------|\n"
278
-
279
- for token, info in sorted(self.mapping_table.items()):
280
- entity_type = info.get('type', 'unknown')
281
- original = info.get('original', '')
282
- note = info.get('note', '')
283
-
284
- note_str = f" ({note})" if note else ""
285
- result += f"| `{token}` | {original}{note_str} | {entity_type} |\n"
286
-
287
- return result
288
-
289
- def restore(self, text: str) -> str:
290
- """بازگردانی اطلاعات اصلی"""
291
- logger.info("🔄 بازگردانی اطلاعات...")
292
- restored = text
293
- for token, info in self.mapping_table.items():
294
- original = info.get('original', '')
295
- restored = restored.replace(token, original)
296
- logger.info("✅ بازگردانی کامل")
297
- return restored
298
-
299
-
300
- # متغیرهای global
301
- anonymizer = None
302
-
303
- def process(input_text: str) -> Tuple[str, str, str, str, str]:
304
- """
305
- روند کامل:
306
- 1. ناشناس‌سازی با Cerebras (llama-3.3-70b) + پرامپت قوی
307
- 2. ارسال به ChatGPT (حتما!)
308
- 3. بازگردانی پاسخ ChatGPT
309
- """
310
- global anonymizer
311
-
312
- try:
313
- if not input_text.strip():
314
- return "", "", "", "", ""
315
-
316
- # دریافت API Keys
317
- api_key_cerebras = os.getenv("CEREBRAS_API_KEY")
318
- api_key_gpt = os.getenv("OPENAI_API_KEY")
319
-
320
- if not api_key_gpt:
321
- logger.error("❌ OPENAI_API_KEY یافت نشد")
322
- return "", "", "", "", ""
323
-
324
- if not api_key_cerebras:
325
- logger.error("❌ CEREBRAS_API_KEY یافت نشد")
326
- return "", "", "", "", ""
327
-
328
- # ============================================
329
- # مرحله 1: مقداردهی
330
- # ============================================
331
- if not anonymizer:
332
- logger.info("Initializing anonymizer...")
333
- anonymizer = AnonymizerCerebrasEnhanced()
334
-
335
- # ============================================
336
- # مرحله 2: ناشناس‌سازی با پرامپت قوی
337
- # ============================================
338
- logger.info("Step 1: Anonymizing text with Cerebras...")
339
-
340
- anonymized_text, entities = anonymizer.anonymize(input_text)
341
-
342
- if not entities:
343
- logger.warning("⚠️ موجودیتی شناسایی نشد - متن ناشناس نشد")
344
- return input_text, "", "", "", ""
345
-
346
- # ============================================
347
- # مرحله 3: جدول نگاشت
348
- # ============================================
349
- logger.info("Step 2: Creating mapping table")
350
- mapping = anonymizer.get_mapping_table_str()
351
- logger.info(f"📋 {len(anonymizer.mapping_table)} نگاشت ایجاد شد")
352
-
353
- # ============================================
354
- # مرحله 4: ارسال به ChatGPT (حتما!)
355
- # ============================================
356
- logger.info("Step 3: Sending to ChatGPT...")
357
-
358
- prompt = f"""متن ناشناس‌شده زیر (متن مالی) را تحلیل و خلاصه کنید.
359
-
360
- متن:
361
- {anonymized_text}
362
-
363
- لطفاً:
364
- 1. خلاصه‌ای مختصر و معنادار ارائه دهید
365
- 2. نکات اصلی را مشخص کنید
366
- 3. تمام توکن‌های ناشناس (مثل company-01، amount-02) را حفظ کنید
367
- 4. تنها اطلاعات موجود در متن را بیان کنید"""
368
-
369
- logger.info(f"📤 ارسال به ChatGPT (gpt-4o-mini)...")
370
-
371
- try:
372
- gpt_response_obj = requests.post(
373
- "https://api.openai.com/v1/chat/completions",
374
- headers={"Authorization": f"Bearer {api_key_gpt}"},
375
- json={
376
- "model": "gpt-4o-mini",
377
- "messages": [
378
- {
379
- "role": "system",
380
- "content": "شما دستیار تحلیل متون مالی فارسی هستید. متن‌های ناشناس‌شده را دقیق تحلیل کنید. تمام توکن‌های ناشناس را حفظ کنید."
381
- },
382
- {"role": "user", "content": prompt}
383
- ],
384
- "max_tokens": 1500,
385
- "temperature": 0.7
386
- },
387
- timeout=30
388
- )
389
-
390
- if gpt_response_obj.status_code == 200:
391
- gpt_response = gpt_response_obj.json()['choices'][0]['message']['content']
392
- logger.info("✅ پاسخ دریافت شد")
393
- else:
394
- error_text = gpt_response_obj.json().get('error', {}).get('message', gpt_response_obj.text)
395
- logger.error(f"❌ خطای ChatGPT: {error_text}")
396
- return input_text, anonymized_text, "", "", mapping
397
-
398
- except Exception as e:
399
- logger.error(f"❌ خطا در ارسال به ChatGPT: {e}")
400
- return input_text, anonymized_text, "", "", mapping
401
-
402
- # ============================================
403
- # مرحله 5: بازگردانی پاسخ ChatGPT
404
- # ============================================
405
- logger.info("Step 4: Restoring original text...")
406
-
407
- restored_text = anonymizer.restore(gpt_response)
408
-
409
- logger.info(f"✅ بازگردانی کامل")
410
-
411
- logger.info(f"Done. Input: {len(input_text)} | Anonymized: {len(anonymized_text)} | Entities: {len(entities)}")
412
-
413
- return input_text, anonymized_text, gpt_response, restored_text, mapping
414
-
415
- except Exception as e:
416
- logger.error(f"❌ خطا عمومی: {e}", exc_info=True)
417
- return "", "", "", "", ""
418
-
419
- def clear():
420
- """پاک کردن"""
421
- return "", "", "", "", ""
422
-
423
- # رابط Gradio
424
- with gr.Blocks(title="Text Anonymization", theme=gr.themes.Soft()) as app:
425
-
426
- with gr.Row():
427
- with gr.Column(scale=2):
428
- input_text = gr.Textbox(
429
- lines=12,
430
- placeholder="متن را وارد کنید...",
431
- label="Input"
432
- )
433
-
434
- with gr.Column(scale=1):
435
- process_btn = gr.Button("Process", variant="primary", size="lg")
436
- clear_btn = gr.Button("Clear", variant="stop")
437
-
438
- with gr.Row():
439
- with gr.Column():
440
- anonymized_text = gr.Textbox(
441
- lines=10,
442
- label="Anonymized",
443
- interactive=False
444
- )
445
-
446
- with gr.Column():
447
- gpt_response = gr.Textbox(
448
- lines=10,
449
- label="GPT Response",
450
- interactive=False
451
- )
452
-
453
- with gr.Column():
454
- restored_text = gr.Textbox(
455
- lines=10,
456
- label="Restored",
457
- interactive=False
458
- )
459
-
460
- with gr.Row():
461
- with gr.Column():
462
- mapping = gr.Textbox(
463
- lines=10,
464
- label="Mapping",
465
- interactive=False
466
- )
467
-
468
- # Event handlers
469
- process_btn.click(
470
- fn=process,
471
- inputs=[input_text],
472
- outputs=[input_text, anonymized_text, gpt_response, restored_text, mapping]
473
- )
474
-
475
- clear_btn.click(
476
- fn=clear,
477
- outputs=[input_text, anonymized_text, gpt_response, restored_text, mapping]
478
- )
479
-
480
- if __name__ == "__main__":
481
- print("Starting Text Anonymization System...")
482
- app.launch(
483
- server_name="0.0.0.0",
484
- server_port=7860,
485
- share=False,
486
- show_error=True
487
- )