leilaghomashchi commited on
Commit
24d3f3e
·
verified ·
1 Parent(s): 7f81bf6

Delete app.py

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