apak commited on
Commit
2ea678c
·
verified ·
1 Parent(s): ecf728a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +212 -210
app.py CHANGED
@@ -1,86 +1,52 @@
1
  import torch
2
  import wikipedia
3
- from transformers import pipeline, AutoModelForCausalLM, AutoTokenizer
4
  import gradio as gr
5
  import os
6
- import threading
7
 
8
- # --- Global Durum ve Önbellek Tanımları ---
9
- _initial_logs = ["Sistem Başlatılıyor..."]
10
- # Dinamik olarak yüklenecek LLM boru hatları için önbellek
11
- LLM_PIPELINE_CACHE = {}
12
- # Önbellek kilidi (aynı anda birden fazla isteğin model yüklemeyi tetiklemesini önler)
13
- LLM_CACHE_LOCK = threading.Lock()
14
- CURRENT_LLM_MODEL = "Qwen/Qwen2.5-1.5B-Instruct" # Varsayılan model
15
 
16
  # --- 1. İlk Tarama Modeli (Hızlı NER) ---
17
- _initial_logs.append("1. Standart NER Modeli yükleniyor (XLM-RoBERTa)...")
18
- try:
19
- ner_pipe = pipeline(
20
- "ner",
21
- model="xlm-roberta-large-finetuned-conll03-english",
22
- aggregation_strategy="simple",
23
- # CUDA varsa 0. cihaza, yoksa CPU'ya yükle
24
- device=0 if torch.cuda.is_available() else -1
25
- )
26
- _initial_logs.append("✅ Standart NER Modeli Hazır!")
27
- except Exception as e:
28
- _initial_logs.append(f"❌ Hata: Standart NER modeli yüklenemedi: {e}")
29
- ner_pipe = None
30
-
31
- # --- LLM Yükleyici Fonksiyonu (Dinamik ve Önbellekli) ---
32
- def load_llm_pipeline(model_id):
33
- """Seçilen LLM'yi yükler veya önbellekten çeker."""
34
- global LLM_PIPELINE_CACHE
35
- global CURRENT_LLM_MODEL
36
-
37
- # Eğer model zaten önbellekte varsa, hemen geri dön
38
- if model_id in LLM_PIPELINE_CACHE:
39
- CURRENT_LLM_MODEL = model_id
40
- return LLM_PIPELINE_CACHE[model_id]
41
-
42
- # Model önbellekte yoksa, kilidi al ve yükle
43
- with LLM_CACHE_LOCK:
44
- # Kilidi aldıktan sonra tekrar kontrol et (başka bir iş parçacığı bu sırada yüklemiş olabilir)
45
- if model_id in LLM_PIPELINE_CACHE:
46
- CURRENT_LLM_MODEL = model_id
47
- return LLM_PIPELINE_CACHE[model_id]
48
-
49
- log_messages = [f"⚙️ LLM Yükleniyor: {model_id} (İlk yükleme yavaş olabilir. Lütfen bekleyiniz.)"]
50
- yield log_messages # Log mesajını Gradio'ya anlık gönder
51
-
52
- try:
53
- llm_model_kwargs = {}
54
- llm_device_map = "auto"
55
 
56
- # 7B/8B modeller için bellek optimizasyonu
57
- if torch.cuda.is_available():
58
- llm_model_kwargs["torch_dtype"] = torch.bfloat16
59
- log_messages.append(f" ℹ️ CUDA mevcut. Model GPU üzerinde bfloat16 ile yüklenecek.")
60
- else:
61
- log_messages.append(f" ⚠️ CUDA mevcut değil. Model CPU üzerinde yüklenecek. **ÇOK YAVAŞ OLABİLİR.**")
62
- llm_device_map = "cpu"
63
 
64
- gen_pipe = pipeline(
65
- "text-generation",
66
- model=model_id,
67
- model_kwargs=llm_model_kwargs,
68
- device_map=llm_device_map
69
- )
70
-
71
- # Başarılı yükleme
72
- LLM_PIPELINE_CACHE[model_id] = gen_pipe
73
- CURRENT_LLM_MODEL = model_id
74
- log_messages.append(f"✅ LLM ({model_id}) Başarıyla Yüklendi ve Önbelleğe Alındı.")
75
- yield log_messages # Başarılı yükleme mesajını gönder
76
 
77
- return gen_pipe
 
 
 
 
 
 
 
 
 
 
 
78
 
79
- except Exception as e:
80
- log_messages.append(f"❌ LLM yüklenirken bir HATA oluştu: {e}")
81
- log_messages.append(" ⚠️ Lütfen daha küçük bir model seçin veya yeterli kaynağa sahip olduğunuzdan emin olun.")
82
- yield log_messages # Hata mesajını gönder
83
- return None
84
 
85
  # --- Wikipedia Fonksiyonu ---
86
  wikipedia.set_lang("tr")
@@ -89,26 +55,28 @@ def get_wiki_summary(term):
89
  try:
90
  results = wikipedia.search(term)
91
  if not results: return None
 
92
  return wikipedia.summary(results[0], sentences=3, auto_suggest=False)
93
  except wikipedia.exceptions.PageError:
94
  return None
95
  except wikipedia.exceptions.RedirectError:
96
  return None
97
  except Exception as e:
 
98
  return None
99
 
100
  # --- LLM ile Etiket Rafine Etme Fonksiyonu ---
101
- def refine_label_with_llm(gen_pipe, entity_text, wiki_context, custom_label_definitions):
102
  """LLM kullanarak MISC etiketini özel etiketlerden biriyle rafine eder."""
103
  if gen_pipe is None:
104
- return ("MISC", "LLM Yüklenemediği için rafine edilemedi.")
105
 
106
  labels_str = ", ".join(custom_label_definitions.keys())
107
  label_definitions_str = "\n".join(
108
  [f"- {k}: {v}" for k, v in custom_label_definitions.items()]
109
  )
110
 
