leilaghomashchi commited on
Commit
35bd94d
·
verified ·
1 Parent(s): 65060ff

Upload llma3_3-70b_with_chatgpt_fixed (2).py

Browse files
Files changed (1) hide show
  1. llma3_3-70b_with_chatgpt_fixed (2).py +424 -0
llma3_3-70b_with_chatgpt_fixed (2).py ADDED
@@ -0,0 +1,424 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+
4
+ import requests
5
+ import json
6
+ import gradio as gr
7
+ import logging
8
+ from typing import Dict, Any, Tuple
9
+ import os
10
+ from dataclasses import dataclass
11
+ import re
12
+ from difflib import SequenceMatcher
13
+
14
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
+ logger = logging.getLogger(__name__)
16
+
17
+
18
+ @dataclass
19
+ class CerebrasConfig:
20
+ api_key: str
21
+ base_url: str = "https://api.cerebras.ai/v1"
22
+ model: str = "llama-3.3-70b"
23
+ max_tokens: int = 2000
24
+ temperature: float = 0.1
25
+
26
+
27
+ class AdvancedCerebrasAnonymizer:
28
+
29
+ def __init__(self, api_key: str = None, openai_api_key: str = None):
30
+ if api_key is None:
31
+ api_key = os.getenv("CEREBRAS_API_KEY")
32
+ if not api_key:
33
+ raise ValueError("❌ کلید API Cerebras یافت نشد")
34
+
35
+ self.config = CerebrasConfig(api_key=api_key)
36
+ self.openai_api_key = openai_api_key or os.getenv("OPENAI_API_KEY", "")
37
+ self.system_prompt = self._create_system_prompt()
38
+ self.mapping_table = {}
39
+ logger.info("✅ سیستم آماده شد")
40
+
41
+ def _create_system_prompt(self) -> str:
42
+ return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
43
+
44
+ ## **قوانین اندیس‌گذاری:**
45
+ - شرکت‌ها: company-01, company-02, company-03, ...
46
+ - اشخاص: person-01, person-02, person-03, ...
47
+ - اعداد: amount-01, amount-02, amount-03, ...
48
+ - درصدها: percent-01, percent-02, percent-03, ...
49
+
50
+ ## **مهم:**
51
+ - اگر "ایران‌خودرو" اول‌بار company-01 شد، در تمام متن همان باشد
52
+ - هر شرکت، شخص، عدد یا درصد یکی از کدها باشد"""
53
+
54
+ def anonymize_text(self, text: str) -> Dict[str, Any]:
55
+ try:
56
+ if not text or not text.strip():
57
+ return {"success": False, "error": "متن ورودی خالی است"}
58
+
59
+ logger.info(f"🔄 ناشناس‌سازی... ({len(text)} کاراکتر)")
60
+
61
+ headers = {
62
+ "Authorization": f"Bearer {self.config.api_key}",
63
+ "Content-Type": "application/json"
64
+ }
65
+
66
+ payload = {
67
+ "model": self.config.model,
68
+ "messages": [
69
+ {"role": "system", "content": self.system_prompt},
70
+ {"role": "user", "content": f"لطفاً این متن را ناشناس‌سازی کنید:\n\n{text}"}
71
+ ],
72
+ "max_tokens": self.config.max_tokens,
73
+ "temperature": self.config.temperature
74
+ }
75
+
76
+ response = requests.post(
77
+ f"{self.config.base_url}/chat/completions",
78
+ headers=headers,
79
+ json=payload,
80
+ timeout=60
81
+ )
82
+
83
+ if response.status_code != 200:
84
+ return {"success": False, "error": f"خطای API: {response.status_code}"}
85
+
86
+ result = response.json()
87
+ anonymized_text = result["choices"][0]["message"]["content"]
88
+ usage = result.get("usage", {})
89
+
90
+ self._extract_mapping_advanced(text, anonymized_text)
91
+
92
+ logger.info(f"✅ ناشناس‌سازی موفق ({len(self.mapping_table)} موجودیت)")
93
+
94
+ return {
95
+ "success": True,
96
+ "anonymized_text": anonymized_text,
97
+ "usage": usage
98
+ }
99
+
100
+ except Exception as e:
101
+ logger.error(f"❌ خطا: {str(e)}")
102
+ return {"success": False, "error": str(e)}
103
+
104
+ def _extract_mapping_advanced(self, original: str, anonymized: str):
105
+ """استخراج mapping دقیق با مقایسه متن‌ها"""
106
+ self.mapping_table = {}
107
+
108
+ try:
109
+ # تقسیم به جملات
110
+ orig_lines = re.split(r'([.!?])', original)
111
+ anon_lines = re.split(r'([.!?])', anonymized)
112
+
113
+ for orig_line, anon_line in zip(orig_lines, anon_lines):
114
+ if not orig_line.strip():
115
+ continue
116
+
117
+ # تقسیم به کلمات
118
+ orig_words = orig_line.split()
119
+ anon_words = anon_line.split()
120
+
121
+ # یافتن تطابقات
122
+ i = 0
123
+ j = 0
124
+
125
+ while i < len(orig_words) and j < len(anon_words):
126
+ orig_word = orig_words[i]
127
+ anon_word = anon_words[j]
128
+
129
+ # اگر کدی است
130
+ if re.match(r'(company|person|amount|percent)-\d+', anon_word):
131
+ # سعی کن تا 3 کلمه قبل را پیدا کن
132
+ found = False
133
+
134
+ for phrase_len in range(min(4, len(orig_words) - i), 0, -1):
135
+ phrase = ' '.join(orig_words[i:i+phrase_len])
136
+
137
+ # بررسی اینکه آیا این عبارت منطقی است
138
+ if self._is_valid_entity(phrase, anon_word):
139
+ if phrase not in self.mapping_table:
140
+ self.mapping_table[phrase] = anon_word
141
+ logger.info(f" {phrase} → {anon_word}")
142
+ i += phrase_len
143
+ found = True
144
+ break
145
+
146
+ if not found:
147
+ i += 1
148
+ j += 1
149
+ else:
150
+ i += 1
151
+ j += 1
152
+
153
+ except Exception as e:
154
+ logger.warning(f"⚠️ خطا در mapping: {str(e)}")
155
+
156
+ def _is_valid_entity(self, phrase: str, code: str) -> bool:
157
+ """بررسی صحت موجودیت"""
158
+ phrase_lower = phrase.lower()
159
+
160
+ if code.startswith('company'):
161
+ return any(word in phrase_lower for word in ['شرکت', 'بانک', 'سازمان', 'وزارت', 'گروه'])
162
+ elif code.startswith('person'):
163
+ return len(phrase) > 2
164
+ elif code.startswith('amount'):
165
+ return bool(re.search(r'[\d]+', phrase))
166
+ elif code.startswith('percent'):
167
+ return 'درصد' in phrase_lower or '%' in phrase
168
+
169
+ return False
170
+
171
+ def send_to_chatgpt(self, anonymized_text: str) -> Dict[str, Any]:
172
+ try:
173
+ if not anonymized_text or not anonymized_text.strip():
174
+ return {"success": False, "error": "متن ناشناس‌شده خالی است"}
175
+
176
+ if not self.openai_api_key:
177
+ return {"success": False, "error": "کلید OpenAI تنظیم نشده"}
178
+
179
+ logger.info("🤖 ارسال به ChatGPT...")
180
+
181
+ headers = {
182
+ "Authorization": f"Bearer {self.openai_api_key}",
183
+ "Content-Type": "application/json"
184
+ }
185
+
186
+ data = {
187
+ "model": "gpt-4o-mini",
188
+ "messages": [
189
+ {"role": "system", "content": "شما یک تحلیلگر مالی حرفه‌ای هستید."},
190
+ {"role": "user", "content": anonymized_text}
191
+ ],
192
+ "max_tokens": 2000,
193
+ "temperature": 0.7
194
+ }
195
+
196
+ response = requests.post(
197
+ "https://api.openai.com/v1/chat/completions",
198
+ headers=headers,
199
+ json=data,
200
+ timeout=30
201
+ )
202
+
203
+ if response.status_code == 200:
204
+ result = response.json()
205
+ return {"success": True, "response": result['choices'][0]['message']['content']}
206
+ else:
207
+ error_data = response.json() if response.content else {}
208
+ error_message = error_data.get('error', {}).get('message', response.text)
209
+ return {"success": False, "error": error_message}
210
+
211
+ except Exception as e:
212
+ logger.error(f"❌ خطا: {str(e)}")
213
+ return {"success": False, "error": str(e)}
214
+
215
+ def _create_reverse_mapping(self, mapping_table: Dict[str, str]) -> Dict[str, str]:
216
+ """ایجاد reverse mapping: code -> original"""
217
+ reverse_map = {}
218
+ for original, code in mapping_table.items():
219
+ reverse_map[code] = original
220
+ return reverse_map
221
+
222
+ def deanonymize_response(self, gpt_response: str, mapping_table: Dict[str, str]) -> str:
223
+ """بازگردانی تمام موجودیت‌ها (placeholder‌ها و کد‌های ساده را پشتیبانی می‌کند)"""
224
+ try:
225
+ if not mapping_table:
226
+ return gpt_response
227
+
228
+ result = gpt_response
229
+
230
+ # ایجاد reverse mapping برای تبدیل code به original
231
+ reverse_map = self._create_reverse_mapping(mapping_table)
232
+
233
+ # مرتب‌سازی بر اساس طول (طولانی‌ترین اول)
234
+ sorted_reverse = sorted(reverse_map.items(), key=lambda x: len(x[0]), reverse=True)
235
+
236
+ logger.info(f"🔄 شروع بازگردانی {len(sorted_reverse)} موجودیت")
237
+
238
+ for code, original in sorted_reverse:
239
+ # الگوهای مختلف برای جستجو:
240
+ # 1. [code] (داخل براکت)
241
+ pattern_bracket = f"[{code}]"
242
+ if pattern_bracket in result:
243
+ result = result.replace(pattern_bracket, original)
244
+ logger.info(f" [{code}] → {original} ✓")
245
+
246
+ # 2. code بدون براکت
247
+ if code in result:
248
+ result = result.replace(code, original)
249
+ logger.info(f" {code} → {original} ✓")
250
+
251
+ # جایگزینی نسخه‌های دارای درصد و هزار جدا شده
252
+ result = self._replace_special_patterns(result, reverse_map)
253
+
254
+ logger.info(f"✅ بازگردانی موفق ({len(mapping_table)} موجودیت)")
255
+ return result
256
+
257
+ except Exception as e:
258
+ logger.error(f"❌ خطا در بازگردانی: {str(e)}")
259
+ return gpt_response
260
+
261
+ def _replace_special_patterns(self, text: str, reverse_map: Dict[str, str]) -> str:
262
+ """جایگزینی الگوهای خاص (درصد-XX، amount-XX و غیره) - شامل placeholder‌های بدون براکت"""
263
+ try:
264
+ result = text
265
+
266
+ # الگوی 1: Placeholder‌های با براکت [code]
267
+ pattern_bracket = r'\[((?:company|person|amount|percent|درصد|شرکت|مبلغ)-\d+)\]'
268
+
269
+ def replacer_bracket(match):
270
+ code = match.group(1)
271
+ # تبدیل الگوهای فارسی به انگلیسی
272
+ code_normalized = code.replace('درصد', 'percent').replace('شرکت', 'company').replace('مبلغ', 'amount')
273
+ return reverse_map.get(code_normalized, reverse_map.get(code, match.group(0)))
274
+
275
+ result = re.sub(pattern_bracket, replacer_bracket, result)
276
+ logger.info(f" الگوی 1 (با براکت): جایگزین شد")
277
+
278
+ # الگوی 2: Placeholder‌های داخل پرانتز (code)
279
+ pattern_paren = r'\(((?:company|person|amount|percent|درصد|شرکت|مبلغ)-\d+)\)'
280
+
281
+ def replacer_paren(match):
282
+ code = match.group(1)
283
+ code_normalized = code.replace('درصد', 'percent').replace('شرکت', 'company').replace('مبلغ', 'amount')
284
+ return f"({reverse_map.get(code_normalized, reverse_map.get(code, code))})"
285
+
286
+ result = re.sub(pattern_paren, replacer_paren, result)
287
+ logger.info(f" الگوی 2 (پرانتز): جایگزین شد")
288
+
289
+ # الگوی 3: Placeholder‌های بدون براکت (بسیار مهم!)
290
+ # جایگزینی تمام code-XX که با word boundary احاطه شده‌اند
291
+ pattern_simple = r'\b((?:company|person|amount|percent|درصد|شرکت|مبلغ)-\d+)\b'
292
+
293
+ def replacer_simple(match):
294
+ code = match.group(1)
295
+ code_normalized = code.replace('درصد', 'percent').replace('شرکت', 'company').replace('مبلغ', 'amount')
296
+ replacement = reverse_map.get(code_normalized, reverse_map.get(code, None))
297
+ if replacement:
298
+ logger.info(f" {code} → {replacement}")
299
+ return replacement
300
+ return match.group(0)
301
+
302
+ result = re.sub(pattern_simple, replacer_simple, result)
303
+ logger.info(f" الگوی 3 (بدون براکت): جایگزین شد")
304
+
305
+ return result
306
+
307
+ except Exception as e:
308
+ logger.warning(f"⚠️ خطا در جایگزینی الگوهای خاص: {str(e)}")
309
+ return text
310
+
311
+
312
+ def create_interface():
313
+ try:
314
+ anonymizer = AdvancedCerebrasAnonymizer()
315
+ except ValueError as e:
316
+ return gr.Interface(fn=lambda x: str(e), inputs="textbox", outputs="textbox", title="❌ خطا")
317
+
318
+ def process_text(input_text: str) -> Tuple[str, str, str, str]:
319
+ logger.info("=" * 60)
320
+ logger.info("شروع پردازش")
321
+ logger.info("=" * 60)
322
+
323
+ if not input_text.strip():
324
+ return "❌ متن ورودی خالی است", "", "", ""
325
+
326
+ try:
327
+ # مرحله 1: ناشناس‌سازی
328
+ logger.info("1️⃣ ناشناس‌سازی...")
329
+ anon_result = anonymizer.anonymize_text(input_text)
330
+
331
+ if not anon_result["success"]:
332
+ return f"❌ {anon_result['error']}", "", "", ""
333
+
334
+ anonymized_text = anon_result["anonymized_text"]
335
+ usage_info = anon_result.get("usage", {})
336
+
337
+ # مرحله 2: ارسال به ChatGPT
338
+ logger.info("2️⃣ ارسال به ChatGPT...")
339
+ gpt_result = anonymizer.send_to_chatgpt(anonymized_text)
340
+
341
+ # تعریف اولیه
342
+ gpt_response = ""
343
+ gpt_response_deanon = ""
344
+
345
+ if not gpt_result["success"]:
346
+ gpt_response = f"❌ {gpt_result['error']}"
347
+ gpt_response_deanon = ""
348
+ else:
349
+ # پاسخ ChatGPT (ناشناس)
350
+ gpt_response = gpt_result["response"]
351
+
352
+ # مرحله 3: بازگردانی
353
+ logger.info("3️⃣ بازگردانی...")
354
+ gpt_response_deanon = anonymizer.deanonymize_response(gpt_response, anonymizer.mapping_table)
355
+
356
+ # آمار
357
+ stats = f"""Token: {usage_info.get('total_tokens', '?')} | Mapping: {len(anonymizer.mapping_table)}"""
358
+
359
+ logger.info("=" * 60)
360
+ logger.info("✅ پردازش کامل")
361
+ logger.info("=" * 60)
362
+
363
+ # ✅ ترتیب صحیح:
364
+ # 1. آمار
365
+ # 2. متن ناشناس‌شده (ورودی)
366
+ # 3. پاسخ ChatGPT (ناشناس) ← باکس "🤖 نتایج ChatGPT"
367
+ # 4. نتیجه نهایی (بازگردانی شده) ← باکس "✅ نتیجه نهایی"
368
+ return stats, anonymized_text, gpt_response, gpt_response_deanon
369
+
370
+ except Exception as e:
371
+ return f"❌ خطا: {str(e)}", "", "", ""
372
+
373
+ def copy_text(text: str):
374
+ if not text or not text.strip():
375
+ return gr.update(visible=False), "⚠️ متنی وجود ندارد"
376
+ return gr.update(value=text, visible=True), "✅ آماده برای کپی"
377
+
378
+ def clear_all():
379
+ anonymizer.mapping_table = {}
380
+ return "", "", "", "", gr.update(visible=False)
381
+
382
+ with gr.Blocks(title="سیستم ناشناس‌سازی", theme=gr.themes.Soft()) as interface:
383
+ gr.HTML("<h1 style='text-align: center; color: #FFD700;'>🔐 سیستم ناشناس‌سازی</h1>")
384
+
385
+ with gr.Row():
386
+ # ستون 1
387
+ with gr.Column(scale=1):
388
+ gr.HTML("<h2>📥 ورودی</h2>")
389
+ input_text = gr.Textbox(lines=20, placeholder="متن را وارد کنید...", label="", rtl=True)
390
+
391
+ process_btn = gr.Button("🚀 پردازش", variant="primary", size="lg")
392
+ with gr.Row():
393
+ copy_btn = gr.Button("📋 کپی", scale=1)
394
+ clear_btn = gr.Button("🗑️ پاک", variant="stop", scale=1)
395
+
396
+ copy_output = gr.Textbox(visible=False)
397
+
398
+ # ستون 2
399
+ with gr.Column(scale=1):
400
+ gr.HTML("<h2>🎭 متن ناشناس‌شده</h2>")
401
+ anonymized_output = gr.Textbox(lines=20, placeholder="", label="", interactive=False, rtl=True)
402
+
403
+ # ستون 3
404
+ with gr.Column(scale=1):
405
+ gr.HTML("<h2>🤖 نتایج ChatGPT</h2>")
406
+ gpt_output = gr.Textbox(lines=10, placeholder="", label="📤 پاسخ", interactive=False, rtl=True)
407
+ final_output = gr.Textbox(lines=10, placeholder="", label="✅ نتیجه نهایی", interactive=False, rtl=True)
408
+ statistics_output = gr.Textbox(lines=1, label="📊 آمار", interactive=False)
409
+
410
+ process_btn.click(fn=process_text, inputs=[input_text], outputs=[statistics_output, anonymized_output, gpt_output, final_output])
411
+ copy_btn.click(fn=copy_text, inputs=[final_output], outputs=[copy_output, statistics_output])
412
+ clear_btn.click(fn=clear_all, outputs=[input_text, anonymized_output, gpt_output, final_output, copy_output])
413
+
414
+ return interface
415
+
416
+
417
+ def main():
418
+ print("\n🔐 سیستم ناشناس‌سازی - نسخه 1.0.0\n")
419
+ interface = create_interface()
420
+ interface.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)
421
+
422
+
423
+ if __name__ == "__main__":
424
+ main()