leilaghomashchi commited on
Commit
6592dc7
·
verified ·
1 Parent(s): 00d626e

Upload 2 files

Browse files
app_rtl_fa_final_UPDATED (7).py ADDED
@@ -0,0 +1,501 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import re
3
+ import os
4
+ import requests
5
+ import json
6
+ import logging
7
+ from typing import Dict, List, Tuple
8
+ from unified_llm_sender import UnifiedLLMSender, get_model_display_names
9
+
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class AnonymizerAdvanced:
14
+ """ناشناس‌ساز پیشرفته با روش‌های متعدد"""
15
+
16
+ def __init__(self, cerebras_key: str = None, llm_model: str = 'gpt-4o-mini'):
17
+ self.cerebras_key = cerebras_key or os.getenv("CEREBRAS_API_KEY")
18
+ self.llm_model = llm_model
19
+ self.mapping_table = {} # {placeholder: original_text}
20
+ self.reverse_mapping = {} # {original_text: placeholder}
21
+
22
+ # ✅ ایجاد instance از UnifiedLLMSender
23
+ self.llm_sender = UnifiedLLMSender(model=llm_model)
24
+
25
+ logger.info(f"✅ Anonymizer Advanced مقداردهی شد - LLM: {llm_model}")
26
+
27
+
28
+ def anonymize_with_cerebras(self, text: str) -> Tuple[str, Dict]:
29
+ """ناشناس‌سازی با Cerebras - دریافت mapping از مدل"""
30
+ logger.info("🧠 روش Cerebras...")
31
+
32
+ if not self.cerebras_key:
33
+ logger.error("❌ Cerebras API Key موجود نیست")
34
+ raise ValueError("Cerebras API Key مورد نیاز است")
35
+
36
+ try:
37
+ # مرحله 1: ناشناس‌سازی متن
38
+ prompt1 = f"""متن زیر را ناشناس کنید. قوانین:
39
+ 1. اسامی اشخاص → person-01, person-02, ...
40
+ 2. نام شرکت‌ها/سازمان‌ها → company-01, company-02, ...
41
+ 3. مقادیر پولی → amount-01, amount-02, ...
42
+ 4. درصدها → percent-01, percent-02, ...
43
+ 5. فقط این توکن‌ها استفاده کنید
44
+ 6. شماره‌های نسخه را درست حفظ کنید
45
+ 7. اگر موجودیت تکرار شود از شماره قدیمی استفاده کنید
46
+
47
+ متن:
48
+ {text}
49
+
50
+ خروجی: فقط متن ناشناس شده"""
51
+
52
+ response1 = requests.post(
53
+ "https://api.cerebras.ai/v1/chat/completions",
54
+ headers={
55
+ "Authorization": f"Bearer {self.cerebras_key}",
56
+ "Content-Type": "application/json"
57
+ },
58
+ json={
59
+ "model": "llama-3.3-70b",
60
+ "messages": [{"role": "user", "content": prompt1}],
61
+ "max_tokens": 4096,
62
+ "temperature": 0.1
63
+ },
64
+ timeout=60
65
+ )
66
+
67
+ if response1.status_code != 200:
68
+ logger.error(f"❌ Cerebras Error: {response1.status_code}")
69
+ raise Exception(f"Cerebras API Error: {response1.status_code}")
70
+
71
+ anonymized_text = response1.json()['choices'][0]['message']['content'].strip()
72
+ logger.info("✅ Cerebras: ناشناس‌سازی موفق")
73
+
74
+ # مرحله 2: استخراج mapping از مدل
75
+ prompt2 = f"""متن اصلی:
76
+ {text}
77
+
78
+ متن ناشناس شده:
79
+ {anonymized_text}
80
+
81
+ لطفاً یک جدول mapping برای همه توکن‌های ناشناس ایجاد کن.
82
+ برای هر توکن، متن اصلی کامل آن را مشخص کن.
83
+
84
+ **مهم:**
85
+ - برای person-XX: نام کامل شخص (مثلاً "علی احمدی")
86
+ - برای company-XX: نام کامل شرکت/سازمان (مثلاً "شرکت پتروشیمی")
87
+ - برای amount-XX: عدد + واحد (مثلاً "80 هزار تومان" یا "50 میلیارد ریال")
88
+ - برای percent-XX: عدد + کلمه "درصد" (مثلاً "40 درصد" نه فقط "40")
89
+
90
+ خروجی را به این فرمت JSON بده (فقط JSON، بدون توضیح اضافی):
91
+ {{
92
+ "person-01": "متن اصلی کامل",
93
+ "company-01": "متن اصلی کامل",
94
+ "amount-01": "متن اصلی کامل با واحد",
95
+ "percent-01": "عدد + درصد",
96
+ ...
97
+ }}"""
98
+
99
+ response2 = requests.post(
100
+ "https://api.cerebras.ai/v1/chat/completions",
101
+ headers={
102
+ "Authorization": f"Bearer {self.cerebras_key}",
103
+ "Content-Type": "application/json"
104
+ },
105
+ json={
106
+ "model": "llama-3.3-70b",
107
+ "messages": [{"role": "user", "content": prompt2}],
108
+ "max_tokens": 2048,
109
+ "temperature": 0.1
110
+ },
111
+ timeout=60
112
+ )
113
+
114
+ if response2.status_code == 200:
115
+ mapping_text = response2.json()['choices'][0]['message']['content'].strip()
116
+
117
+ # پاک‌سازی و parse کردن JSON
118
+ # حذف markdown code blocks اگر وجود داشته باشه
119
+ mapping_text = mapping_text.replace('```json', '').replace('```', '').strip()
120
+
121
+ try:
122
+ self.mapping_table = json.loads(mapping_text)
123
+
124
+ # پست-پروسسینگ: اصلاح mapping برای percent ها
125
+ self._fix_percent_mapping()
126
+
127
+ # ساخت reverse mapping
128
+ self.reverse_mapping = {v: k for k, v in self.mapping_table.items()}
129
+ logger.info(f"✅ Mapping استخراج شد: {len(self.mapping_table)} موجودیت")
130
+ except json.JSONDecodeError:
131
+ logger.warning("⚠️ خطا در parse کردن JSON mapping - استفاده از روش fallback")
132
+ self._extract_mapping_from_text(text, anonymized_text)
133
+ else:
134
+ logger.warning("⚠️ خطا در دریافت mapping - استفاده از روش fallback")
135
+ self._extract_mapping_from_text(text, anonymized_text)
136
+
137
+ return anonymized_text, self.mapping_table
138
+
139
+ except Exception as e:
140
+ logger.error(f"❌ Cerebras Exception: {e}")
141
+ raise
142
+
143
+ def _fix_percent_mapping(self):
144
+ """اصلاح mapping برای درصدها و مقادیر - اضافه کردن واحدها اگر فقط عدد باشد"""
145
+ for token, value in self.mapping_table.items():
146
+ value_str = str(value).strip()
147
+
148
+ if token.startswith('percent-'):
149
+ # چک کنیم آیا کلمه "درصد" یا "%" در value هست
150
+ if not re.search(r'(درصد|%|درصدی)', value_str):
151
+ # فقط عدد هست، کلمه "درصد" رو اضافه کن
152
+ self.mapping_table[token] = f"{value_str} درصد"
153
+ logger.info(f"✅ اصلاح {token}: '{value_str}' → '{value_str} درصد'")
154
+
155
+ elif token.startswith('amount-'):
156
+ # چک کنیم آیا واحد پولی در value هست
157
+ if not re.search(r'(میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)', value_str):
158
+ # فقط عدد هست، احتمالاً باید واحد اضافه بشه
159
+ # اما نمی‌دونیم چه واحدی، پس warning بده
160
+ logger.warning(f"⚠️ {token}: فقط عدد '{value_str}' - واحد مشخص نیست")
161
+
162
+ def _extract_mapping_from_text(self, original: str, anonymized: str):
163
+ """استخراج mapping از متن‌های اصلی و ناشناس شده - نسخه بهبود یافته"""
164
+
165
+ # استخراج همه توکن‌های ناشناس از متن ناشناس‌سازی شده
166
+ all_tokens = []
167
+ for entity_type in ['person', 'company', 'amount', 'percent']:
168
+ tokens = re.findall(f'{entity_type}-\\d+', anonymized)
169
+ all_tokens.extend([(t, entity_type) for t in tokens])
170
+
171
+ # حذف تکراری‌ها و مرتب‌سازی
172
+ all_tokens = sorted(set(all_tokens), key=lambda x: (x[1], int(x[0].split('-')[1])))
173
+
174
+ # الگوهای موجودیت در متن اصلی
175
+ patterns = {
176
+ 'person': r'\b[ء-ي]+\s+[ء-ي]+(?:\s+[ء-ي]+)*\b',
177
+ 'company': r'(?:شرکت|بانک|سازمان|گروه|هلدینگ)\s+[ء-ي]+(?:\s+[ء-ي]+)*',
178
+ 'amount': r'\d+(?:\.\d+)?\s*(?:میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)',
179
+ 'percent': r'\d+(?:\.\d+)?\s*(?:درصد|%|درصدی)',
180
+ }
181
+
182
+ # استخراج موجودیت‌های اصلی
183
+ original_entities = {}
184
+ for entity_type, pattern in patterns.items():
185
+ matches = list(re.finditer(pattern, original))
186
+ original_entities[entity_type] = [m.group().strip() for m in matches]
187
+
188
+ # نگاشت توکن‌ها به موجودیت‌های اصلی
189
+ for token, entity_type in all_tokens:
190
+ if entity_type in original_entities and original_entities[entity_type]:
191
+ # گرفتن شماره توکن (مثلاً از person-01 عدد 1 رو میگیریم)
192
+ token_num = int(token.split('-')[1]) - 1
193
+
194
+ if token_num < len(original_entities[entity_type]):
195
+ original_text = original_entities[entity_type][token_num]
196
+ self.mapping_table[token] = original_text
197
+ self.reverse_mapping[original_text] = token
198
+ else:
199
+ # اگر شماره توکن بیشتر از تعداد موجودیت‌ها بود
200
+ # از آخرین موجودیت استفاده کن
201
+ original_text = original_entities[entity_type][-1]
202
+ if token not in self.mapping_table:
203
+ self.mapping_table[token] = original_text
204
+ self.reverse_mapping[original_text] = token
205
+
206
+ def analyze_with_gpt(self, anonymized_text: str, analysis_prompt: str = None) -> str:
207
+ """
208
+ ✅ اصلاح شده - استفاده از UnifiedLLMSender
209
+ اجرای پرامپت‌های درون متن ناشناس‌سازی شده با LLM
210
+ """
211
+ logger.info(f"🤖 {self.llm_model} اجرای پرامپت...")
212
+
213
+ # ✅ اگر پرامپتی نیست، فقط متن ناشناس‌سازی شده برگردان
214
+ if not analysis_prompt or analysis_prompt.strip() == "":
215
+ logger.info("📝 بدون دستورات - فقط متن ناشناس‌سازی شده برگردانده می‌شود")
216
+ return anonymized_text
217
+
218
+ try:
219
+ # متن ارسالی شامل متن ناشناس‌سازی شده + دستورات کاربر
220
+ user_message = f"""متن ناشناس‌سازی شده:
221
+ ---
222
+ {anonymized_text}
223
+ ---
224
+
225
+ دستورات:
226
+ {analysis_prompt}
227
+
228
+ توکن‌های ناشناس را حتماً حفظ کن. فقط نتیجه اجرای دستورات را برگردان."""
229
+
230
+ logger.info(f"📋 متن ارسالی به {self.llm_model}:\n{user_message}\n")
231
+
232
+ # ✅ استفاده از UnifiedLLMSender
233
+ system_msg = """شما دستیار اجرای دستورات روی متون ناشناس‌سازی شده‌اید.
234
+ توکن‌های ناشناس (person-01, company-01, amount-01, percent-01) را حتماً حفظ کن.
235
+ فقط دستورات دادشده را اجرا کن."""
236
+
237
+ llm_response = self.llm_sender.send(
238
+ text=user_message,
239
+ system_msg=system_msg,
240
+ max_tokens=4096,
241
+ temperature=0.3,
242
+ lang='fa'
243
+ )
244
+
245
+ # بررسی برای خطاهای API
246
+ if llm_response.startswith("❌"):
247
+ logger.error(f"❌ LLM Error: {llm_response}")
248
+ return llm_response
249
+
250
+ logger.info(f"✅ {self.llm_model}: دستورات اجرا شدند")
251
+ logger.info(f"📤 پاسخ {self.llm_model}:\n{llm_response}\n")
252
+ return llm_response
253
+
254
+ except Exception as e:
255
+ logger.error(f"❌ LLM Exception: {e}")
256
+ return f"❌ خطا: {str(e)}"
257
+
258
+ def restore_text(self, anonymized_text: str) -> str:
259
+ """بازگردانی متن اصلی"""
260
+ logger.info("🔄 بازگردانی...")
261
+
262
+ restored = anonymized_text
263
+ for placeholder, original in sorted(self.mapping_table.items()):
264
+ restored = restored.replace(placeholder, original)
265
+
266
+ logger.info("✅ بازگردانی کامل")
267
+ return restored
268
+
269
+ def get_mapping_table_md(self) -> str:
270
+ """تبدیل جدول نگاشت به Markdown"""
271
+ if not self.mapping_table:
272
+ return "### 📋 جدول نگاشت\n\nهیچ موجودیتی شناسایی نشد"
273
+
274
+ table = "### 📋 جدول نگاشت\n\n"
275
+ table += "| شناسه | متن اصلی |\n"
276
+ table += "|-------|----------|\n"
277
+
278
+ for token, original in sorted(self.mapping_table.items()):
279
+ table += f"| **{token}** | {original} |\n"
280
+
281
+ return table
282
+
283
+ # متغیر سراسری
284
+ anonymizer = None
285
+
286
+ def process(input_text: str, analysis_prompt: str = None, llm_model: str = 'gpt-4o-mini'):
287
+ """پردازش متن - 4 مرحله"""
288
+ global anonymizer
289
+
290
+ if not input_text.strip():
291
+ return "", "", "", ""
292
+
293
+ cerebras_key = os.getenv("CEREBRAS_API_KEY")
294
+
295
+ if not anonymizer or anonymizer.llm_model != llm_model:
296
+ anonymizer = AnonymizerAdvanced(cerebras_key, llm_model)
297
+ else:
298
+ anonymizer.mapping_table = {}
299
+ anonymizer.reverse_mapping = {}
300
+
301
+ try:
302
+ logger.info("=" * 70)
303
+ logger.info(f"🚀 شروع پردازش - LLM: {llm_model}")
304
+ logger.info("=" * 70)
305
+
306
+ # ============================================
307
+ # مرحله 1: ناشناس‌سازی
308
+ # ============================================
309
+ logger.info("📝 مرحله 1: ناشناس‌سازی...")
310
+
311
+ anonymized_text, _ = anonymizer.anonymize_with_cerebras(input_text)
312
+
313
+ logger.info(f"✅ ناشناس‌سازی: {len(anonymized_text)} کاراکتر")
314
+
315
+ # ============================================
316
+ # مرحله 2: LLM با متن ناشناس‌سازی شده + دستورات
317
+ # ============================================
318
+ logger.info(f"🤖 مرحله 2: {llm_model}...")
319
+ llm_response = anonymizer.analyze_with_gpt(anonymized_text, analysis_prompt)
320
+ logger.info(f"✅ {llm_model}: {len(llm_response)} کاراکتر")
321
+
322
+ # ============================================
323
+ # مرحله 3: ب��زگردانی پاسخ LLM
324
+ # ============================================
325
+ logger.info("🔄 مرحله 3: بازگردانی...")
326
+ restored_text = anonymizer.restore_text(llm_response)
327
+ logger.info("✅ بازگردانی کامل")
328
+
329
+ # ============================================
330
+ # مرحله 4: جدول نگاشت
331
+ # ============================================
332
+ logger.info("📋 مرحله 4: جدول نگاشت...")
333
+ mapping_str = anonymizer.get_mapping_table_md()
334
+ logger.info(f"✅ {len(anonymizer.mapping_table)} موجودیت")
335
+
336
+ logger.info("=" * 70)
337
+ logger.info("✅ تمام مراحل کامل!")
338
+ logger.info("=" * 70)
339
+
340
+ return restored_text, llm_response, anonymized_text, mapping_str
341
+
342
+ except Exception as e:
343
+ logger.error(f"❌ خطا: {str(e)}", exc_info=True)
344
+ return "", f"❌ خطا: {str(e)}", "", ""
345
+
346
+ def clear_all():
347
+ """پاک کردن همه"""
348
+ return "", "", "", "", "", ""
349
+
350
+ def get_model_key_from_display(display_name: str) -> str:
351
+ """تبدیل نام نمایشی به نام واقعی مدل"""
352
+ model_names = get_model_display_names()
353
+ reverse_map = {v: k for k, v in model_names.items()}
354
+ return reverse_map.get(display_name, 'gpt-4o-mini')
355
+
356
+ def process_wrapper(input_text: str, llm_display_name: str, analysis_prompt: str = None):
357
+ """Wrapper برای تبدیل نام نمایشی به نام واقعی مدل"""
358
+ llm_model = get_model_key_from_display(llm_display_name)
359
+ return process(input_text, analysis_prompt, llm_model)
360
+
361
+ # Gradio Interface
362
+ css_rtl = """
363
+ .input-box { direction: rtl; text-align: right; }
364
+ .textbox textarea { direction: rtl; text-align: right; font-family: 'Tahoma', serif; }
365
+ """
366
+
367
+ with gr.Blocks(title="سیستم ناشناس‌سازی متون", theme=gr.themes.Soft(), css=css_rtl) as app:
368
+
369
+ gr.Markdown("# 🔐 سیستم ناشناس‌سازی متون مالی فارسی", elem_classes="input-box")
370
+
371
+ # ============================================
372
+ # صفحه اول: دکمه‌ها (راست) + ورودی (چپ)
373
+ # ============================================
374
+ with gr.Row():
375
+ # سمت راست: دکمه‌ها و دستورات
376
+ with gr.Column(scale=1):
377
+ # انتخاب مدل LLM
378
+ model_names = get_model_display_names()
379
+ llm_model = gr.Radio(
380
+ choices=list(model_names.values()),
381
+ value='🤖 ChatGPT 4o-mini',
382
+ label="🤖 انتخاب مدل زبانی",
383
+ elem_classes="input-box"
384
+ )
385
+
386
+ gr.Markdown("---")
387
+
388
+ analysis_prompt = gr.Textbox(
389
+ lines=8,
390
+ placeholder="",
391
+ label="📋 دستورات تحلیل (اختیاری)",
392
+ elem_classes="textbox"
393
+ )
394
+
395
+ gr.Markdown("---")
396
+
397
+ with gr.Column():
398
+ process_btn = gr.Button(
399
+ "▶️ پردازش",
400
+ variant="primary",
401
+ size="lg"
402
+ )
403
+
404
+ clear_btn = gr.Button(
405
+ "🗑️ پاک کردن",
406
+ variant="stop",
407
+ size="lg"
408
+ )
409
+
410
+ # سمت چپ: متن ورودی (بزرگ‌تر)
411
+ with gr.Column(scale=3):
412
+ input_text = gr.Textbox(
413
+ lines=14,
414
+ placeholder="متن مالی/خبری را وارد کنید...",
415
+ label="📝 متن ورودی",
416
+ elem_classes="textbox"
417
+ )
418
+
419
+ # ============================================
420
+ # صفحه دوم: 3 باکس نتایج (وسط)
421
+ # ============================================
422
+ gr.Markdown("---")
423
+ gr.Markdown("## 📊 نتایج پردازش", elem_classes="input-box")
424
+
425
+ with gr.Row():
426
+ # باکس 1: متن بازگردانی شده (راست)
427
+ with gr.Column(scale=1):
428
+ restored_text = gr.Textbox(
429
+ lines=12,
430
+ label="✅ متن بازگردانی شده",
431
+ interactive=False,
432
+ elem_classes="textbox"
433
+ )
434
+
435
+ # باکس 2: تحلیل LLM (وسط)
436
+ with gr.Column(scale=1):
437
+ gpt_analysis = gr.Textbox(
438
+ lines=12,
439
+ label="🤖 تحلیل LLM",
440
+ interactive=False,
441
+ elem_classes="textbox"
442
+ )
443
+
444
+ # باکس 3: متن ناشناس‌شده (چپ)
445
+ with gr.Column(scale=1):
446
+ anonymized_text = gr.Textbox(
447
+ lines=12,
448
+ label="🔒 متن ناشناس‌شده",
449
+ interactive=False,
450
+ elem_classes="textbox"
451
+ )
452
+
453
+ # ============================================
454
+ # پایین: جدول نگاشت (Markdown)
455
+ # ============================================
456
+ gr.Markdown("---")
457
+
458
+ mapping_table = gr.Markdown(
459
+ value="### 📋 جدول نگاشت\n\nهنوز پردازشی انجام نشده",
460
+ label="📋 جدول نگاشت",
461
+ elem_classes="input-box"
462
+ )
463
+
464
+ # ============================================
465
+ # Event Handlers
466
+ # ============================================
467
+ process_btn.click(
468
+ fn=process_wrapper,
469
+ inputs=[input_text, llm_model, analysis_prompt],
470
+ outputs=[restored_text, gpt_analysis, anonymized_text, mapping_table]
471
+ )
472
+
473
+ clear_btn.click(
474
+ fn=clear_all,
475
+ outputs=[input_text, analysis_prompt, restored_text, gpt_analysis, anonymized_text, mapping_table]
476
+ )
477
+
478
+ if __name__ == "__main__":
479
+ print("=" * 70)
480
+ print("🚀 سیستم ناشناس‌سازی متون در حال راه‌اندازی...")
481
+ print("=" * 70)
482
+ print("\n📋 نحوه استفاده:\n")
483
+ print("1. CEREBRAS_API_KEY را تنظیم کنید")
484
+ print("2. API Key مدل مورد نظر را تنظیم کنید:")
485
+ print(" - OPENAI_API_KEY برای ChatGPT")
486
+ print(" - XAI_API_KEY برای Grok")
487
+ print(" - GOOGLE_API_KEY برای Gemini")
488
+ print(" - DEEPSEEK_API_KEY برای DeepSeek")
489
+ print("3. http://localhost:7860 را باز کنید")
490
+ print("4. متن را وارد کنید و مدل را انتخاب کنید")
491
+ print("5. 'پردازش' را کلیک کنید\n")
492
+ print("روش ناشناس‌سازی: Cerebras (Llama 3.3-70B)")
493
+ print("مدل‌های تحلیل: ChatGPT, Grok, Gemini, DeepSeek")
494
+ print("=" * 70 + "\n")
495
+
496
+ app.launch(
497
+ server_name="0.0.0.0",
498
+ server_port=7860,
499
+ share=False,
500
+ show_error=True
501
+ )
unified_llm_sender.py ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ماژول مدیریت یکپارچه LLM ها
3
+ پشتیبانی از: ChatGPT, Grok, Gemini, DeepSeek
4
+ """
5
+
6
+ import os
7
+ import requests
8
+ import logging
9
+ from typing import Optional
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class UnifiedLLMSender:
15
+ """کلاس یکپارچه برای ارسال به LLM های مختلف"""
16
+
17
+ MODELS = {
18
+ 'gpt-4o-mini': {
19
+ 'provider': 'openai',
20
+ 'api_base': 'https://api.openai.com/v1/chat/completions',
21
+ 'env_key': 'OPENAI_API_KEY'
22
+ },
23
+ 'gpt-4o': {
24
+ 'provider': 'openai',
25
+ 'api_base': 'https://api.openai.com/v1/chat/completions',
26
+ 'env_key': 'OPENAI_API_KEY'
27
+ },
28
+ 'o1': {
29
+ 'provider': 'openai',
30
+ 'api_base': 'https://api.openai.com/v1/chat/completions',
31
+ 'env_key': 'OPENAI_API_KEY'
32
+ },
33
+ 'o1-mini': {
34
+ 'provider': 'openai',
35
+ 'api_base': 'https://api.openai.com/v1/chat/completions',
36
+ 'env_key': 'OPENAI_API_KEY'
37
+ },
38
+ 'grok-beta': {
39
+ 'provider': 'xai',
40
+ 'api_base': 'https://api.x.ai/v1/chat/completions',
41
+ 'env_key': 'XAI_API_KEY'
42
+ },
43
+ 'grok-2-latest': {
44
+ 'provider': 'xai',
45
+ 'api_base': 'https://api.x.ai/v1/chat/completions',
46
+ 'env_key': 'XAI_API_KEY'
47
+ },
48
+ 'gemini-2.0-flash-exp': {
49
+ 'provider': 'google',
50
+ 'api_base': 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash-exp:generateContent',
51
+ 'env_key': 'GOOGLE_API_KEY'
52
+ },
53
+ 'gemini-1.5-pro': {
54
+ 'provider': 'google',
55
+ 'api_base': 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-pro:generateContent',
56
+ 'env_key': 'GOOGLE_API_KEY'
57
+ },
58
+ 'deepseek-chat': {
59
+ 'provider': 'deepseek',
60
+ 'api_base': 'https://api.deepseek.com/v1/chat/completions',
61
+ 'env_key': 'DEEPSEEK_API_KEY'
62
+ },
63
+ 'deepseek-reasoner': {
64
+ 'provider': 'deepseek',
65
+ 'api_base': 'https://api.deepseek.com/v1/chat/completions',
66
+ 'env_key': 'DEEPSEEK_API_KEY'
67
+ }
68
+ }
69
+
70
+ def __init__(self, model: str = 'gpt-4o-mini'):
71
+ """
72
+ مقداردهی اولیه
73
+
74
+ Args:
75
+ model: نام مدل (مثلاً 'gpt-4o-mini', 'grok-beta', 'gemini-2.0-flash-exp', 'deepseek-chat')
76
+ """
77
+ self.model = model
78
+
79
+ if model not in self.MODELS:
80
+ raise ValueError(f"مدل {model} پشتیبانی نمی‌شود. مدل‌های موجود: {list(self.MODELS.keys())}")
81
+
82
+ self.config = self.MODELS[model]
83
+ self.provider = self.config['provider']
84
+ self.api_key = os.getenv(self.config['env_key'])
85
+
86
+ if not self.api_key:
87
+ logger.warning(f"⚠️ API Key برای {model} تنظیم نشده: {self.config['env_key']}")
88
+
89
+ def send(self,
90
+ text: str,
91
+ system_msg: Optional[str] = None,
92
+ max_tokens: int = 4096,
93
+ temperature: float = 0.3,
94
+ lang: str = 'fa') -> str:
95
+ """
96
+ ارسال پیام به LLM
97
+
98
+ Args:
99
+ text: متن ورودی
100
+ system_msg: پیام سیستم
101
+ max_tokens: حداکثر توکن خروجی
102
+ temperature: دما
103
+ lang: زبان
104
+
105
+ Returns:
106
+ پاسخ مدل
107
+ """
108
+ if not self.api_key:
109
+ return f"❌ API Key برای {self.model} موجود نیست. لطفاً {self.config['env_key']} را تنظیم کنید."
110
+
111
+ try:
112
+ if self.provider == 'google':
113
+ return self._send_google(text, system_msg, max_tokens, temperature)
114
+ else:
115
+ # OpenAI-compatible APIs (OpenAI, xAI, DeepSeek)
116
+ return self._send_openai_compatible(text, system_msg, max_tokens, temperature)
117
+
118
+ except Exception as e:
119
+ logger.error(f"❌ خطا در ارسال به {self.model}: {e}")
120
+ return f"❌ خطا: {str(e)}"
121
+
122
+ def _send_openai_compatible(self, text: str, system_msg: Optional[str],
123
+ max_tokens: int, temperature: float) -> str:
124
+ """ارسال به API های سازگار با OpenAI"""
125
+
126
+ messages = []
127
+ if system_msg:
128
+ messages.append({"role": "system", "content": system_msg})
129
+ messages.append({"role": "user", "content": text})
130
+
131
+ headers = {
132
+ "Authorization": f"Bearer {self.api_key}",
133
+ "Content-Type": "application/json"
134
+ }
135
+
136
+ payload = {
137
+ "model": self.model,
138
+ "messages": messages,
139
+ "max_tokens": max_tokens,
140
+ "temperature": temperature
141
+ }
142
+
143
+ logger.info(f"📤 ارسال به {self.model}...")
144
+
145
+ response = requests.post(
146
+ self.config['api_base'],
147
+ headers=headers,
148
+ json=payload,
149
+ timeout=120
150
+ )
151
+
152
+ if response.status_code == 200:
153
+ result = response.json()
154
+ content = result['choices'][0]['message']['content']
155
+ logger.info(f"✅ پاسخ از {self.model} دریافت شد ({len(content)} کاراکتر)")
156
+ return content
157
+ else:
158
+ error_msg = f"خطای API {response.status_code}: {response.text}"
159
+ logger.error(f"❌ {error_msg}")
160
+ return f"❌ {error_msg}"
161
+
162
+ def _send_google(self, text: str, system_msg: Optional[str],
163
+ max_tokens: int, temperature: float) -> str:
164
+ """ارسال به Google Gemini API"""
165
+
166
+ # ترکیب system message با user message
167
+ full_text = text
168
+ if system_msg:
169
+ full_text = f"{system_msg}\n\n{text}"
170
+
171
+ url = f"{self.config['api_base']}?key={self.api_key}"
172
+
173
+ payload = {
174
+ "contents": [{
175
+ "parts": [{
176
+ "text": full_text
177
+ }]
178
+ }],
179
+ "generationConfig": {
180
+ "maxOutputTokens": max_tokens,
181
+ "temperature": temperature
182
+ }
183
+ }
184
+
185
+ logger.info(f"📤 ارسال به {self.model}...")
186
+
187
+ response = requests.post(
188
+ url,
189
+ headers={"Content-Type": "application/json"},
190
+ json=payload,
191
+ timeout=120
192
+ )
193
+
194
+ if response.status_code == 200:
195
+ result = response.json()
196
+ content = result['candidates'][0]['content']['parts'][0]['text']
197
+ logger.info(f"✅ پاسخ از {self.model} دریافت شد ({len(content)} کاراکتر)")
198
+ return content
199
+ else:
200
+ error_msg = f"خطای API {response.status_code}: {response.text}"
201
+ logger.error(f"❌ {error_msg}")
202
+ return f"❌ {error_msg}"
203
+
204
+
205
+ # تابع کمکی برای گرفتن لیست مدل‌های موجود
206
+ def get_available_models() -> dict:
207
+ """دریافت لیست مدل‌های موجود و وضعیت API key های آن‌ها"""
208
+ available = {}
209
+
210
+ for model_name, config in UnifiedLLMSender.MODELS.items():
211
+ env_key = config['env_key']
212
+ has_key = bool(os.getenv(env_key))
213
+ available[model_name] = {
214
+ 'provider': config['provider'],
215
+ 'has_key': has_key,
216
+ 'env_key': env_key
217
+ }
218
+
219
+ return available
220
+
221
+
222
+ def get_model_display_names() -> dict:
223
+ """دریافت نام‌های نمایشی مدل‌ها"""
224
+ return {
225
+ 'gpt-4o-mini': '🤖 ChatGPT 4o-mini',
226
+ 'gpt-4o': '🤖 ChatGPT 4o',
227
+ 'o1': '🤖 ChatGPT o1',
228
+ 'o1-mini': '🤖 ChatGPT o1-mini',
229
+ 'grok-beta': '🚀 Grok Beta',
230
+ 'grok-2-latest': '🚀 Grok 2',
231
+ 'gemini-2.0-flash-exp': '✨ Gemini 2.0 Flash',
232
+ 'gemini-1.5-pro': '✨ Gemini 1.5 Pro',
233
+ 'deepseek-chat': '🧠 DeepSeek Chat',
234
+ 'deepseek-reasoner': '🧠 DeepSeek Reasoner'
235
+ }