111
- # LLM için örnekler (Few-shot learning)
112
  few_shot_examples = """ÖRNEK 1 (NORP İÇİN):
113
  VARLIK: Türk
114
  BAĞLAM: Türkler, Türkiye Cumhuriyeti'nde yaşayan ve Türkçe konuşan büyük bir etnik gruptur. (NORP tanımı ile eşleşir.)
@@ -129,9 +97,16 @@ BAĞLAM: Halide Edib Adıvar'ın 1935'te yayınlanan ve popüler kültürde öne
129
  GÖZLEM: 'Sinekli Bakkal' yazar adı ve yayın tarihi ile birlikte bir 'roman' olarak anılıyor. Bağlamda 'romanıdır' ifadesi geçiyor.
130
  AKIL YÜRÜTME: Yazılı veya basılı eserleri, romanları ve yayınları tanımlayan etiket 'BOOK'tur. Bu tanıma uymaktadır.
131
  CEVAP: BOOK
 
 
 
 
 
 
 
132
  """
133
 
134
- # --- Chain of Thought (CoT) Prompt Yapısı ---
135
  prompt = f"""Sen uzman bir veri sınıflandırma sistemisin.
136
  Görevin, aşağıdaki varlığı ve bağlamı analiz ederek, **YALNIZCA** ETİKET TANIMLARI'ndan birini (veya uymuyorsa MISC'i) seçmektir.
137
 
@@ -159,7 +134,7 @@ CEVAP: [SEÇİLEN ETİKET]
159
  # LLM çağrısı
160
  outputs = gen_pipe(
161
  messages,
162
- max_new_tokens=150,
163
  do_sample=False,
164
  temperature=0.1
165
  )
@@ -167,151 +142,154 @@ CEVAP: [SEÇİLEN ETİKET]
167
  # LLM çıktısını analiz etme
168
  full_output = outputs[0]["generated_text"][-1]["content"].strip()
169
  final_label = "MISC"
 
 
170
  cog_output = ""
171
 
172
  try:
 
 
173
  cevap_satiri = [line for line in full_output.split('\n') if 'CEVAP:' in line][-1]
174
  raw_label = cevap_satiri.split('CEVAP:')[-1].strip().upper()
175
 
 
 
176
  akil_yurutme = [line for line in full_output.split('\n') if 'AKIL YÜRÜTME:' in line]
177
- if akil_yurutme:
 
 
178
  cog_output = akil_yurutme[-1].replace('AKIL YÜRÜTME:', '').strip()
179
 
180
- valid_labels = list(custom_label_definitions.keys())
 
181
  if raw_label in valid_labels:
182
  final_label = raw_label
183
-
 
 
 
184
  except IndexError:
 
185
  final_label = "MISC"
186
- cog_output = "LLM Çıktısı Beklenmeyen Formatta."
187
 
188
- return (final_label, cog_output)
189
-
190
- # --- Özel Etiket Tanımları (23 Etikete Genişletildi) ---
191
- custom_label_definitions = {
192
- "PER": "Kişi adları, takma adlar, ünlüler.", "ORG": "Şirketler, kurumlar, hükümet kuruluşları.",
193
- "LOC": "Coğrafi yerler, siyasi bölgeler, binalar.", "DATE": "Mutlak veya göreceli tarih ifadeleri.",
194
- "TIME": "Günün saati, zaman aralığı.", "MONEY": "Parasal değerler, para birimleri.",
195
- "QUANTITY": "Ağırlık, uzunluk, hacim gibi ölçü birimleri ve sayısal değerler.",
196
- "PERCENT": "Yüzdelik ifadeler.", "NORP": "Milliyetler, etnik, dini veya politik gruplar.",
197
- "LAW": "Resmi kanun, yasa, yönetmelik veya hukuki belge adı.",
198
- "EVENT": "Savaşlar, festivaller, spor turnuvaları, doğal afetler veya kurumsal etkinlikler.",
199
- "BOOK": "Yazılı veya basılı bir eser, yayınlanmış roman, ders kitabı.",
200
- "MOVIE": "Sinema filmi, dizi, belgesel gibi görsel-işitsel yapıt.",
201
- "SONG": "Müzik eseri, şarkı, beste veya albüm adı.",
202
- "ART": "Resim, heykel, spesifik mimari eser adı.", "AWARD": "Ödül, madalya veya nişan adı.",
203
- "PRODUCT": "Ticari olarak satılan somut bir eşya, model, cihaz veya marka serisi.",
204
- "SOFTWARE": "Bilgisayar programları, mobil uygulamalar, yapay zeka sistemleri, işletim sistemleri.",
205
- "ORG_SUB": "Şirket birimleri, üniversite fakülteleri, dernek şubeleri gibi büyük bir kurumun alt birimleri.",
206
- "LANGUAGE": "Dillerin adı.", "TITLE": "Kişinin unvanı, rütbesini veya pozisyonunu.",
207
- "CYBER": "URL, E-posta adresi, IP adresi, hashtag veya kullanıcı adı.",
208
- "MISC": "Diğer adlandırılmış varlıklar (LLM tarafından rafine edilemeyen veya uymayanlar)."
209
- }
210
-
211
 
212
- # --- Ana İşlem Fonksiyonu ---
213
- def advanced_ner_pipeline(text, llm_model_name, target_labels, progress=gr.Progress()):
214
  """
215
  Standart NER modelini çalıştırır, MISC etiketli varlıklar için
216
  Wikipedia ve LLM kullanarak zenginleştirme (RAG) yapar.
