leilaghomashchi commited on
Commit
658dd36
·
verified ·
1 Parent(s): 373cf25

Upload app_multi_model_final (11).py

Browse files
Files changed (1) hide show
  1. app_multi_model_final (11).py +720 -0
app_multi_model_final (11).py ADDED
@@ -0,0 +1,720 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import re
3
+ import os
4
+ import requests
5
+ import json
6
+ import logging
7
+ import time
8
+ from typing import Dict, List, Tuple, Optional
9
+ from chatgpt_sender import ChatGPTSender
10
+
11
+ logging.basicConfig(level=logging.INFO)
12
+ logger = logging.getLogger(__name__)
13
+
14
+ class MultiModelSender:
15
+ """کلاس واحد برای ارسال به مدل‌های مختلف با retry mechanism"""
16
+
17
+ def __init__(self):
18
+ # دریافت API Keys از environment و trim کردن آنها
19
+ self.openai_key = os.getenv("OPENAI_API_KEY", "").strip() if os.getenv("OPENAI_API_KEY") else None
20
+ self.grok_key = os.getenv("XAI_API_KEY", "").strip() if os.getenv("XAI_API_KEY") else None
21
+ self.gemini_key = os.getenv("GEMINI_API_KEY", "").strip() if os.getenv("GEMINI_API_KEY") else None
22
+ self.deepseek_key = os.getenv("DEEPSEEK_API_KEY", "").strip() if os.getenv("DEEPSEEK_API_KEY") else None
23
+
24
+ # ایجاد instance از ChatGPTSender
25
+ self.chatgpt_sender = ChatGPTSender(api_key=self.openai_key, model="gpt-4o-mini")
26
+
27
+ logger.info("✅ MultiModelSender مقداردهی شد")
28
+
29
+ def send_to_chatgpt(self, text: str, system_msg: Optional[str] = None) -> str:
30
+ """
31
+ ارسال به ChatGPT با استفاده از ماژول پیشرفته
32
+ """
33
+ try:
34
+ if system_msg:
35
+ return self.chatgpt_sender.send(
36
+ text=text,
37
+ system_msg=system_msg,
38
+ max_tokens=4096,
39
+ temperature=0.1,
40
+ lang='fa',
41
+ retry_count=3
42
+ )
43
+ else:
44
+ return self.chatgpt_sender.send_simple(text, lang='fa')
45
+ except Exception as e:
46
+ logger.error(f"❌ ChatGPT Error: {e}")
47
+ return f"❌ خطا در ChatGPT: {str(e)}"
48
+
49
+ def send_to_grok(self, text: str, system_msg: Optional[str] = None, retry_count: int = 3) -> str:
50
+ """ارسال به Grok (xAI) با retry mechanism"""
51
+ if not self.grok_key:
52
+ return "❌ XAI_API_KEY (Grok) موجود نیست"
53
+
54
+ messages = []
55
+ if system_msg:
56
+ messages.append({"role": "system", "content": system_msg})
57
+ messages.append({"role": "user", "content": text})
58
+
59
+ for attempt in range(retry_count):
60
+ try:
61
+ logger.info(f"📤 ارسال به Grok (تلاش {attempt + 1}/{retry_count})...")
62
+
63
+ response = requests.post(
64
+ "https://api.x.ai/v1/chat/completions",
65
+ headers={
66
+ "Authorization": f"Bearer {self.grok_key}",
67
+ "Content-Type": "application/json"
68
+ },
69
+ json={
70
+ "model": "grok-beta",
71
+ "messages": messages,
72
+ "temperature": 0.1,
73
+ "max_tokens": 4096
74
+ },
75
+ timeout=60
76
+ )
77
+
78
+ if response.status_code == 200:
79
+ logger.info("✅ Grok: پاسخ دریافت شد")
80
+ return response.json()['choices'][0]['message']['content'].strip()
81
+
82
+ elif response.status_code == 429:
83
+ wait_time = 5 * (attempt + 1)
84
+ logger.warning(f"⚠️ Grok Rate limit | صبر: {wait_time} ثانیه")
85
+ if attempt < retry_count - 1:
86
+ time.sleep(wait_time)
87
+ continue
88
+ else:
89
+ return "❌ سهمیه Grok تمام شده است"
90
+
91
+ elif response.status_code == 401:
92
+ return "❌ کلید API Grok نامعتبر است!"
93
+
94
+ elif response.status_code in [502, 503, 504]:
95
+ wait_time = 2 * (attempt + 1)
96
+ logger.warning(f"⚠️ Grok Server error | صبر: {wait_time} ثانیه")
97
+ if attempt < retry_count - 1:
98
+ time.sleep(wait_time)
99
+ continue
100
+ else:
101
+ return f"❌ خطای سرور Grok: {response.status_code}"
102
+
103
+ else:
104
+ error_msg = response.text
105
+ logger.error(f"❌ Grok API Error: {error_msg}")
106
+ return f"❌ Grok API Error: {response.status_code}"
107
+
108
+ except requests.exceptions.Timeout:
109
+ logger.warning("⚠️ Grok Timeout | تلاش مجدد...")
110
+ if attempt < retry_count - 1:
111
+ time.sleep(3)
112
+ continue
113
+ else:
114
+ return "❌ خطای اتصال به Grok: timeout"
115
+
116
+ except Exception as e:
117
+ logger.error(f"❌ Grok Exception: {e}")
118
+ if attempt < retry_count - 1:
119
+ time.sleep(2)
120
+ continue
121
+ else:
122
+ return f"❌ خطا در Grok: {str(e)}"
123
+
124
+ return "❌ خطای ناشناخته در Grok"
125
+
126
+ def send_to_gemini(self, text: str, system_msg: Optional[str] = None, retry_count: int = 3) -> str:
127
+ """ارسال به Google Gemini با retry mechanism"""
128
+ if not self.gemini_key:
129
+ return "❌ GEMINI_API_KEY موجود نیست"
130
+
131
+ # ترکیب system message و text
132
+ full_prompt = f"{system_msg}\n\n{text}" if system_msg else text
133
+
134
+ for attempt in range(retry_count):
135
+ try:
136
+ logger.info(f"📤 ارسال به Gemini (تلاش {attempt + 1}/{retry_count})...")
137
+
138
+ response = requests.post(
139
+ f"https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key={self.gemini_key}",
140
+ headers={
141
+ "Content-Type": "application/json"
142
+ },
143
+ json={
144
+ "contents": [{
145
+ "parts": [{"text": full_prompt}]
146
+ }],
147
+ "generationConfig": {
148
+ "temperature": 0.1,
149
+ "maxOutputTokens": 4096
150
+ }
151
+ },
152
+ timeout=60
153
+ )
154
+
155
+ if response.status_code == 200:
156
+ result = response.json()
157
+ logger.info("✅ Gemini: پاسخ دریافت شد")
158
+ return result['candidates'][0]['content']['parts'][0]['text'].strip()
159
+
160
+ elif response.status_code == 429:
161
+ wait_time = 5 * (attempt + 1)
162
+ logger.warning(f"⚠️ Gemini Rate limit | صبر: {wait_time} ثانیه")
163
+ if attempt < retry_count - 1:
164
+ time.sleep(wait_time)
165
+ continue
166
+ else:
167
+ return "❌ سهمیه Gemini تمام شده است"
168
+
169
+ elif response.status_code == 400:
170
+ error_data = response.json()
171
+ error_msg = error_data.get('error', {}).get('message', 'خطای نامعتبر')
172
+ return f"❌ خطای Gemini: {error_msg}"
173
+
174
+ elif response.status_code in [502, 503, 504]:
175
+ wait_time = 2 * (attempt + 1)
176
+ logger.warning(f"⚠️ Gemini Server error | صبر: {wait_time} ثانیه")
177
+ if attempt < retry_count - 1:
178
+ time.sleep(wait_time)
179
+ continue
180
+ else:
181
+ return f"❌ خطای سرور Gemini: {response.status_code}"
182
+
183
+ else:
184
+ error_msg = response.text
185
+ logger.error(f"❌ Gemini API Error: {error_msg}")
186
+ return f"❌ Gemini API Error: {response.status_code}"
187
+
188
+ except requests.exceptions.Timeout:
189
+ logger.warning("⚠️ Gemini Timeout | تلاش مجدد...")
190
+ if attempt < retry_count - 1:
191
+ time.sleep(3)
192
+ continue
193
+ else:
194
+ return "❌ خطای اتصال به Gemini: timeout"
195
+
196
+ except Exception as e:
197
+ logger.error(f"❌ Gemini Exception: {e}")
198
+ if attempt < retry_count - 1:
199
+ time.sleep(2)
200
+ continue
201
+ else:
202
+ return f"❌ خطا در Gemini: {str(e)}"
203
+
204
+ return "❌ خطای ناشناخته در Gemini"
205
+
206
+ def send_to_deepseek(self, text: str, system_msg: Optional[str] = None, retry_count: int = 3) -> str:
207
+ """ارسال به DeepSeek با retry mechanism"""
208
+ if not self.deepseek_key:
209
+ return "❌ DEEPSEEK_API_KEY موجود نیست"
210
+
211
+ messages = []
212
+ if system_msg:
213
+ messages.append({"role": "system", "content": system_msg})
214
+ messages.append({"role": "user", "content": text})
215
+
216
+ for attempt in range(retry_count):
217
+ try:
218
+ logger.info(f"📤 ارسال به DeepSeek (تلاش {attempt + 1}/{retry_count})...")
219
+
220
+ response = requests.post(
221
+ "https://api.deepseek.com/v1/chat/completions",
222
+ headers={
223
+ "Authorization": f"Bearer {self.deepseek_key}",
224
+ "Content-Type": "application/json"
225
+ },
226
+ json={
227
+ "model": "deepseek-chat",
228
+ "messages": messages,
229
+ "temperature": 0.1,
230
+ "max_tokens": 4096
231
+ },
232
+ timeout=60
233
+ )
234
+
235
+ if response.status_code == 200:
236
+ logger.info("✅ DeepSeek: پاسخ دریافت شد")
237
+ return response.json()['choices'][0]['message']['content'].strip()
238
+
239
+ elif response.status_code == 429:
240
+ wait_time = 5 * (attempt + 1)
241
+ logger.warning(f"⚠️ DeepSeek Rate limit | صبر: {wait_time} ثانیه")
242
+ if attempt < retry_count - 1:
243
+ time.sleep(wait_time)
244
+ continue
245
+ else:
246
+ return "❌ سهمیه DeepSeek تمام شده است"
247
+
248
+ elif response.status_code == 401:
249
+ return "❌ کلید API DeepSeek نامعتبر است!"
250
+
251
+ elif response.status_code in [502, 503, 504]:
252
+ wait_time = 2 * (attempt + 1)
253
+ logger.warning(f"⚠️ DeepSeek Server error | صبر: {wait_time} ثانیه")
254
+ if attempt < retry_count - 1:
255
+ time.sleep(wait_time)
256
+ continue
257
+ else:
258
+ return f"❌ خطای سرور DeepSeek: {response.status_code}"
259
+
260
+ else:
261
+ error_msg = response.text
262
+ logger.error(f"❌ DeepSeek API Error: {error_msg}")
263
+ return f"❌ DeepSeek API Error: {response.status_code}"
264
+
265
+ except requests.exceptions.Timeout:
266
+ logger.warning("⚠️ DeepSeek Timeout | تلاش مجدد...")
267
+ if attempt < retry_count - 1:
268
+ time.sleep(3)
269
+ continue
270
+ else:
271
+ return "❌ خطای اتصال به DeepSeek: timeout"
272
+
273
+ except Exception as e:
274
+ logger.error(f"❌ DeepSeek Exception: {e}")
275
+ if attempt < retry_count - 1:
276
+ time.sleep(2)
277
+ continue
278
+ else:
279
+ return f"❌ خطا در DeepSeek: {str(e)}"
280
+
281
+ return "❌ خطای ناشناخته در DeepSeek"
282
+
283
+ def send_message(self, text: str, model_name: str, system_msg: Optional[str] = None) -> str:
284
+ """
285
+ ارسال پیام به مدل انتخاب شده
286
+
287
+ Args:
288
+ text: متن ارسالی
289
+ model_name: نام مدل
290
+ system_msg: پیام سیستم (اختیاری)
291
+
292
+ Returns:
293
+ پاسخ مدل
294
+ """
295
+ logger.info(f"🤖 ارسال به {model_name}...")
296
+
297
+ if model_name == "ChatGPT-4o-mini":
298
+ return self.send_to_chatgpt(text, system_msg)
299
+ elif model_name == "Grok":
300
+ return self.send_to_grok(text, system_msg)
301
+ elif model_name == "Gemini":
302
+ return self.send_to_gemini(text, system_msg)
303
+ elif model_name == "DeepSeek":
304
+ return self.send_to_deepseek(text, system_msg)
305
+ else:
306
+ return f"❌ مدل نامعتبر: {model_name}"
307
+
308
+
309
+ class AnonymizerAdvanced:
310
+ """ناشناس‌ساز پیشرفته با Cerebras"""
311
+
312
+ def __init__(self, cerebras_key: str = None):
313
+ self.cerebras_key = cerebras_key or os.getenv("CEREBRAS_API_KEY")
314
+ self.mapping_table = {}
315
+ self.reverse_mapping = {}
316
+
317
+ # ایجاد instance از MultiModelSender
318
+ self.model_sender = MultiModelSender()
319
+
320
+ logger.info("✅ Anonymizer Advanced مقداردهی شد")
321
+
322
+ def anonymize_with_cerebras(self, text: str) -> Tuple[str, Dict]:
323
+ """ناشناس‌سازی با Cerebras - دریافت mapping از مدل"""
324
+ logger.info("🧠 روش Cerebras...")
325
+
326
+ if not self.cerebras_key:
327
+ logger.error("❌ Cerebras API Key موجود نیست")
328
+ raise ValueError("Cerebras API Key مورد نیاز است")
329
+
330
+ try:
331
+ # مرحله 1: ناشناس‌سازی متن
332
+ prompt1 = f"""متن زیر را ناشناس کنید. قوانین:
333
+ 1. اسامی اشخاص → person-01, person-02, ...
334
+ 2. نام شرکت‌ها/سازمان‌ها → company-01, company-02, ...
335
+ 3. مقادیر پولی → amount-01, amount-02, ...
336
+ 4. درصدها → percent-01, percent-02, ...
337
+ 5. فقط این توکن‌ها استفاده کنید
338
+ 6. شماره‌های نسخه را درست حفظ کنید
339
+ 7. اگر موجودیت تکرار شود از شماره قدیمی استفاده کنید
340
+
341
+ متن:
342
+ {text}
343
+
344
+ خروجی: فقط متن ناشناس شده"""
345
+
346
+ response1 = requests.post(
347
+ "https://api.cerebras.ai/v1/chat/completions",
348
+ headers={
349
+ "Authorization": f"Bearer {self.cerebras_key}",
350
+ "Content-Type": "application/json"
351
+ },
352
+ json={
353
+ "model": "llama-3.3-70b",
354
+ "messages": [{"role": "user", "content": prompt1}],
355
+ "max_tokens": 4096,
356
+ "temperature": 0.1
357
+ },
358
+ timeout=60
359
+ )
360
+
361
+ if response1.status_code != 200:
362
+ logger.error(f"❌ Cerebras Error: {response1.status_code}")
363
+ raise Exception(f"Cerebras API Error: {response1.status_code}")
364
+
365
+ anonymized_text = response1.json()['choices'][0]['message']['content'].strip()
366
+ logger.info("✅ Cerebras: ناشناس‌سازی موفق")
367
+
368
+ # مرحله 2: استخراج mapping از مدل
369
+ prompt2 = f"""متن اصلی:
370
+ {text}
371
+
372
+ متن ناشناس شده:
373
+ {anonymized_text}
374
+
375
+ لطفاً یک جدول mapping برای همه توکن‌های ناشناس ایجاد کن.
376
+ برای هر توکن، متن اصلی کامل آن را مشخص کن.
377
+
378
+ **مهم:**
379
+ - برای person-XX: نام کامل شخص (مثلاً "علی احمدی")
380
+ - برای company-XX: نام کامل شرکت/سازمان (مثلاً "شرکت پتروشیمی")
381
+ - برای amount-XX: عدد + واحد (مثلاً "80 هزار تومان" یا "50 میلیارد ریال")
382
+ - برای percent-XX: عدد + کلمه "درصد" (مثلاً "40 درصد" نه فقط "40")
383
+
384
+ خروجی را به این فرمت JSON بده (فقط JSON، بدون توضیح اضافی):
385
+ {{
386
+ "person-01": "متن اصلی کامل",
387
+ "company-01": "متن اصلی کامل",
388
+ "amount-01": "متن اصلی کامل با واحد",
389
+ "percent-01": "عدد + درصد",
390
+ ...
391
+ }}"""
392
+
393
+ response2 = requests.post(
394
+ "https://api.cerebras.ai/v1/chat/completions",
395
+ headers={
396
+ "Authorization": f"Bearer {self.cerebras_key}",
397
+ "Content-Type": "application/json"
398
+ },
399
+ json={
400
+ "model": "llama-3.3-70b",
401
+ "messages": [{"role": "user", "content": prompt2}],
402
+ "max_tokens": 2048,
403
+ "temperature": 0.1
404
+ },
405
+ timeout=60
406
+ )
407
+
408
+ if response2.status_code == 200:
409
+ mapping_text = response2.json()['choices'][0]['message']['content'].strip()
410
+ mapping_text = mapping_text.replace('```json', '').replace('```', '').strip()
411
+
412
+ try:
413
+ self.mapping_table = json.loads(mapping_text)
414
+ self._fix_percent_mapping()
415
+ self.reverse_mapping = {v: k for k, v in self.mapping_table.items()}
416
+ logger.info(f"✅ Mapping استخراج شد: {len(self.mapping_table)} موجودیت")
417
+ except json.JSONDecodeError:
418
+ logger.warning("⚠️ خطا در parse کردن JSON mapping - استفاده از روش fallback")
419
+ self._extract_mapping_from_text(text, anonymized_text)
420
+ else:
421
+ logger.warning("⚠️ خطا در دریافت mapping - استفاده از روش fallback")
422
+ self._extract_mapping_from_text(text, anonymized_text)
423
+
424
+ return anonymized_text, self.mapping_table
425
+
426
+ except Exception as e:
427
+ logger.error(f"❌ Cerebras Exception: {e}")
428
+ raise
429
+
430
+ def _fix_percent_mapping(self):
431
+ """اصلاح mapping برای درصدها و مقادیر"""
432
+ for token, value in self.mapping_table.items():
433
+ value_str = str(value).strip()
434
+
435
+ if token.startswith('percent-'):
436
+ if not re.search(r'(درصد|%|درصدی)', value_str):
437
+ self.mapping_table[token] = f"{value_str} درصد"
438
+ logger.info(f"✅ اصلاح {token}: '{value_str}' → '{value_str} درصد'")
439
+
440
+ elif token.startswith('amount-'):
441
+ if not re.search(r'(میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)', value_str):
442
+ logger.warning(f"⚠️ {token}: فقط عدد '{value_str}' - واحد مشخص نیست")
443
+
444
+ def _extract_mapping_from_text(self, original: str, anonymized: str):
445
+ """استخراج mapping از متن‌های اصلی و ناشناس شده"""
446
+ all_tokens = []
447
+ for entity_type in ['person', 'company', 'amount', 'percent']:
448
+ tokens = re.findall(f'{entity_type}-\\d+', anonymized)
449
+ all_tokens.extend([(t, entity_type) for t in tokens])
450
+
451
+ all_tokens = sorted(set(all_tokens), key=lambda x: (x[1], int(x[0].split('-')[1])))
452
+
453
+ patterns = {
454
+ 'person': r'\b[ء-ي]+\s+[ء-ي]+(?:\s+[ء-ي]+)*\b',
455
+ 'company': r'(?:شرکت|بانک|سازمان|گروه|هلدینگ)\s+[ء-ي]+(?:\s+[ء-ي]+)*',
456
+ 'amount': r'\d+(?:\.\d+)?\s*(?:میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)',
457
+ 'percent': r'\d+(?:\.\d+)?\s*(?:درصد|%|درصدی)',
458
+ }
459
+
460
+ original_entities = {}
461
+ for entity_type, pattern in patterns.items():
462
+ matches = list(re.finditer(pattern, original))
463
+ original_entities[entity_type] = [m.group().strip() for m in matches]
464
+
465
+ for token, entity_type in all_tokens:
466
+ if entity_type in original_entities and original_entities[entity_type]:
467
+ token_num = int(token.split('-')[1]) - 1
468
+
469
+ if token_num < len(original_entities[entity_type]):
470
+ original_text = original_entities[entity_type][token_num]
471
+ self.mapping_table[token] = original_text
472
+ self.reverse_mapping[original_text] = token
473
+ else:
474
+ original_text = original_entities[entity_type][-1]
475
+ if token not in self.mapping_table:
476
+ self.mapping_table[token] = original_text
477
+ self.reverse_mapping[original_text] = token
478
+
479
+ def analyze_with_model(self, anonymized_text: str, analysis_prompt: str, model_name: str) -> str:
480
+ """
481
+ اجرای پرامپت‌های درون متن ناشناس‌سازی شده با مدل انتخابی
482
+ """
483
+ logger.info(f"🤖 {model_name} اجرای پرامپت...")
484
+
485
+ if not analysis_prompt or not analysis_prompt.strip():
486
+ logger.info("⚠️ پرامپتی وارد نشده - متن ناشناس‌سازی شده برگردانده می‌شود")
487
+ return anonymized_text
488
+
489
+ try:
490
+ # ساخت system message
491
+ system_msg = """شما یک تحلیلگر مالی حرفه‌ای هستید. متن حاوی کدهای ناشناس است (person-XX، company-XX، amount-XX، percent-XX).
492
+ به سوالات و درخواست‌ها با دقت پاسخ دهید و این کدها را در پاسخ خود حفظ کنید."""
493
+
494
+ # ساخت پیام کامل
495
+ full_text = f"""{analysis_prompt}
496
+
497
+ متن برای تحلیل:
498
+ {anonymized_text}"""
499
+
500
+ # ارسال به مدل انتخابی
501
+ response = self.model_sender.send_message(full_text, model_name, system_msg)
502
+
503
+ logger.info(f"✅ {model_name} پاسخ داد: {len(response)} کاراکتر")
504
+ return response
505
+
506
+ except Exception as e:
507
+ logger.error(f"❌ {model_name} Exception: {e}")
508
+ return f"❌ خطا در {model_name}: {str(e)}"
509
+
510
+ def restore_text(self, anonymized_text: str) -> str:
511
+ """بازگردانی متن ناشناس‌سازی شده به متن اصلی"""
512
+ logger.info("🔄 بازگردانی متن...")
513
+
514
+ if not self.mapping_table:
515
+ logger.warning("⚠️ جدول نگاشت خالی است")
516
+ return anonymized_text
517
+
518
+ restored = anonymized_text
519
+ for placeholder, original in sorted(self.mapping_table.items()):
520
+ restored = restored.replace(placeholder, original)
521
+
522
+ logger.info("✅ بازگردانی کامل")
523
+ return restored
524
+
525
+ def get_mapping_table_md(self) -> str:
526
+ """تبدیل جدول نگاشت به Markdown"""
527
+ if not self.mapping_table:
528
+ return "### 📋 جدول نگاشت\n\nهیچ موجودیتی شناسایی نشد"
529
+
530
+ table = "### 📋 جدول نگاشت\n\n"
531
+ table += "| شناسه | متن اصلی |\n"
532
+ table += "|-------|----------|\n"
533
+
534
+ for token, original in sorted(self.mapping_table.items()):
535
+ table += f"| **{token}** | {original} |\n"
536
+
537
+ return table
538
+
539
+ # متغیر سراسری
540
+ anonymizer = None
541
+
542
+ def process(input_text: str, analysis_prompt: str, model_choice: str):
543
+ """پردازش متن - 4 مرحله"""
544
+ global anonymizer
545
+
546
+ if not input_text.strip():
547
+ return "", "", "", ""
548
+
549
+ cerebras_key = os.getenv("CEREBRAS_API_KEY")
550
+
551
+ if not anonymizer:
552
+ anonymizer = AnonymizerAdvanced(cerebras_key)
553
+ else:
554
+ anonymizer.mapping_table = {}
555
+ anonymizer.reverse_mapping = {}
556
+
557
+ try:
558
+ logger.info("=" * 70)
559
+ logger.info(f"🚀 شروع پردازش - مدل تحلیل: {model_choice}")
560
+ logger.info("=" * 70)
561
+
562
+ # مرحله 1: ناشناس‌سازی
563
+ logger.info("📝 مرحله 1: ناشناس‌سازی...")
564
+ anonymized_text, _ = anonymizer.anonymize_with_cerebras(input_text)
565
+ logger.info(f"✅ ناشناس‌سازی: {len(anonymized_text)} کاراکتر")
566
+
567
+ # مرحله 2: مدل انتخابی
568
+ logger.info(f"🤖 مرحله 2: {model_choice}...")
569
+ model_response = anonymizer.analyze_with_model(anonymized_text, analysis_prompt, model_choice)
570
+ logger.info(f"✅ {model_choice}: {len(model_response)} کاراکتر")
571
+
572
+ # مرحله 3: بازگردانی
573
+ logger.info("🔄 مرحله 3: بازگردانی...")
574
+ restored_text = anonymizer.restore_text(model_response)
575
+ logger.info("✅ بازگردانی کامل")
576
+
577
+ # مرحله 4: جدول نگاشت
578
+ logger.info("📋 مرحله 4: جدول نگاشت...")
579
+ mapping_str = anonymizer.get_mapping_table_md()
580
+ logger.info(f"✅ {len(anonymizer.mapping_table)} موجودیت")
581
+
582
+ logger.info("=" * 70)
583
+ logger.info("✅ تمام مراحل کامل!")
584
+ logger.info("=" * 70)
585
+
586
+ return restored_text, model_response, anonymized_text, mapping_str
587
+
588
+ except Exception as e:
589
+ logger.error(f"❌ خطا: {str(e)}", exc_info=True)
590
+ return "", f"❌ خطا: {str(e)}", "", ""
591
+
592
+ def clear_all():
593
+ """پاک کردن همه"""
594
+ return "", "", "", "", "", ""
595
+
596
+ # Gradio Interface
597
+ css_rtl = """
598
+ .input-box { direction: rtl; text-align: right; }
599
+ .textbox textarea { direction: rtl; text-align: right; font-family: 'Tahoma', serif; }
600
+ """
601
+
602
+ with gr.Blocks(title="سیستم ناشناس‌سازی متون", theme=gr.themes.Soft(), css=css_rtl) as app:
603
+
604
+ gr.Markdown("# 🔐 سیستم ناشناس‌سازی متون مالی فارسی (چند مدل)", elem_classes="input-box")
605
+ gr.Markdown("### با قابلیت Retry و مدیریت خطای پیشرفته", elem_classes="input-box")
606
+
607
+ with gr.Row():
608
+ with gr.Column(scale=1):
609
+ # منوی انتخاب مدل
610
+ model_dropdown = gr.Dropdown(
611
+ choices=["ChatGPT-4o-mini", "Grok", "Gemini", "DeepSeek"],
612
+ value="ChatGPT-4o-mini",
613
+ label="🤖 انتخاب مدل تحلیل",
614
+ info="هر مدل دارای Retry و Error Handling است",
615
+ interactive=True
616
+ )
617
+
618
+ analysis_prompt = gr.Textbox(
619
+ lines=8,
620
+ placeholder="مثال: این متن را خلاصه کن\nمثال: نقاط قوت و ضعف را استخراج کن",
621
+ label="📋 دستورات تحلیل (اختیاری)",
622
+ elem_classes="textbox"
623
+ )
624
+
625
+ gr.Markdown("---")
626
+
627
+ with gr.Column():
628
+ process_btn = gr.Button(
629
+ "▶️ پردازش",
630
+ variant="primary",
631
+ size="lg"
632
+ )
633
+
634
+ clear_btn = gr.Button(
635
+ "🗑️ پاک کردن",
636
+ variant="stop",
637
+ size="lg"
638
+ )
639
+
640
+ with gr.Column(scale=3):
641
+ input_text = gr.Textbox(
642
+ lines=14,
643
+ placeholder="متن مالی/خبری فارسی را وارد کنید...",
644
+ label="📝 متن ورودی",
645
+ elem_classes="textbox"
646
+ )
647
+
648
+ gr.Markdown("---")
649
+ gr.Markdown("## 📊 نتایج پردازش", elem_classes="input-box")
650
+
651
+ with gr.Row():
652
+ with gr.Column(scale=1):
653
+ restored_text = gr.Textbox(
654
+ lines=12,
655
+ label="✅ متن بازگردانی شده",
656
+ interactive=False,
657
+ elem_classes="textbox"
658
+ )
659
+
660
+ with gr.Column(scale=1):
661
+ model_analysis = gr.Textbox(
662
+ lines=12,
663
+ label="🤖 تحلیل مدل (ناشناس)",
664
+ interactive=False,
665
+ elem_classes="textbox"
666
+ )
667
+
668
+ with gr.Column(scale=1):
669
+ anonymized_text = gr.Textbox(
670
+ lines=12,
671
+ label="🔒 متن ناشناس‌شده",
672
+ interactive=False,
673
+ elem_classes="textbox"
674
+ )
675
+
676
+ gr.Markdown("---")
677
+
678
+ mapping_table = gr.Markdown(
679
+ value="### 📋 جدول نگاشت\n\nهنوز پردازشی انجام نشده",
680
+ label="📋 جدول نگاشت",
681
+ elem_classes="input-box"
682
+ )
683
+
684
+ # Event Handlers
685
+ process_btn.click(
686
+ fn=process,
687
+ inputs=[input_text, analysis_prompt, model_dropdown],
688
+ outputs=[restored_text, model_analysis, anonymized_text, mapping_table]
689
+ )
690
+
691
+ clear_btn.click(
692
+ fn=clear_all,
693
+ outputs=[input_text, analysis_prompt, restored_text, model_analysis, anonymized_text, mapping_table]
694
+ )
695
+
696
+ if __name__ == "__main__":
697
+ print("=" * 70)
698
+ print("🚀 سیس��م ناشناس‌سازی متون در حال راه‌اندازی...")
699
+ print("=" * 70)
700
+ print("\n📋 API Keys مورد نیاز:\n")
701
+ print(" ✅ CEREBRAS_API_KEY (برای ناشناس‌سازی)")
702
+ print(" ✅ OPENAI_API_KEY (برای ChatGPT)")
703
+ print(" ✅ XAI_API_KEY (برای Grok)")
704
+ print(" ✅ GEMINI_API_KEY (برای Gemini)")
705
+ print(" ✅ DEEPSEEK_API_KEY (برای DeepSeek)")
706
+ print("\n⚡ ویژگی‌های جدید:\n")
707
+ print(" 🔄 Retry mechanism برای همه مدل‌ها")
708
+ print(" ⚠️ مدیریت خطاهای Rate limiting")
709
+ print(" 🛡️ Error handling پیشرفته")
710
+ print(" ⏱️ Timeout handling با تلاش مجدد")
711
+ print("\n🎯 روش ناشناس‌سازی: Cerebras (Llama 3.3-70B)")
712
+ print("🤖 مدل‌های تحلیل: ChatGPT, Grok, Gemini, DeepSeek")
713
+ print("=" * 70 + "\n")
714
+
715
+ app.launch(
716
+ server_name="0.0.0.0",
717
+ server_port=7860,
718
+ share=False,
719
+ show_error=True
720
+ )