leilaghomashchi commited on
Commit
1e96a08
·
verified ·
1 Parent(s): 578a618

Delete app.py

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