217
  """
218
- # LLM'i yüklemeyi deneme ve logları güncelleme
219
- llm_loader_generator = load_llm_pipeline(llm_model_name)
220
 
221
- # Yükleyici bir jeneratör olduğu için, ilk logları ve ardından pipeline objesini almalıyız.
222
- llm_pipe = None
223
- all_logs = list(_initial_logs)
224
-
225
- # Yükleme aşamasındaki logları döndür
226
- for logs_step in llm_loader_generator:
227
- if isinstance(logs_step, list): # Eğer log listesi döndüyse
228
- all_logs.extend(logs_step)
229
- yield all_logs, None # Logları göster
230
- else: # Pipeline objesi döndüyse
231
- llm_pipe = logs_step
232
-
233
- # Eğer yükleme sırasında hata olduysa ve llm_pipe hala None ise, dur
234
- if llm_pipe is None and llm_model_name not in LLM_PIPELINE_CACHE:
235
- all_logs.append("❌ LLM Yüklenemediği için zenginleştirme (RAG) devre dışı.")
236
- # Devam etmeden önce son logları göster
237
- yield all_logs, None
238
- # llm_pipe'ın None olması durumunda aşağıda RAG yapılmayacak.
239
-
240
- # Temel NER modelini çalıştır
241
- if ner_pipe is None:
242
- all_logs.append("❌ Temel NER boru hattı kullanılamıyor.")
243
- yield all_logs, None
244
- return
245
-
246
  try:
 
 
247
  initial_results = ner_pipe(text)
248
  except Exception as e:
249
- all_logs.append(f"❌ Hata: Temel NER modelinde sorun oluştu: {e}")
250
- yield all_logs, []
251
  return
252
 
253
  final_results = []
254
  total_entities = len(initial_results)
255
 
256
- all_logs.append(f"🕵️‍♀️ Toplam {total_entities} varlık inceleniyor...")
257
- yield all_logs, None # Log mesajını gönder
 
 
 
258
 
259
  for i, entity in enumerate(initial_results):
260
  word = entity['word']
261
  label = entity['entity_group']
262
- score = entity['score']
 
263
 
 
264
  current_progress = (i + 1) / total_entities
 
265
  progress(current_progress, desc=f"İşleniyor: {word}")
266
 
267
  result_obj = {
268
  "entity": word,
269
  "initial_label": label,
270
- "initial_score": score,
271
  "final_label": label,
272
  "source": "Model",
273
- "reasoning": ""
274
  }
275
 
276
- # Eğer etiket MISC ise ve LLM yüklü ise RAG sürecini başlat
277
- if label == "MISC" and llm_pipe is not None:
 
278
  standardized_word = word.title()
279
- all_logs.append(f" ⚠️ MISC tespit edildi: '{word}' (Skor: {score:.2f}). Wikipedia'ya soruluyor...")
280
- yield all_logs, final_results # Geçici logları ve sonuçları gönder
281
 
282
  wiki_context = get_wiki_summary(standardized_word)
283
 
284
  if wiki_context:
285
- all_logs.append(f" 📄 Wiki Bağlamı bulundu: {wiki_context[:50]}...")
286
- yield all_logs, final_results
287
 
288
  # LLM'den hem etiketi hem de gerekçeyi al
289
- new_label, reasoning = refine_label_with_llm(llm_pipe, standardized_word, wiki_context, target_labels)
290
 
291
  result_obj["final_label"] = new_label
292
- result_obj["source"] = f"RAG+LLM ({llm_model_name.split('/')[-1]})"
293
- result_obj["reasoning"] = reasoning
294
 
295
- all_logs.append(f" 🔄 Etiket Güncellendi: MISC -> {new_label}")
296
  if reasoning:
297
- all_logs.append(f" 💡 Akıl Yürütme: {reasoning[:70]}...")
298
- yield all_logs, final_results
299
  else:
300
- all_logs.append(" ❌ Wiki'de bilgi bulunamadı, MISC olarak kalıyor.")
301
- yield all_logs, final_results
302
  else:
303
- all_logs.append(f" ✅ {word} için etiket: {label} (Skor: {score:.2f})")
304
- yield all_logs, final_results
305
 
306
  final_results.append(result_obj)
307
 
308
- all_logs.append("✅ İşlem tamamlandı! Nihai sonuçlar tabloda.")
309
- # Nihai sonuçlar ve loglar
310
- yield all_logs, final_results
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
311
 
312
- # --- Gradio Arayüzü İçin Wrapper Fonksiyonu ---
313
- # Gradio bu jeneratör fonksiyonunu otomatik olarak işler.
314
- def process_ner_request(text, llm_model_name, progress=gr.Progress()):
315
  """
316
  Gradio arayüzünden çağrılan ana fonksiyondur.
317
  advanced_ner_pipeline'dan gelen logları ve sonuçları HTML olarak biçimlendirir.
@@ -319,79 +297,106 @@ def process_ner_request(text, llm_model_name, progress=gr.Progress()):
319
  all_logs = []
320
  final_results = None
321
 
322
- # Jeneratörden gelen her adımda UI'yı güncelle
323
- for logs_step, results_step in advanced_ner_pipeline(text, llm_model_name, custom_label_definitions, progress=progress):
324
- all_logs = logs_step
325
 
326
- # Eğer sonuçlar (tablo verisi) varsa, sonuçları güncelle
327
  if results_step is not None:
328
  final_results = results_step
329
 
330
  # --- Geçici Log Çıktısı Oluşturma ---
331
  log_output_html = "<div style='max-height: 200px; overflow-y: scroll; border: 1px solid #eee; padding: 10px; margin-bottom: 10px; background-color: #f9f9f9; border-radius: 8px; font-family: monospace; font-size: 12px;'>"
332
  for log in all_logs:
 
333
  color = 'blue' if '✅' in log else ('orange' if '⚠️' in log else ('red' if '❌' in log else 'black'))
334
  log_output_html += f"<p style='margin: 0; color: {color};'>{log.replace(' ', '&nbsp;&nbsp;&nbsp;')}</p>"
335
  log_output_html += "</div>"
336
 
337
  # --- Geçici/Nihai Sonuç Tablosu Oluşturma ---
338
  current_results_html = ""
339
- if final_results:
 
 
340
  current_results_html = """
341
  <style>
342
  .ner-table {
343
- width:100%; border-collapse: collapse; font-family: Arial, sans-serif;
344
- border-radius: 8px; overflow: hidden; box-shadow: 0 4px 6px rgba(0,0,0,0.1);
 
 
 
 
345
  }
346
  .ner-table th, .ner-table td {
347
- padding: 12px 15px; text-align: left; border-bottom: 1px solid #ddd;
 
 
348
  }
349
  .ner-table th {
350
- background-color: #3b82f6; color: white; font-weight: bold; text-transform: uppercase;
 
 
 
351
  }
352
  .ner-table tr:nth-child(even) {
353
- background-color: #f3f4f6;
354
  }
355
  .ner-table tr:hover {
356
- background-color: #e5e7eb;
357
  }
358
  .tooltip-text {
359
- visibility: hidden; background-color: #333; color: #fff; text-align: center;
360
- border-radius: 6px; padding: 5px 10px; position: absolute; z-index: 10;
361
- bottom: 125%; left: 50%; transform: translateX(-50%); opacity: 0;
362
- transition: opacity 0.3s; width: 300px; font-size: 11px; line-height: 1.4;
 
 
 
 
 
 
 
 
 
 
 
 
363
  }
364
  .tooltip-container:hover .tooltip-text {
365
- visibility: visible; opacity: 1;
 
366
  }
367
  .tooltip-container {
368
- position: relative; display: inline-block;
 
369
  }
370
  </style>
371
  <table class='ner-table'>\n"""
