leilaghomashchi commited on
Commit
0f8a858
·
verified ·
1 Parent(s): 24d3f3e

Upload app_enhanced (1).py

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