372
  current_results_html += " <tr>\n"
373
  current_results_html += " <th>VARLIK</th>\n"
374
  current_results_html += " <th>İLK ETİKET</th>\n"
375
- current_results_html += " <th>İLK TAHMİN SKORU</th>\n"
376
  current_results_html += " <th>RAG SONRASI ETİKETİ (GEREKÇE)</th>\n"
377
  current_results_html += " <th>KAYNAK</th>\n"
378
  current_results_html += " </tr>\n"
379
-
380
  for item in final_results:
 
381
  color_map = {
382
  "PER": "background-color: #f8c291;", "LOC": "background-color: #a2c4c9;", "ORG": "background-color: #b3c99f;",
383
- "MISC": "background-color: #fef08a;", "DATE": "background-color: #e5ccff;", "TIME": "background-color: #d1d5db;",
384
- "MONEY": "background-color: #fcd34d;", "QUANTITY": "background-color: #bfdbfe;", "PERCENT": "background-color: #99f6e4;",
385
- "NORP": "background-color: #fbcfe8;", "LAW": "background-color: #f0abfc;", "EVENT": "background-color: #a7f3d0;",
386
- "BOOK": "background-color: #ffedd5;", "MOVIE": "background-color: #c7d2fe;", "SONG": "background-color: #e9d5ff;",
387
- "ART": "background-color: #bae6fd;", "AWARD": "background-color: #fee2e2;", "PRODUCT": "background-color: #ffc999;",
388
- "SOFTWARE": "background-color: #d1fae5;", "ORG_SUB": "background-color: #ccfbf1;", "LANGUAGE": "background-color: #fef9c3;",
389
- "TITLE": "background-color: #fecaca;", "CYBER": "background-color: #dbeafe;"
 
390
  }
391
  final_label_style = color_map.get(item['final_label'], "")
392
 
 
393
  reasoning_tooltip = ""
394
  if item.get("reasoning"):
 
395
  reasoning_tooltip = f"""
396
  <div class='tooltip-container'>
397
  {item['final_label']}
@@ -403,44 +408,40 @@ def process_ner_request(text, llm_model_name, progress=gr.Progress()):
403
  else:
404
  reasoning_tooltip = item['final_label']
405
 
 
406
  score_formatted = f"{item['initial_score'] * 100:.2f}%"
407
- score_color = "color: #10b981;" if item['initial_score'] > 0.95 else ("color: #f59e0b;" if item['initial_score'] > 0.8 else "color: #ef4444;")
 
 
 
 
 
 
 
 
408
 
409
  current_results_html += " <tr>\n"
410
  current_results_html += f" <td>{item['entity']}</td>\n"
411
  current_results_html += f" <td>{item['initial_label']}</td>\n"
412
- current_results_html += f" <td style='{score_color} font-weight: bold;'>{score_formatted}</td>\n"
413
  current_results_html += f" <td style='{final_label_style}'>{reasoning_tooltip}</td>\n"
414
  current_results_html += f" <td>{item['source']}</td>\n"
415
  current_results_html += " </tr>\n"
416
  current_results_html += "</table>"
417
 
 
418
  yield log_output_html, current_results_html
419
 
420
  # --- Gradio Arayüzünü Tanımla ---
421
- # Modelleri dropdown'a ekleme
422
- llm_choices = [
423
- ("Qwen 1.5B Instruct ", "Qwen/Qwen2.5-1.5B-Instruct"),
424
- ("Mistral 7B Instruct ", "mistralai/Mistral-Large-3-675B-Instruct-2512"),
425
- ("Llama 3 8B Instruct ", "meta-llama/Llama-3.1-8B-Instruct"),
426
- ("Gemma-3-4b-it", "google/gemma-3-4b-it")
427
- ]
428
-
429
  iface = gr.Interface(
430
  fn=process_ner_request,
431
- inputs=[
432
- gr.Textbox(lines=5, placeholder="Metin giriniz...", label="Giriş Metni"),
433
- gr.Dropdown(
434
- choices=llm_choices,
435
- value=llm_choices[0][1], # Varsayılan: Qwen 1.5B
436
- label="RAG/Akıl Yürütme İçin LLM Seçimi (Yüksek kaynak modelleri yavaş yüklenebilir!)",
437
- info="Seçilen model, MISC etiketlerini rafine etmek için kullanılacaktır."
438
- )
439
- ],
440
- outputs=[gr.HTML(label="İşlem Logları"), gr.HTML(label="İyileştirilmiş NER Sonuçları")],
441
- title="Project BETA",
442
- #description="Metindeki varlıkları tanır, Wikipedia (RAG) kullanarak bağlamı bulur ve seçtiğiniz LLM (Akıl Yürütme Zinciri) ile etiketleri 23 ince taneli kategoriye rafine eder. İlk modelin tahmin skorları da tabloda gösterilmektedir.",
443
  examples=[
 
444
  ["Milli Eğitim Bakanlığı'na bağlı Lise Birimleri, 2024 Türkiye Kupası etkinliğine katılacak ve %15 indirim uygulayacak."],
445
  ["General Vural, Türkçe dilini kullanan Türk askerlerini, https://example.com üzerinden uyardı. 'Hürriyet Kasidesi' eserini okudu."],
446
  ["Windows 11 işletim sistemi, 1000 TL karşılığında satışa sunulmuştur. 'Cumhurbaşkanı' unvanına sahip kişi, Kanun maddesini değiştirdi."],
@@ -450,5 +451,6 @@ iface = gr.Interface(
450
 
451
  # --- Gradio Uygulamasını Başlat ---
452
  if __name__ == "__main__":
 
453
  port = int(os.environ.get('PORT', 7860))
454
  iface.launch(share=False, server_port=port)
 
1
  import torch
2
  import wikipedia
3
+ from transformers import pipeline
4
  import gradio as gr
5
  import os
 
6
 
7
+ # --- Global Log Listesini Tanımla ---
8
+ # Başlangıçtaki model yükleme mesajlarını Gradio UI'a aktarmak için bu listeyi kullanıyoruz.
9
+ _initial_logs = []
 
 
 
 
10
 
11
  # --- 1. İlk Tarama Modeli (Hızlı NER) ---
12
+ # XLM-Roberta çok dillidir ve standart olarak PER, LOC, ORG, MISC etiketleri üretir.
13
+ _initial_logs.append("1. Standart NER Modeli yükleniyor...")
14
+ # NER modelini yüklerken, cihazı açıkça belirterek Gradio'nun başlangıç aşamalarında oluşabilecek hataları azaltıyoruz.
15
+ ner_pipe = pipeline(
16
+ "ner",
17
+ model="xlm-roberta-large-finetuned-conll03-english",
18
+ aggregation_strategy="simple",
19
+ device=0 if torch.cuda.is_available() else -1
20
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
+ # --- 2. Akıl Yürütme Modeli (LLM - RAG Karar Verici) ---
23
+ _initial_logs.append("2. LLM (Karar Verici) yükleniyor...")
24
+ model_id = "Qwen/Qwen2.5-1.5B-Instruct" # Türkçe yeteneği iyi ve hızlı
25
+ llm_model_kwargs = {}
 
 
 
26
 
27
+ # Cihaz ayarlarını yapma
28
+ if torch.cuda.is_available():
29
+ llm_model_kwargs["torch_dtype"] = torch.bfloat16
30
+ llm_device_map = "auto"
31
+ _initial_logs.append("CUDA desteği bulundu, model GPU üzerinde bfloat16 ile yüklenecek.")
32
+ else:
33
+ _initial_logs.append("CUDA desteği bulunamadı, model CPU üzerinde float32 ile yüklenecek.")
34
+ llm_device_map = "cpu" # Açıkça CPU'ya ayarlandı
 
 
 
 
35
 
36
+ try:
37
+ gen_pipe = pipeline(
38
+ "text-generation",
39
+ model=model_id,
40
+ model_kwargs=llm_model_kwargs,
41
+ device_map=llm_device_map
42
+ )
43
+ except Exception as e:
44
+ _initial_logs.append(f"❌ LLM yüklenirken bir hata oluştu (Muhtemelen Bellek Yetersizliği/OOM): {e}")
45
+ # Olası bir çökme durumunu önlemek için alternatif bir çözüm ekleyin
46
+ gen_pipe = None
47
+ _initial_logs.append("LLM boru hattı devre dışı bırakıldı. Uygulama sadece temel NER yapacaktır.")
48
 
49
+ _initial_logs.append("✅ Modeller Hazır!")
 
 
 
 
50
 
51
  # --- Wikipedia Fonksiyonu ---
52
  wikipedia.set_lang("tr")
 
55
  try:
56
  results = wikipedia.search(term)
57
  if not results: return None
58
+ # İlk sonucu alır, ilk 3 cümleyi özetler
59
  return wikipedia.summary(results[0], sentences=3, auto_suggest=False)
60
  except wikipedia.exceptions.PageError:
61
  return None
62
  except wikipedia.exceptions.RedirectError:
63
  return None
64
  except Exception as e:
65
+ # Bu loglar konsola düşer, ancak UI log akışına dahil edilmez.
66
  return None
67
 
68
  # --- LLM ile Etiket Rafine Etme Fonksiyonu ---
69
+ def refine_label_with_llm(entity_text, wiki_context, custom_label_definitions):
70
  """LLM kullanarak MISC etiketini özel etiketlerden biriyle rafine eder."""
71
  if gen_pipe is None:
72
+ return "MISC" # LLM devre dışıysa rafine etme
73
 
74
  labels_str = ", ".join(custom_label_definitions.keys())
75
  label_definitions_str = "\n".join(
76
  [f"- {k}: {v}" for k, v in custom_label_definitions.items()]
77
  )
78
 
79
+ # LLM için örnekler (Few-shot learning) - İyileştirilmiş ve daha çeşitli
80
  few_shot_examples = """ÖRNEK 1 (NORP İÇİN):
81
  VARLIK: Türk
82
  BAĞLAM: Türkler, Türkiye Cumhuriyeti'nde yaşayan ve Türkçe konuşan büyük bir etnik gruptur. (NORP tanımı ile eşleşir.)
 
97
  GÖZLEM: 'Sinekli Bakkal' yazar adı ve yayın tarihi ile birlikte bir 'roman' olarak anılıyor. Bağlamda 'romanıdır' ifadesi geçiyor.
98
  AKIL YÜRÜTME: Yazılı veya basılı eserleri, romanları ve yayınları tanımlayan etiket 'BOOK'tur. Bu tanıma uymaktadır.
99
  CEVAP: BOOK
100
+
101
+ ÖRNEK 4 (TITLE İÇİN):
102
+ VARLIK: General
103
+ BAĞLAM: Birçok orduda yüksek rütbeli bir subay unvanıdır. Türkiye'de en yüksek rütbelerden biridir. (TITLE tanımı ile eşleşir.)
104
+ GÖZLEM: 'General' bir rütbe, unvan veya pozisyon belirtiyor. Bağlamda 'yüksek rütbeli bir subay unvanı' ifadesi geçiyor.
105
+ AKIL YÜRÜTME: Kişinin unvanını, rütbesini veya pozisyonunu tanımlayan etiket 'TITLE'dır. 'General' bu tanıma uymaktadır.
106
+ CEVAP: TITLE
107
  """
108
 
109
+ # --- Chain of Thought (CoT) Prompt Yapısı --- İyileştirilmiş ve daha kuralcı
110
  prompt = f"""Sen uzman bir veri sınıflandırma sistemisin.
111
  Görevin, aşağıdaki varlığı ve bağlamı analiz ederek, **YALNIZCA** ETİKET TANIMLARI'ndan birini (veya uymuyorsa MISC'i) seçmektir.
112
 
 
134
  # LLM çağrısı
135
  outputs = gen_pipe(
136
  messages,
137
+ max_new_tokens=150, # CoT için token sayısını koruduk
138
  do_sample=False,
139
  temperature=0.1
140
  )
 
142
  # LLM çıktısını analiz etme
143
  full_output = outputs[0]["generated_text"][-1]["content"].strip()
144
  final_label = "MISC"
145
+
146
+ # CoT'yi log'a kaydetmek için parse edelim
147
  cog_output = ""
148
 
149
  try:
150
+ # Cevap satırını bulur
151
+ # Regex ile daha sağlam arama yapılabilir, ancak şimdilik listenin sonuncusunu alıyoruz.
152
  cevap_satiri = [line for line in full_output.split('\n') if 'CEVAP:' in line][-1]
153
  raw_label = cevap_satiri.split('CEVAP:')[-1].strip().upper()
154
 
155
+ # GÖZLEM ve AKIL YÜRÜTME satırlarını bul
156
+ gözlem = [line for line in full_output.split('\n') if 'GÖZLEM:' in line]
157
  akil_yurutme = [line for line in full_output.split('\n') if 'AKIL YÜRÜTME:' in line]
158
+
159
+ if gözlem and akil_yurutme:
160
+ # Sadece akıl yürütme kısmını log'a eklemek için alalım
161
  cog_output = akil_yurutme[-1].replace('AKIL YÜRÜTME:', '').strip()
162
 
163
+ # Geçerli etiketleri kontrol et
164
+ valid_labels = list(custom_label_definitions.keys()) # UNKNOWN'ı çıkarttık, zaten tanımlı değil.
165
  if raw_label in valid_labels:
166
  final_label = raw_label
167
+ # CoT çıktısını etikete ekleyerek sonraki aşamada kullanmak için
168
+ if cog_output:
169
+ return (final_label, cog_output)
170
+
171
  except IndexError:
172
+ # LLM çıktısı beklenmedik bir formattaysa MISC döner
173
  final_label = "MISC"
 
174
 
175
+ return (final_label, cog_output) # (Etiket, CoT Gerekçesi)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
+ # --- NER Pipeline Fonksiyonu (YALNIZCA 2 DEĞER ÜRETİYOR) ---
178
+ def advanced_ner_pipeline(text, target_labels, progress=gr.Progress()):
179
  """
180
  Standart NER modelini çalıştırır, MISC etiketli varlıklar için
181
  Wikipedia ve LLM kullanarak zenginleştirme (RAG) yapar.
182
  """
183
+ # İlk adımda, global başlangıç loglarını log_messages'a ekle.
184
+ log_messages = list(_initial_logs)
185
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  try:
187
+ # Not: aggregation_strategy="simple" kullanıldığı için, her birleştirilmiş varlığın
188
+ # tahmin skoru, en yüksek skorlu alt parçaya ait olacaktır.
189
  initial_results = ner_pipe(text)
190
  except Exception as e:
191
+ log_messages.append(f"❌ Hata: Temel NER modelinde sorun oluştu: {e}")
192
+ yield log_messages, []
193
  return
194
 
195
  final_results = []
196
  total_entities = len(initial_results)
197
 
198
+ log_messages.append(f"🕵️‍♀️ Toplam {total_entities} varlık inceleniyor...")
199
+
200
+ # İlk yield (2 değer)
201
+ # Bu, model yükleme loglarını Gradio arayüzüne ilk kez gönderir.
202
+ yield log_messages, None
203
 
204
  for i, entity in enumerate(initial_results):
205
  word = entity['word']
206
  label = entity['entity_group']
207
+ # YENİ: Tahmin skorunu yakala ve 4 ondalık basamağa yuvarla
208
+ score = entity['score']
209
 
210
+ # Gradio ilerleme çubuğunu güncelle
211
  current_progress = (i + 1) / total_entities
212
+ # İlerlemeyi, işlenen varlık sayısıyla güncelleyin.
213
  progress(current_progress, desc=f"İşleniyor: {word}")
214
 
215
  result_obj = {
216
  "entity": word,
217
  "initial_label": label,
218
+ "initial_score": score, # YENİ: Skoru ekle
219
  "final_label": label,
220
  "source": "Model",
221
+ "reasoning": "" # CoT gerekçesi için yeni alan
222
  }
223
 
224
+ # Eğer etiket MISC ise RAG sürecini başlat
225
+ # Ayrıca, skor ne kadar düşükse LLM'e olan ihtiyaç o kadar artar.
226
+ if label == "MISC" and gen_pipe is not None:
227
  standardized_word = word.title()
228
+ log_messages.append(f" ⚠️ MISC tespit edildi: '{word}' (Skor: {score:.2f}). Wikipedia'ya soruluyor...")
229
+ yield log_messages, None # Log mesajını gönder (2 değer)
230
 
231
  wiki_context = get_wiki_summary(standardized_word)
232
 
233
  if wiki_context:
234
+ log_messages.append(f" 📄 Wiki Bağlamı bulundu: {wiki_context[:50]}...")
235
+ yield log_messages, None # Log mesajını gönder (2 değer)
236
 
237
  # LLM'den hem etiketi hem de gerekçeyi al
238
+ new_label, reasoning = refine_label_with_llm(standardized_word, wiki_context, target_labels)
239
 
240
  result_obj["final_label"] = new_label
241
+ result_obj["source"] = "RAG+LLM (CoT)"
242
+ result_obj["reasoning"] = reasoning # Gerekçeyi kaydet
243
 
244
+ log_messages.append(f" 🔄 Etiket Güncellendi: MISC -> {new_label}")
245
  if reasoning:
246
+ log_messages.append(f" 💡 Akıl Yürütme: {reasoning[:70]}...")
247
+ yield log_messages, None # Log mesajını gönder (2 değer)
248
  else:
249
+ log_messages.append(" ❌ Wiki'de bilgi bulunamadı, MISC olarak kalıyor.")
250
+ yield log_messages, None # Log mesajını gönder (2 değer)
251
  else:
252
+ log_messages.append(f" ✅ {word} için etiket: {label} (Skor: {score:.2f})")
253
+ yield log_messages, None # Log mesajını gönder (2 değer)
254
 
255
  final_results.append(result_obj)
256
 
257
+ log_messages.append("✅ İşlem tamamlandı! Nihai sonuçlar tabloda.")
258
+ # Sonuçlar hazır olduğunda final_results'ı gönder
259
+ yield log_messages, final_results # Nihai loglar ve sonuçlar (2 değer)
260
+
261
+ # --- Özel Etiket Tanımları (23 Etikete Genişletildi) ---
262
+ custom_label_definitions = {
263
+ # Standart CoNLL-2003 etiketleri (LLM tarafından rafine edilmez, ancak sonuçta görünür)
264
+ "PER": "Kişi adları, takma adlar, ünlüler.",
265
+ "ORG": "Şirketler, kurumlar, hükümet kuruluşları.",
266
+ "LOC": "Coğrafi yerler, siyasi bölgeler, binalar.",
267
+ # MISC içinden rafine edilecek ince taneli etiketler (Toplam 19 adet)
268
+ "DATE": "Mutlak veya göreceli tarih ifadeleri (yıl, ay, gün).",
269
+ "TIME": "Günün saati, zaman aralığı.",
270
+ "MONEY": "Parasal değerler, para birimleri.",
271
+ "QUANTITY": "Ağırlık, uzunluk, hacim gibi ölçü birimleri ve sayısal değerler.",
272
+ "PERCENT": "Yüzdelik ifadeler.",
273
+ "NORP": "Milliyetler, etnik, dini veya politik gruplar.",
274
+ "LAW": "Resmi kanun, yasa, yönetmelik veya hukuki belge adı.",
275
+ "EVENT": "Savaşlar, festivaller, spor turnuvaları, doğal afetler veya kurumsal etkinlikler.",
276
+ "BOOK": "Yazılı veya basılı bir eser, yayınlanmış roman, ders kitabı.",
277
+ "MOVIE": "Sinema filmi, dizi, belgesel gibi görsel-işitsel yapıt.",
278
+ "SONG": "Müzik eseri, şarkı, beste veya albüm adı.",
279
+ "ART": "Resim, heykel, spesifik mimari eser adı (Örn: Mona Lisa).",
280
+ "AWARD": "Ödül, madalya veya nişan adı (Örn: Nobel, Oscar).",
281
+ "PRODUCT": "Ticari olarak satılan somut bir eşya, model, cihaz veya marka serisi (Örn: iPhone 15 Pro, Mercedes C200).",
282
+ "SOFTWARE": "Bilgisayar programları, mobil uygulamalar, yapay zeka sistemleri, işletim sistemleri.",
283
+ "ORG_SUB": "Şirket birimleri, üniversite fakülteleri, dernek şubeleri gibi büyük bir kurumun alt birimleri.",
284
+ "LANGUAGE": "Dillerin adı (Örn: İngilizce, Arapça).",
285
+ "TITLE": "Kişinin unvanı, rütbesini veya pozisyonunu (Örn: Profesör, General, Başkan).",
286
+ "CYBER": "URL, E-posta adresi, IP adresi, hashtag veya kullanıcı adı.",
287
+ # MISC: Kalan her şey
288
+ "MISC": "Diğer adlandırılmış varlıklar (LLM tarafından rafine edilemeyen veya uymayanlar)."
289
+ }
290
 
291
+ # --- Gradio Arayüzü Fonksiyonu (Düzeltilmiş ve Birleştirilmiş) ---
292
+ def process_ner_request(text, progress=gr.Progress()):
 
293
  """
294
  Gradio arayüzünden çağrılan ana fonksiyondur.
295
  advanced_ner_pipeline'dan gelen logları ve sonuçları HTML olarak biçimlendirir.
 
297
  all_logs = []
298
  final_results = None
299
 
300
+ for logs_step, results_step in advanced_ner_pipeline(text, custom_label_definitions, progress=progress):
301
+ all_logs = logs_step # Her adımda güncel logları al
 
302
 
 
303
  if results_step is not None:
304
  final_results = results_step
305
 
306
  # --- Geçici Log Çıktısı Oluşturma ---
307
  log_output_html = "<div style='max-height: 200px; overflow-y: scroll; border: 1px solid #eee; padding: 10px; margin-bottom: 10px; background-color: #f9f9f9; border-radius: 8px; font-family: monospace; font-size: 12px;'>"
308
  for log in all_logs:
309
+ # Stil iyileştirmeleri
310
  color = 'blue' if '✅' in log else ('orange' if '⚠️' in log else ('red' if '❌' in log else 'black'))
311
  log_output_html += f"<p style='margin: 0; color: {color};'>{log.replace(' ', '&nbsp;&nbsp;&nbsp;')}</p>"
312
  log_output_html += "</div>"
313
 
314
  # --- Geçici/Nihai Sonuç Tablosu Oluşturma ---
315
  current_results_html = ""
316
+
317
+ if final_results: # Eğer sonuç listesi boş değilse tabloyu oluştur
318
+ # Tailwind benzeri sınıflar kullanarak basit bir tablo oluştur
319
  current_results_html = """
320
  <style>
321
  .ner-table {
322
+ width:100%;
323
+ border-collapse: collapse;
324
+ font-family: Arial, sans-serif;
325
+ border-radius: 8px;
326
+ overflow: hidden;
327
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
328
  }
329
  .ner-table th, .ner-table td {
330
+ padding: 12px 15px;
331
+ text-align: left;
332
+ border-bottom: 1px solid #ddd;
333
  }
334
  .ner-table th {
335
+ background-color: #3b82f6; /* Tailwind blue-500 */
336
+ color: white;
337
+ font-weight: bold;
338
+ text-transform: uppercase;
339
  }
340
  .ner-table tr:nth-child(even) {
341
+ background-color: #f3f4f6; /* Tailwind gray-100 */
342
  }
343
  .ner-table tr:hover {
344
+ background-color: #e5e7eb; /* Tailwind gray-200 */
345
  }
346
  .tooltip-text {
347
+ visibility: hidden;
348
+ background-color: #333;
349
+ color: #fff;
350
+ text-align: center;
351
+ border-radius: 6px;
352
+ padding: 5px 10px;
353
+ position: absolute;
354
+ z-index: 1;
355
+ bottom: 125%; /* Üstte göster */
356
+ left: 50%;
357
+ transform: translateX(-50%);
358
+ opacity: 0;
359
+ transition: opacity 0.3s;
360
+ width: 300px;
361
+ font-size: 11px;
362
+ line-height: 1.4;
363
  }
364
  .tooltip-container:hover .tooltip-text {
365
+ visibility: visible;
366
+ opacity: 1;
367
  }
368
  .tooltip-container {
369
+ position: relative;
370
+ display: inline-block;
371
  }
372
  </style>
373
  <table class='ner-table'>\n"""
374
  current_results_html += " <tr>\n"
375
  current_results_html += " <th>VARLIK</th>\n"
376
  current_results_html += " <th>İLK ETİKET</th>\n"
377
+ current_results_html += " <th>İLK TAHMİN SKORU</th>\n" # YENİ SÜTUN
378
  current_results_html += " <th>RAG SONRASI ETİKETİ (GEREKÇE)</th>\n"
379
  current_results_html += " <th>KAYNAK</th>\n"
380
  current_results_html += " </tr>\n"
 
381
  for item in final_results:
382
+ # Etiket renklendirmesi için basit bir mantık (23 etiket için rastgele renkler atandı)
383
  color_map = {
384
  "PER": "background-color: #f8c291;", "LOC": "background-color: #a2c4c9;", "ORG": "background-color: #b3c99f;",
385
+ "MISC": "background-color: #fef08a;",
386
+ "DATE": "background-color: #e5ccff;", "TIME": "background-color: #d1d5db;", "MONEY": "background-color: #fcd34d;",
387
+ "QUANTITY": "background-color: #bfdbfe;", "PERCENT": "background-color: #99f6e4;", "NORP": "background-color: #fbcfe8;",
388
+ "LAW": "background-color: #f0abfc;", "EVENT": "background-color: #a7f3d0;", "BOOK": "background-color: #ffedd5;",
389
+ "MOVIE": "background-color: #c7d2fe;", "SONG": "background-color: #e9d5ff;", "ART": "background-color: #bae6fd;",
390
+ "AWARD": "background-color: #fee2e2;", "PRODUCT": "background-color: #ffc999;", "SOFTWARE": "background-color: #d1fae5;",
391
+ "ORG_SUB": "background-color: #ccfbf1;", "LANGUAGE": "background-color: #fef9c3;", "TITLE": "background-color: #fecaca;",
392
+ "CYBER": "background-color: #dbeafe;"
393
  }
394
  final_label_style = color_map.get(item['final_label'], "")
395
 
396
+ # CoT gerekçesini bir tooltip olarak ekleme
397
  reasoning_tooltip = ""
398
  if item.get("reasoning"):
399
+ # Gerekçe varsa tooltip'i oluştur
400
  reasoning_tooltip = f"""
401
  <div class='tooltip-container'>
402
  {item['final_label']}
 
408
  else:
409
  reasoning_tooltip = item['final_label']
410
 
411
+ # Skoru formatla
412
  score_formatted = f"{item['initial_score'] * 100:.2f}%"
413
+
414
+ # Skorun rengini güven seviyesine göre ayarlama (Yüksek skor=Yeşil, Düşük skor=Turuncu/Kırmızı)
415
+ score_color = ""
416
+ if item['initial_score'] > 0.95:
417
+ score_color = "color: #10b981;" # Yeşil
418
+ elif item['initial_score'] > 0.8:
419
+ score_color = "color: #f59e0b;" # Sarı/Turuncu
420
+ else:
421
+ score_color = "color: #ef4444;" # Kırmızı
422
 
423
  current_results_html += " <tr>\n"
424
  current_results_html += f" <td>{item['entity']}</td>\n"
425
  current_results_html += f" <td>{item['initial_label']}</td>\n"
426
+ current_results_html += f" <td style='{score_color} font-weight: bold;'>{score_formatted}</td>\n" # YENİ SKOR SÜTUNU
427
  current_results_html += f" <td style='{final_label_style}'>{reasoning_tooltip}</td>\n"
428
  current_results_html += f" <td>{item['source']}</td>\n"
429
  current_results_html += " </tr>\n"
430
  current_results_html += "</table>"
431
 
432
+ # Gradio'ya logları ve sonuç tablosunu gönder (her adımda güncelleyin)
433
  yield log_output_html, current_results_html
434
 
435
  # --- Gradio Arayüzünü Tanımla ---
 
 
 
 
 
 
 
 
436
  iface = gr.Interface(
437
  fn=process_ner_request,
438
+ inputs=gr.Textbox(lines=5, placeholder="Metin giriniz...", label="Giriş Metni"),
439
+ # Çıktı olarak 2 ayrı HTML bileşeni döndürülmeli.
440
+ outputs=[gr.HTML(label="İşlem Logları"), gr.HTML(label="Zenginleştirilmiş NER Sonuçları")],
441
+ title="Gelişmiş İnce Taneli NER (23 Etiket - RAG/LLM Destekli)",
442
+ description="Metindeki varlıkları tanır ve MISC etiketlileri, 23 ince taneli kategoriye dönüştürmek için Wikipedia (RAG) ve LLM (Qwen2.5-1.5B) kullanılmaktadır. İlk modelin tahmin skorları da artık tabloda yer almaktadır.",
 
 
 
 
 
 
 
443
  examples=[
444
+ # Yeni etiketleri test eden örnekler eklendi
445
  ["Milli Eğitim Bakanlığı'na bağlı Lise Birimleri, 2024 Türkiye Kupası etkinliğine katılacak ve %15 indirim uygulayacak."],
446
  ["General Vural, Türkçe dilini kullanan Türk askerlerini, https://example.com üzerinden uyardı. 'Hürriyet Kasidesi' eserini okudu."],
447
  ["Windows 11 işletim sistemi, 1000 TL karşılığında satışa sunulmuştur. 'Cumhurbaşkanı' unvanına sahip kişi, Kanun maddesini değiştirdi."],
 
451
 
452
  # --- Gradio Uygulamasını Başlat ---
453
  if __name__ == "__main__":
454
+ # Eğer port belirlenmemişse, varsayılan 7860 kullanılır.
455
  port = int(os.environ.get('PORT', 7860))
456
  iface.launch(share=False, server_port=port)